gaptool-server 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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