gaptool-server 0.1.1 → 0.2.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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
3
  require 'thin'
4
- require 'gaptool-server/app.rb'
4
+ #require 'gaptool-server/app.rb'
5
5
 
6
6
  # Hijack the thin module to set default options to run in the right place
7
7
  module Thin
data/config.ru CHANGED
@@ -1,17 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'sinatra/base'
4
- require 'sinatra'
5
- require 'json'
6
3
  require 'redis'
7
- require 'yaml'
8
- require 'erb'
9
- require 'aws-sdk'
10
- require 'openssl'
11
- require 'net/ssh'
12
4
 
13
- $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
5
+ ENV['REDIS_HOST'] = 'localhost' unless ENV['REDIS_HOST']
6
+ ENV['REDIS_PORT'] = '6379' unless ENV['REDIS_PORT']
7
+ ENV['REDIS_PASS'] = nil unless ENV['REDIS_PASS']
8
+ $redis = Redis.new(:host => ENV['REDIS_HOST'], :port => ENV['REDIS_PORT'], :password => ENV['REDIS_PASS'])
14
9
 
15
- require 'gaptool-server/app.rb'
10
+ libpath = File.expand_path(File.join(File.dirname(__FILE__), "lib"))
16
11
 
17
- run GaptoolServer
12
+ $:.unshift(libpath)
13
+
14
+ require "#{libpath}/app.rb"
15
+ #Dir["#{ENV['HOME']}/.gaptool-server-plugins/*.rb"].each {|file| require file }
16
+ #Dir["#{libpath}/plugins/*.rb"].each {|file| require file }
17
+
18
+ instance = GaptoolServer.new
19
+ #$redis.smembers("plugins").each do |plugin|
20
+ ## puts "Loading Plugin #{plugin}"
21
+ # instance.extend(Object.instance_eval{remove_const(plugin)})
22
+ #end
23
+
24
+ run instance
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "gaptool-server"
8
- s.version = "0.1.1"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Matt Bailey"]
12
- s.date = "2012-12-17"
12
+ s.date = "2013-01-05"
13
13
  s.description = "gaptool-server for managing cloud resources"
14
14
  s.email = "m@mdb.io"
15
15
  s.executables = ["gaptool-server"]
@@ -29,9 +29,19 @@ Gem::Specification.new do |s|
29
29
  "bin/gaptool-server",
30
30
  "config.ru",
31
31
  "gaptool-server.gemspec",
32
- "lib/gaptool-server/app.rb",
33
- "lib/gaptool-server/views/hosts.erb",
34
- "lib/gaptool-server/views/init.erb",
32
+ "lib/app.rb",
33
+ "lib/helpers/gaptool-base.rb",
34
+ "lib/helpers/init.rb",
35
+ "lib/helpers/nicebytes.rb",
36
+ "lib/helpers/partials.rb",
37
+ "lib/models/init.rb",
38
+ "lib/models/user.rb",
39
+ "lib/public/css/common.css",
40
+ "lib/public/js/manifest.txt",
41
+ "lib/routes/init.rb",
42
+ "lib/routes/main.rb",
43
+ "lib/views/hosts.erb",
44
+ "lib/views/init.erb",
35
45
  "setup.rb",
36
46
  "test/helper.rb",
37
47
  "test/test_gaptool-server.rb"
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ require 'sinatra'
3
+ require 'json'
4
+ require 'yaml'
5
+ require 'erb'
6
+ require 'aws-sdk'
7
+ require 'openssl'
8
+ require 'net/ssh'
9
+ require 'peach'
10
+
11
+ class GaptoolServer < Sinatra::Application
12
+ enable :sessions
13
+ disable :show_exceptions
14
+ enable :raise_errors
15
+
16
+ before do
17
+ error 401 unless $redis.hget('users', env['HTTP_X_GAPTOOL_USER']) == env['HTTP_X_GAPTOOL_KEY']
18
+ error 401 unless env['HTTP_X_GAPTOOL_USER'] && env['HTTP_X_GAPTOOL_KEY']
19
+ end
20
+
21
+ helpers do
22
+ include Rack::Utils
23
+ alias_method :h, :escape_html
24
+ end
25
+ end
26
+
27
+ require_relative 'helpers/init'
28
+ require_relative 'models/init'
29
+ require_relative 'routes/init'
@@ -0,0 +1,155 @@
1
+ # encoding: utf-8
2
+ module GaptoolBaseHelpers
3
+ def hash2redis( key, hash )
4
+ hash.keys.each do |hkey|
5
+ $redis.hset key, hkey, hash[hkey]
6
+ end
7
+ end
8
+
9
+ def putkey( host )
10
+ @key = OpenSSL::PKey::RSA.new 2048
11
+ @pubkey = "#{@key.ssh_type} #{[@key.to_blob].pack('m0')} GAPTOOL_GENERATED_KEY"
12
+ ENV['SSH_AUTH_SOCK'] = ''
13
+ Net::SSH.start(host, 'admin', :key_data => [$redis.hget('config', 'gaptoolkey')], :config => false, :keys_only => true, :paranoid => false) do |ssh|
14
+ ssh.exec! "grep -v GAPTOOL_GENERATED_KEY ~/.ssh/authorized_keys > /tmp/pubkeys"
15
+ ssh.exec! "cat /tmp/pubkeys > ~/.ssh/authorized_keys"
16
+ ssh.exec! "rm /tmp/pubkeys"
17
+ ssh.exec! "echo #{@pubkey} >> ~/.ssh/authorized_keys"
18
+ end
19
+ return @key.to_pem
20
+ end
21
+
22
+ def gt_securitygroup(role, environment, zone)
23
+ AWS.config(:access_key_id => $redis.hget('config', 'aws_id'), :secret_access_key => $redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{zone.chop}.amazonaws.com")
24
+ @ec2 = AWS::EC2.new
25
+ groupname = "#{role}-#{environment}"
26
+ default_list = [ 22 ]
27
+ @ec2.security_groups.each do |group|
28
+ if group.name == "#{role}-#{environment}"
29
+ return group.id
30
+ end
31
+ end
32
+ internet = ['0.0.0.0/0']
33
+ sg = @ec2.security_groups.create("#{role}-#{environment}")
34
+ sg.authorize_ingress :tcp, 22, *internet
35
+ return sg.id
36
+ end
37
+
38
+ def runservice(host, role, environment, service, keys, state)
39
+ ENV['SSH_AUTH_SOCK'] = ''
40
+ Net::SSH.start(host, 'admin', :key_data => [$redis.hget('config', 'gaptoolkey')], :config => false, :keys_only => true, :paranoid => false) do |ssh|
41
+ if state == 'start'
42
+ ssh.exec! "echo '#{keys.to_yaml}' > /tmp/apikeys-#{service}.yml"
43
+ ssh.exec! "sudo restart #{service} || sudo start #{service} || exit 0"
44
+ $redis.lpush("running", "{:hostname => '#{host}', :role => '#{role}', :environment => '#{environment}', :service => '#{service}'}")
45
+ elsif state == 'stop'
46
+ ssh.exec! "rm /tmp/apikeys-#{service}.yml"
47
+ ssh.exec! "sudo stop #{service} || exit 0"
48
+ $redis.lrem("running", -1, "{:hostname => '#{host}', :role => '#{role}', :environment => '#{environment}', service => '#{service}'}")
49
+ end
50
+ end
51
+ end
52
+
53
+ def balanceservices(role, environment)
54
+ @runable = Array.new
55
+ @available = Array.new
56
+ @totalcap = 0
57
+ @volume = 0
58
+ $redis.keys("host:#{role}:#{environment}:*").each do |host|
59
+ @available << {
60
+ :hostname => $redis.hget(host, 'hostname'),
61
+ :instance => $redis.hget(host, 'instance'),
62
+ :capacity => $redis.hget(host, 'capacity').to_i,
63
+ }
64
+ @totalcap = @totalcap + $redis.hget(host, 'capacity').to_i
65
+ end
66
+ $redis.keys("service:#{role}:#{environment}:*").each do |service|
67
+ unless service =~ /:count/
68
+ if $redis.hget(service, 'run').to_i == 1
69
+ @runable << {
70
+ :name => $redis.hget(service, 'name'),
71
+ :keys => eval($redis.hget(service, 'keys')),
72
+ :weight => $redis.hget(service, 'weight').to_i
73
+ }
74
+ end
75
+ end
76
+ end
77
+ @volume = 0
78
+ @runable.each do |service|
79
+ @volume += service[:weight]
80
+ end
81
+ if @totalcap < @volume
82
+ return { :error => true, :message => "This would overcommit, remove some resources or add nodes", :totalcap => @totalcap, :volume => @volume }
83
+ else
84
+ @runable.sort! { |x, y| x[:weight] <=> y[:weight] }
85
+ @available.sort! { |x, y| x[:capacity] <=> y[:capacity] }
86
+ @runlist = Array.new
87
+ @svctab = Hash.new
88
+ @runable.each do |event|
89
+ @svctab[event[:name]] = Array.new
90
+ end
91
+ @exitrunable = 0
92
+ while @runable != []
93
+ break if @exitrunable == 1
94
+ @available.each do |host|
95
+ break if @runable.last.nil?
96
+ @exitrunable = 1 if @svctab[@runable.last[:name]].include? host[:hostname]
97
+ break if @svctab[@runable.last[:name]].include? host[:hostname]
98
+ if host[:capacity] >= @runable.last[:weight]
99
+ host[:capacity] = host[:capacity] - @runable.last[:weight]
100
+ @svctab[@runable.last[:name]] << host[:hostname]
101
+ @runlist << { :host => host, :service => @runable.pop }
102
+ end
103
+ end
104
+ end
105
+ return @runlist
106
+ end
107
+ end
108
+
109
+ def servicestopall(role, environment)
110
+ $redis.lrange('running', 0, -1).peach do |service|
111
+ line = eval(service)
112
+ if line[:role] == role && line[:environment] == environment
113
+ runservice(line[:hostname], role, environment, line[:service], nil, 'stop')
114
+ end
115
+ end
116
+ end
117
+
118
+ def hostsgen(zone)
119
+ AWS.config(:access_key_id => $redis.hget('config', 'aws_id'), :secret_access_key => $redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{zone}.amazonaws.com")
120
+ @ec2 = AWS::EC2.new
121
+ hosts = Hash.new
122
+ $redis.keys("host:*").each do |host|
123
+ $redis.hset(host, 'hostname', @ec2.instances[$redis.hget(host, 'instance')].dns_name)
124
+ hosts.merge!(host.gsub(/host:/, '').gsub(/:/,'-') => Resolv.getaddress($redis.hget(host, 'hostname')))
125
+ if $redis.hget(host, 'alias')
126
+ hosts.merge!($redis.hget(host, 'alias') => Resolv.getaddress($redis.hget(host, 'hostname')))
127
+ end
128
+ end
129
+
130
+ hostsfile = "# DO NOT EDIT, GENERATED BY GAPTOOL\n127.0.0.1 localhost\n::1 localhost\n"
131
+ hosts.keys.each do |key|
132
+ hostsfile += "#{hosts[key]} #{key} # PLACED BY GT\n"
133
+ end
134
+ # $redis.keys("host:*").peach do |host|
135
+ # ENV['SSH_AUTH_SOCK'] = ''
136
+ # Net::SSH.start($redis.hget(host, 'hostname'), 'admin', :key_data => [$redis.hget('config', 'gaptoolkey')], :config => false, :keys_only => true, :paranoid => false) do |ssh|
137
+ # ssh.exec! "echo \"127.0.0.1 #{currenthost}\n\" \"#{hostsfile}\" > /etc/hosts.generated"
138
+ # end
139
+ # end
140
+ return hosts
141
+ end
142
+
143
+ def getservices()
144
+ services = Array.new
145
+ $redis.keys('service:*').each do |service|
146
+ unless service =~ /:count/
147
+ line = $redis.hgetall(service)
148
+ line['keys'] = eval(line['keys'])
149
+ services << line
150
+ end
151
+ end
152
+ return services
153
+ end
154
+
155
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ require_relative 'partials'
3
+ GaptoolServer.helpers PartialPartials
4
+
5
+ require_relative 'nicebytes'
6
+ GaptoolServer.helpers NiceBytes
7
+
8
+ require_relative 'gaptool-base'
9
+ GaptoolServer.helpers GaptoolBaseHelpers
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ module NiceBytes
3
+ K = 2.0**10
4
+ M = 2.0**20
5
+ G = 2.0**30
6
+ T = 2.0**40
7
+ def nice_bytes( bytes, max_digits=3 )
8
+ value, suffix, precision = case bytes
9
+ when 0...K
10
+ [ bytes, 'B', 0 ]
11
+ else
12
+ value, suffix = case bytes
13
+ when K...M then [ bytes / K, 'kiB' ]
14
+ when M...G then [ bytes / M, 'MiB' ]
15
+ when G...T then [ bytes / G, 'GiB' ]
16
+ else [ bytes / T, 'TiB' ]
17
+ end
18
+ used_digits = case value
19
+ when 0...10 then 1
20
+ when 10...100 then 2
21
+ when 100...1000 then 3
22
+ else 4
23
+ end
24
+ leftover_digits = max_digits - used_digits
25
+ [ value, suffix, leftover_digits > 0 ? leftover_digits : 0 ]
26
+ end
27
+ "%.#{precision}f#{suffix}" % value
28
+ end
29
+ module_function :nice_bytes
30
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ module PartialPartials
3
+ def spoof_request(uri,env_modifications={})
4
+ call(env.merge("PATH_INFO" => uri).merge(env_modifications)).last.join
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ #require 'sequel'
3
+ # DB = Sequel.postgres 'dbname', user:'bduser', password:'dbpass', host:'localhost'
4
+ # DB << "SET CLIENT_ENCODING TO 'UTF8';"
5
+
6
+ #require_relative 'user'
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ #class User < Sequel::Model
3
+ # ...
4
+ #end
@@ -0,0 +1,4 @@
1
+ html, body { background:#eee; margin:0; padding:0; color:#000 }
2
+ h1,h2,h3,h4,h5,h6 { font-family:sans-serif; border-bottom:1px solid #aaa; margin:2em 0 0.5em 0 }
3
+ h1#title { background:#333; margin-top:0; color:#ccc; padding:0.1em 0.5em; font-size:105% }
4
+ #content { margin:1em 2em }
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require_relative 'main'
@@ -0,0 +1,199 @@
1
+ # encoding: utf-8
2
+ class GaptoolServer < Sinatra::Application
3
+
4
+ get '/' do
5
+ "You must be lost. Read the instructions."
6
+ end
7
+
8
+ get '/servicebalance/:role/:environment' do
9
+ runlist = balanceservices(params[:role], params[:environment])
10
+ unless runlist.kind_of? Hash
11
+ servicestopall(params[:role], params[:environment])
12
+ runlist.peach do |event|
13
+ runservice(event[:host][:hostname], params[:role], params[:environment], event[:service][:name], event[:service][:keys], 'start')
14
+ end
15
+ end
16
+ runlist.to_json
17
+ end
18
+
19
+ put '/service/:role/:environment' do
20
+ data = JSON.parse request.body.read
21
+ count = $redis.incr("service:#{params[:role]}:#{params[:environment]}:#{data['name']}:count")
22
+ key = "service:#{params[:role]}:#{params[:environment]}:#{data['name']}:#{count}"
23
+ $redis.hset(key, 'name', data['name'])
24
+ $redis.hset(key, 'keys', data['keys'])
25
+ $redis.hset(key, 'weight', data['weight'])
26
+ $redis.hset(key, 'role', params[:role])
27
+ $redis.hset(key, 'environment', params[:environment])
28
+ $redis.hset(key, 'run', data['enabled'])
29
+ {
30
+ :role => params[:role],
31
+ :environment => params[:environment],
32
+ :service => data['name'],
33
+ :count => count,
34
+ }.to_json
35
+ end
36
+
37
+ delete '/service/:role/:environment/:service' do
38
+ if $redis.get("service:#{params[:role]}:#{params[:environment]}:#{params[:service]}:count") == '0'
39
+ count = 0
40
+ else
41
+ count = $redis.decr("service:#{params[:role]}:#{params[:environment]}:#{params[:service]}:count")
42
+ service = eval($redis.range("running", 0, -1).grep(/scoring/).last)
43
+ runservice(service[:hostname], params[:role], params[:environment], params[:service], 'stop')
44
+ $redis.del("service:#{params[:role]}:#{params[:environment]}:#{params[:service]}:#{count + 1}")
45
+ end
46
+ {
47
+ :role => params[:role],
48
+ :environment => params[:environment],
49
+ :service => params[:service],
50
+ :count => count,
51
+ }.to_json
52
+ end
53
+
54
+ get '/services' do
55
+ getservices().to_json
56
+ end
57
+
58
+ post '/regenhosts' do
59
+ data = JSON.parse request.body.read
60
+ hostsgen(data['zone'])
61
+ hosts.to_json
62
+ end
63
+
64
+ post '/init' do
65
+ data = JSON.parse request.body.read
66
+ AWS.config(:access_key_id => $redis.hget('config', 'aws_id'), :secret_access_key => $redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{data['zone'].chop}.amazonaws.com")
67
+ @ec2 = AWS::EC2.new
68
+ # create shared secret to reference in /register
69
+ @secret = (0...8).map{65.+(rand(26)).chr}.join
70
+ data.merge!("secret" => @secret)
71
+ sgid = gt_securitygroup(data['role'], data['environment'], data['zone'])
72
+ if data['mirror']
73
+ instance = @ec2.instances.create(
74
+ :image_id => $redis.hget("amis", data['zone'].chop),
75
+ :availability_zone => data['zone'],
76
+ :instance_type => data['itype'],
77
+ :key_name => "gaptool",
78
+ :security_group_ids => sgid,
79
+ :user_data => "#!/bin/bash\ncurl --silent -H 'X-GAPTOOL-USER: #{env['HTTP_X_GAPTOOL_USER']}' -H 'X-GAPTOOL-KEY: #{env['HTTP_X_GAPTOOL_KEY']}' #{$redis.hget('config', 'url')}/register -X PUT --data '#{data.to_json}' | bash",
80
+ :block_device_mappings => {
81
+ "/dev/sdf" => {
82
+ :volume_size => data['mirror'].to_i,
83
+ :delete_on_termination => false
84
+ },
85
+ "/dev/sdg" => {
86
+ :volume_size => data['mirror'].to_i,
87
+ :delete_on_termination => false
88
+ }
89
+ }
90
+ )
91
+ else
92
+ instance = @ec2.instances.create(
93
+ :image_id => $redis.hget("amis", data['zone'].chop),
94
+ :availability_zone => data['zone'],
95
+ :instance_type => data['itype'],
96
+ :key_name => "gaptool",
97
+ :security_group_ids => sgid,
98
+ :user_data => "#!/bin/bash\ncurl --silent -H 'X-GAPTOOL-USER: #{env['HTTP_X_GAPTOOL_USER']}' -H 'X-GAPTOOL-KEY: #{env['HTTP_X_GAPTOOL_KEY']}' #{$redis.hget('config', 'url')}/register -X PUT --data '#{data.to_json}' | bash"
99
+ )
100
+ end
101
+ # Add host tag
102
+ instance.add_tag('Name', :value => "#{data['role']}-#{data['environment']}-#{instance.id}")
103
+ # Create temporary redis entry for /register to pull the instance id
104
+ $redis.set("instance:#{data['role']}:#{data['environment']}:#{@secret}", instance.id)
105
+ "{\"instance\":\"#{instance.id}\"}"
106
+ end
107
+
108
+ post '/terminate' do
109
+ data = JSON.parse request.body.read
110
+ AWS.config(:access_key_id => $redis.hget('config', 'aws_id'), :secret_access_key => $redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{data['zone']}.amazonaws.com")
111
+ @ec2 = AWS::EC2.new
112
+ begin
113
+ @instance = @ec2.instances[data['id']]
114
+ res = @instance.terminate
115
+ res = $redis.del($redis.keys("*#{data['id']}"))
116
+ out = {data['id'] => {'status'=> 'terminated'}}
117
+ rescue
118
+ error 404
119
+ end
120
+ out.to_json
121
+ end
122
+
123
+ put '/register' do
124
+ data = JSON.parse request.body.read
125
+ AWS.config(:access_key_id => $redis.hget('config', 'aws_id'), :secret_access_key => $redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{data['zone'].chop}.amazonaws.com")
126
+ @ec2 = AWS::EC2.new
127
+ @instance = @ec2.instances[$redis.get("instance:#{data['role']}:#{data['environment']}:#{data['secret']}")]
128
+ hostname = @instance.dns_name
129
+ delete = $redis.del("instance:#{data['role']}:#{data['environment']}:#{data['secret']}")
130
+ @apps = Array.new
131
+ $redis.keys("app:*").each do |app|
132
+ if $redis.hget(app, 'role') == data['role']
133
+ @apps << app.gsub('app:', '')
134
+ end
135
+ end
136
+ data.merge!("capacity" => $redis.hget('capacity', data['itype']))
137
+ data.merge!("hostname" => hostname)
138
+ data.merge!("apps" => @apps.to_json)
139
+ data.merge!("instance" => @instance.id)
140
+ hash2redis("host:#{data['role']}:#{data['environment']}:#{@instance.id}", data)
141
+ @json = {
142
+ 'hostname' => hostname,
143
+ 'recipe' => 'init',
144
+ 'number' => @instance.id,
145
+ 'run_list' => ['recipe[init]'],
146
+ 'role' => data['role'],
147
+ 'environment' => data['environment'],
148
+ 'chefrepo' => $redis.hget('config', 'chefrepo'),
149
+ 'chefbranch' => $redis.hget('config', 'chefbranch'),
150
+ 'identity' => $redis.hget('config','initkey'),
151
+ 'appuser' => $redis.hget('config','appuser'),
152
+ 'apps' => @apps
153
+ }.to_json
154
+ erb :init
155
+ end
156
+
157
+ get '/hosts' do
158
+ out = Array.new
159
+ $redis.keys("host:*").each do |host|
160
+ out << $redis.hgetall(host)
161
+ end
162
+ out.to_json
163
+ end
164
+
165
+ get '/apps' do
166
+ out = Hash.new
167
+ $redis.keys("app:*").each do |app|
168
+ out.merge!(app => $redis.hgetall(app))
169
+ end
170
+ out.to_json
171
+ end
172
+
173
+ get '/hosts/:role' do
174
+ out = Array.new
175
+ $redis.keys("host:#{params[:role]}:*").each do |host|
176
+ out << $redis.hgetall(host)
177
+ end
178
+ out.to_json
179
+ end
180
+
181
+ get '/hosts/:role/:environment' do
182
+ out = Array.new
183
+ $redis.keys("host:#{params[:role]}:#{params[:environment]}*").each do |host|
184
+ out << $redis.hgetall(host)
185
+ end
186
+ out.to_json
187
+ end
188
+
189
+ get '/host/:role/:environment/:instance' do
190
+ $redis.hgetall("host:#{params[:role]}:#{params[:environment]}:#{params[:instance]}").to_json
191
+ end
192
+
193
+ get '/ssh/:role/:environment/:instance' do
194
+ @host = $redis.hget("host:#{params[:role]}:#{params[:environment]}:#{params[:instance]}", 'hostname')
195
+ @key = putkey(@host)
196
+ {'hostname' => @host, 'key' => @key, 'pubkey' => @pubkey}.to_json
197
+ end
198
+
199
+ end
@@ -2,7 +2,7 @@
2
2
  <body>
3
3
  <ul>
4
4
  <% @hosts.each do |host|%>
5
- <li><%=host%> <%=@redis.get host%></li>
5
+ <li><%=host%> <%=$redis.get host%></li>
6
6
  <% end %>
7
7
  </ul>
8
8
  </body>
@@ -1,10 +1,10 @@
1
1
  apt-get install -y zsh git libssl-dev ruby1.9.1-full build-essential
2
2
  gem install --bindir /usr/local/bin --no-ri --no-rdoc chef
3
3
  cat << 'EOFKEY' > /root/.ssh/id_rsa
4
- <%=@redis.hget('config', 'initkey')%>
4
+ <%=$redis.hget('config', 'initkey')%>
5
5
  EOFKEY
6
6
  chmod 600 /root/.ssh/id_rsa
7
7
  echo 'StrictHostKeyChecking no' > /root/.ssh/config
8
- git clone -b <%=@redis.hget('config', 'chefbranch')%> <%=@redis.hget('config', 'chefrepo')%> /root/ops
8
+ git clone -b <%=$redis.hget('config', 'chefbranch')%> <%=$redis.hget('config', 'chefrepo')%> /root/ops
9
9
  echo '<%=@json%>' > /root/init.json
10
10
  chef-solo -c /root/ops/cookbooks/init.rb -j /root/init.json && (rm /root/.ssh/id_rsa; userdel -r ubuntu; rm -rf /root/.ssh; rm -rf /root/ops; rm -rf /root/init.json)
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: gaptool-server
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.1
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Matt Bailey
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-12-17 00:00:00 Z
13
+ date: 2013-01-05 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: sinatra
@@ -176,9 +176,19 @@ files:
176
176
  - bin/gaptool-server
177
177
  - config.ru
178
178
  - gaptool-server.gemspec
179
- - lib/gaptool-server/app.rb
180
- - lib/gaptool-server/views/hosts.erb
181
- - lib/gaptool-server/views/init.erb
179
+ - lib/app.rb
180
+ - lib/helpers/gaptool-base.rb
181
+ - lib/helpers/init.rb
182
+ - lib/helpers/nicebytes.rb
183
+ - lib/helpers/partials.rb
184
+ - lib/models/init.rb
185
+ - lib/models/user.rb
186
+ - lib/public/css/common.css
187
+ - lib/public/js/manifest.txt
188
+ - lib/routes/init.rb
189
+ - lib/routes/main.rb
190
+ - lib/views/hosts.erb
191
+ - lib/views/init.erb
182
192
  - setup.rb
183
193
  - test/helper.rb
184
194
  - test/test_gaptool-server.rb
@@ -195,7 +205,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
195
205
  requirements:
196
206
  - - ">="
197
207
  - !ruby/object:Gem::Version
198
- hash: -3959515583740972889
208
+ hash: 1050799946438420370
199
209
  segments:
200
210
  - 0
201
211
  version: "0"
@@ -1,336 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'sinatra/base'
4
- require 'sinatra'
5
- require 'json'
6
- require 'redis'
7
- require 'yaml'
8
- require 'erb'
9
- require 'aws-sdk'
10
- require 'openssl'
11
- require 'net/ssh'
12
- require 'peach'
13
-
14
- ENV['REDIS_HOST'] = 'localhost' unless ENV['REDIS_HOST']
15
- ENV['REDIS_PORT'] = '6379' unless ENV['REDIS_PORT']
16
- ENV['REDIS_PASS'] = nil unless ENV['REDIS_PASS']
17
-
18
- class GaptoolServer < Sinatra::Base
19
- # Don't generate fancy HTML for stack traces.
20
- disable :show_exceptions
21
- # Allow errors to get out of the app so Cucumber can display them.
22
- enable :raise_errors
23
-
24
- def hash2redis( key, hash )
25
- hash.keys.each do |hkey|
26
- @redis.hset key, hkey, hash[hkey]
27
- end
28
- end
29
-
30
- before do
31
- @redis = Redis.new(:host => ENV['REDIS_HOST'], :port => ENV['REDIS_PORT'], :password => ENV['REDIS_PASS'])
32
- error 401 unless @redis.hget('users', env['HTTP_X_GAPTOOL_USER']) == env['HTTP_X_GAPTOOL_KEY']
33
- error 401 unless env['HTTP_X_GAPTOOL_USER'] && env['HTTP_X_GAPTOOL_KEY']
34
- end
35
-
36
- def putkey( host )
37
- @key = OpenSSL::PKey::RSA.new 2048
38
- @pubkey = "#{@key.ssh_type} #{[@key.to_blob].pack('m0')} GAPTOOL_GENERATED_KEY"
39
- ENV['SSH_AUTH_SOCK'] = ''
40
- Net::SSH.start(host, 'admin', :key_data => [@redis.hget('config', 'gaptoolkey')], :config => false, :keys_only => true, :paranoid => false) do |ssh|
41
- ssh.exec! "grep -v GAPTOOL_GENERATED_KEY ~/.ssh/authorized_keys > /tmp/pubkeys"
42
- ssh.exec! "cat /tmp/pubkeys > ~/.ssh/authorized_keys"
43
- ssh.exec! "rm /tmp/pubkeys"
44
- ssh.exec! "echo #{@pubkey} >> ~/.ssh/authorized_keys"
45
- end
46
- return @key.to_pem
47
- end
48
-
49
- def gt_securitygroup(role, environment, zone)
50
- AWS.config(:access_key_id => @redis.hget('config', 'aws_id'), :secret_access_key => @redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{zone.chop}.amazonaws.com")
51
- @ec2 = AWS::EC2.new
52
- groupname = "#{role}-#{environment}"
53
- default_list = [ 22 ]
54
- @ec2.security_groups.each do |group|
55
- if group.name == "#{role}-#{environment}"
56
- return group.id
57
- end
58
- end
59
- internet = ['0.0.0.0/0']
60
- sg = @ec2.security_groups.create("#{role}-#{environment}")
61
- sg.authorize_ingress :tcp, 22, *internet
62
- return sg.id
63
- end
64
-
65
- def runservice(host, service, keys)
66
- ENV['SSH_AUTH_SOCK'] = ''
67
- Net::SSH.start(host, 'admin', :key_data => [@redis.hget('config', 'gaptoolkey')], :config => false, :keys_only => true, :paranoid => false) do |ssh|
68
- ssh.exec! "echo '#{keys.to_yaml}' > /tmp/apikeys-#{service}.yml"
69
- ssh.exec! "sudo restart #{service} || sudo start #{service} || exit 0"
70
- end
71
- end
72
-
73
- def balanceservices(role, environment)
74
- @runable = Array.new
75
- @available = Array.new
76
- @totalcap = 0
77
- @volume = 0
78
- @redis.keys("host:#{role}:#{environment}:*").each do |host|
79
- @available << {
80
- :hostname => @redis.hget(host, 'hostname'),
81
- :instance => @redis.hget(host, 'instance'),
82
- :capacity => @redis.hget(host, 'capacity').to_i,
83
- }
84
- @totalcap = @totalcap + @redis.hget(host, 'capacity').to_i
85
- end
86
- @redis.keys("service:#{role}:#{environment}:*").each do |service|
87
- unless service =~ /:count/
88
- if @redis.hget(service, 'run').to_i == 1
89
- @runable << {
90
- :name => @redis.hget(service, 'name'),
91
- :keys => eval(@redis.hget(service, 'keys')),
92
- :weight => @redis.hget(service, 'weight').to_i
93
- }
94
- end
95
- end
96
- end
97
- @volume = 0
98
- @runable.each do |service|
99
- @volume += service[:weight]
100
- end
101
- if @totalcap < @volume
102
- return {'error' => true,"message" => "This would overcommit, remove some resources or add nodes","totalcap" => @totalcap, "volume" => @volume}.to_json
103
- else
104
- @runable.sort! { |x, y| x[:weight] <=> y[:weight] }
105
- @available.sort! { |x, y| x[:capacity] <=> y[:capacity] }
106
- @runlist = Array.new
107
- @svctab = Hash.new
108
- @runable.each do |event|
109
- @svctab[event[:name]] = Array.new
110
- end
111
- @exitrunable = 0
112
- while @runable != []
113
- break if @exitrunable == 1
114
- @available.each do |host|
115
- break if @runable.last.nil?
116
- @exitrunable = 1 if @svctab[@runable.last[:name]].include? host[:hostname]
117
- break if @svctab[@runable.last[:name]].include? host[:hostname]
118
- if host[:capacity] >= @runable.last[:weight]
119
- host[:capacity] = host[:capacity] - @runable.last[:weight]
120
- @svctab[@runable.last[:name]] << host[:hostname]
121
- @runlist << { :host => host, :service => @runable.pop }
122
- end
123
- end
124
- end
125
- return @runlist
126
- end
127
- end
128
-
129
- get '/' do
130
- "You must be lost. Read the instructions."
131
- end
132
-
133
- get '/servicebalance/:role/:environment' do
134
- runlist = balanceservices(params[:role], params[:environment])
135
- runlist.peach do |event|
136
- runservice(event[:host][:hostname], event[:service][:name], event[:service][:keys])
137
- end
138
- runlist.to_json
139
- end
140
-
141
- put '/service/:role/:environment' do
142
- data = JSON.parse request.body.read
143
- count = @redis.incr("service:#{params[:role]}:#{params[:environment]}:#{data['name']}:count")
144
- key = "service:#{params[:role]}:#{params[:environment]}:#{data['name']}:#{count}"
145
- @redis.hset(key, 'name', data['name'])
146
- @redis.hset(key, 'keys', data['keys'])
147
- @redis.hset(key, 'weight', data['weight'])
148
- @redis.hset(key, 'role', params[:role])
149
- @redis.hset(key, 'environment', params[:environment])
150
- @redis.hset(key, 'run', data['enabled'])
151
- {
152
- :role => params[:role],
153
- :environment => params[:environment],
154
- :service => data['name'],
155
- :count => count,
156
- }.to_json
157
- end
158
-
159
- delete '/service/:role/:environment/:service' do
160
- if @redis.get("service:#{params[:role]}:#{params[:environment]}:#{params[:service]}:count") == '0'
161
- count = 0
162
- else
163
- count = @redis.decr("service:#{params[:role]}:#{params[:environment]}:#{params[:service]}:count")
164
- @redis.del("service:#{params[:role]}:#{params[:environment]}:#{params[:service]}:#{count + 1}")
165
- end
166
- {
167
- :role => params[:role],
168
- :environment => params[:environment],
169
- :service => params[:service],
170
- :count => count,
171
- }.to_json
172
- end
173
-
174
- def getservices()
175
- services = Array.new
176
- @redis.keys('service:*').each do |service|
177
- unless service =~ /:count/
178
- line = @redis.hgetall(service)
179
- line['keys'] = eval(line['keys'])
180
- services << line
181
- end
182
- end
183
- return services
184
- end
185
-
186
- get '/services' do
187
- getservices().to_json
188
- end
189
-
190
- post '/regenhosts' do
191
- data = JSON.parse request.body.read
192
- AWS.config(:access_key_id => @redis.hget('config', 'aws_id'), :secret_access_key => @redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{data['zone']}.amazonaws.com")
193
- @ec2 = AWS::EC2.new
194
- @redis.keys("host:*").each do |host|
195
- out = @redis.hset(host, 'hostname', @ec2.instances[@redis.hget(host, 'instance')].dns_name)
196
- end
197
- "{\"regen\":\"running\"}"
198
- end
199
-
200
- post '/init' do
201
- data = JSON.parse request.body.read
202
- AWS.config(:access_key_id => @redis.hget('config', 'aws_id'), :secret_access_key => @redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{data['zone'].chop}.amazonaws.com")
203
- @ec2 = AWS::EC2.new
204
- # create shared secret to reference in /register
205
- @secret = (0...8).map{65.+(rand(26)).chr}.join
206
- data.merge!("secret" => @secret)
207
- sgid = gt_securitygroup(data['role'], data['environment'], data['zone'])
208
- if data['mirror']
209
- instance = @ec2.instances.create(
210
- :image_id => @redis.hget("amis", data['zone'].chop),
211
- :availability_zone => data['zone'],
212
- :instance_type => data['itype'],
213
- :key_name => "gaptool",
214
- :security_group_ids => sgid,
215
- :user_data => "#!/bin/bash\ncurl --silent -H 'X-GAPTOOL-USER: #{env['HTTP_X_GAPTOOL_USER']}' -H 'X-GAPTOOL-KEY: #{env['HTTP_X_GAPTOOL_KEY']}' #{@redis.hget('config', 'url')}/register -X PUT --data '#{data.to_json}' | bash",
216
- :block_device_mappings => {
217
- "/dev/sdf" => {
218
- :volume_size => data['mirror'].to_i,
219
- :delete_on_termination => false
220
- },
221
- "/dev/sdg" => {
222
- :volume_size => data['mirror'].to_i,
223
- :delete_on_termination => false
224
- }
225
- }
226
- )
227
- else
228
- instance = @ec2.instances.create(
229
- :image_id => @redis.hget("amis", data['zone'].chop),
230
- :availability_zone => data['zone'],
231
- :instance_type => data['itype'],
232
- :key_name => "gaptool",
233
- :security_group_ids => sgid,
234
- :user_data => "#!/bin/bash\ncurl --silent -H 'X-GAPTOOL-USER: #{env['HTTP_X_GAPTOOL_USER']}' -H 'X-GAPTOOL-KEY: #{env['HTTP_X_GAPTOOL_KEY']}' #{@redis.hget('config', 'url')}/register -X PUT --data '#{data.to_json}' | bash"
235
- )
236
- end
237
- # Add host tag
238
- instance.add_tag('Name', :value => "#{data['role']}-#{data['environment']}-#{instance.id}")
239
- # Create temporary redis entry for /register to pull the instance id
240
- @redis.set("instance:#{data['role']}:#{data['environment']}:#{@secret}", instance.id)
241
- "{\"instance\":\"#{instance.id}\"}"
242
- end
243
-
244
- post '/terminate' do
245
- data = JSON.parse request.body.read
246
- AWS.config(:access_key_id => @redis.hget('config', 'aws_id'), :secret_access_key => @redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{data['zone']}.amazonaws.com")
247
- @ec2 = AWS::EC2.new
248
- begin
249
- @instance = @ec2.instances[data['id']]
250
- res = @instance.terminate
251
- res = @redis.del(@redis.keys("*#{data['id']}"))
252
- out = {data['id'] => {'status'=> 'terminated'}}
253
- rescue
254
- error 404
255
- end
256
- out.to_json
257
- end
258
-
259
- put '/register' do
260
- data = JSON.parse request.body.read
261
- AWS.config(:access_key_id => @redis.hget('config', 'aws_id'), :secret_access_key => @redis.hget('config', 'aws_secret'), :ec2_endpoint => "ec2.#{data['zone'].chop}.amazonaws.com")
262
- @ec2 = AWS::EC2.new
263
- @instance = @ec2.instances[@redis.get("instance:#{data['role']}:#{data['environment']}:#{data['secret']}")]
264
- hostname = @instance.dns_name
265
- delete = @redis.del("instance:#{data['role']}:#{data['environment']}:#{data['secret']}")
266
- @apps = Array.new
267
- @redis.keys("app:*").each do |app|
268
- if @redis.hget(app, 'role') == data['role']
269
- @apps << app.gsub('app:', '')
270
- end
271
- end
272
- data.merge!("capacity" => @redis.hget('capacity', data['itype']))
273
- data.merge!("hostname" => hostname)
274
- data.merge!("apps" => @apps.to_json)
275
- data.merge!("instance" => @instance.id)
276
- hash2redis("host:#{data['role']}:#{data['environment']}:#{@instance.id}", data)
277
- @json = {
278
- 'hostname' => hostname,
279
- 'recipe' => 'init',
280
- 'number' => @instance.id,
281
- 'run_list' => ['recipe[init]'],
282
- 'role' => data['role'],
283
- 'environment' => data['environment'],
284
- 'chefrepo' => @redis.hget('config', 'chefrepo'),
285
- 'chefbranch' => @redis.hget('config', 'chefbranch'),
286
- 'identity' => @redis.hget('config','initkey'),
287
- 'appuser' => @redis.hget('config','appuser'),
288
- 'apps' => @apps
289
- }.to_json
290
- erb :init
291
- end
292
-
293
- get '/hosts' do
294
- out = Array.new
295
- @redis.keys("host:*").each do |host|
296
- out << @redis.hgetall(host)
297
- end
298
- out.to_json
299
- end
300
-
301
- get '/apps' do
302
- out = Hash.new
303
- @redis.keys("app:*").each do |app|
304
- out.merge!(app => @redis.hgetall(app))
305
- end
306
- out.to_json
307
- end
308
-
309
- get '/hosts/:role' do
310
- out = Array.new
311
- @redis.keys("host:#{params[:role]}:*").each do |host|
312
- out << @redis.hgetall(host)
313
- end
314
- out.to_json
315
- end
316
-
317
- get '/hosts/:role/:environment' do
318
- out = Array.new
319
- @redis.keys("host:#{params[:role]}:#{params[:environment]}*").each do |host|
320
- out << @redis.hgetall(host)
321
- end
322
- out.to_json
323
- end
324
-
325
- get '/host/:role/:environment/:instance' do
326
- @redis.hgetall("host:#{params[:role]}:#{params[:environment]}:#{params[:instance]}").to_json
327
- end
328
-
329
- get '/ssh/:role/:environment/:instance' do
330
- @host = @redis.hget("host:#{params[:role]}:#{params[:environment]}:#{params[:instance]}", 'hostname')
331
- @key = putkey(@host)
332
- {'hostname' => @host, 'key' => @key, 'pubkey' => @pubkey}.to_json
333
- end
334
-
335
-
336
- end