some 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/README.md +2 -0
  2. data/Rakefile +1 -0
  3. data/TODO +30 -2
  4. data/VERSION +1 -1
  5. data/bin/some +135 -5
  6. data/lib/some.rb +71 -6
  7. metadata +19 -8
data/README.md CHANGED
@@ -110,6 +110,8 @@ NIFTY Cloud 上で手軽にサーバーを立ち上げることができます
110
110
  ## TODO
111
111
 
112
112
  * image_id=17 (Ubuntu 10.04) で bootstrap できない (apt-get update; apt-get install -y curl する必要あり)
113
+ * かっこいいロゴを github ページにつける、あるいは github pages を使ってほーむぺーじを作る
114
+ * デフォルトの設定ファイルを同梱するようにする
113
115
 
114
116
  ## ライセンス
115
117
 
data/Rakefile CHANGED
@@ -13,6 +13,7 @@ Jeweler::Tasks.new do |s|
13
13
  s.add_dependency "nifty-cloud-sdk", "1.11.beta1"
14
14
  s.add_dependency "thor"
15
15
  s.add_dependency "net-ssh"
16
+ s.add_dependency "capistrano"
16
17
  end
17
18
 
18
19
  Jeweler::RubyforgeTasks.new
data/TODO CHANGED
@@ -1,5 +1,33 @@
1
- capistrano 連携
1
+ * some コマンドがどんどん便利になっていく
2
2
 
3
+ 自分で欲しい機能をガンガン盛り込んでいったら、便利すぎて手放せない感じになってきた。
3
4
 
5
+ ** some reset
4
6
 
5
- some sync コマンドで全サーバーに全サーバの ohai を撒くみたいなのいいかも
7
+ some コマンドで作成したすべてのインスタンス・FW・キーを削除する。
8
+
9
+ ** some cap
10
+
11
+ some コマンドで作成したインスタンスに対して capistrano を実行できる。
12
+
13
+ some list
14
+ some cap invoke ROLES="server1,server2" COMMAND="hostname"
15
+
16
+ ** some cache
17
+
18
+ インスタンス一覧を ~/.some/cache にキャッシュする。
19
+
20
+ ** some sync
21
+
22
+ some コマンドで作成したすべてのインスタンスから ohai 情報を取得し、
23
+ すべてのインスタンスの /var/chef/data_bags/hostname.json に配置する。
24
+ chef-solo-search と組み合わせると Chef サーバがなくてもレシピで search が使える。
25
+ munin とかで使うと便利。
26
+
27
+ ** some batch
28
+
29
+ some コマンドを一括実行できる。
30
+
31
+ ** some start/stop
32
+
33
+ ** some volumes/create_volume/attach_volume/detach_volume
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.5
1
+ 0.0.6
data/bin/some CHANGED
@@ -47,9 +47,14 @@ class CLI < Thor
47
47
  end
48
48
 
49
49
  desc "list", "list running instances"
50
+ option :format, :type => :string, :aliases => '-f', :desc => 'format (capfile)'
50
51
  def list
51
- some.list.each do |inst|
52
- printf "%-50s %-12s %s\n", inst[:hostname], inst[:instance_id], inst[:status]
52
+ if options[:format] == 'capfile'
53
+ puts some.capfile
54
+ else
55
+ some.list.each do |inst|
56
+ printf "%-15s %-15s %s\n", inst[:hostname], inst[:instance_id], inst[:status]
57
+ end
53
58
  end
54
59
  end
55
60
 
@@ -57,9 +62,8 @@ class CLI < Thor
57
62
  def terminate(id=nil)
58
63
  inst = some.find(id) || (some.running | some.pending).first || abort("No running or pending instances")
59
64
  if inst[:status] != 'stopped'
60
- task 'Wait to stop' do
61
- some.wait_to_stop(inst[:instance_id])
62
- end
65
+ task('Stop Instance') { some.stop(inst[:instance_id]); "done" }
66
+ task('Wait to stop') { some.wait_to_stop(inst[:instance_id]) }
63
67
  end
64
68
  some.terminate(inst[:instance_id])
65
69
  some.wait_to_terminate(inst[:instance_id])
@@ -103,6 +107,132 @@ class CLI < Thor
103
107
  end
104
108
  end
105
109
 
110
+ desc "cap", "call capistrano"
111
+ def cap(*args)
112
+ File.open(ENV['HOME'] + '/.some/Capfile', 'w') do |f|
113
+ f.write some.capfile
114
+ end
115
+ system "cap -f ~/.some/Capfile #{args.join(' ')}"
116
+ end
117
+
118
+ desc "cache", "cache instance list"
119
+ def cache(*args)
120
+ some.cache_list
121
+ end
122
+
123
+ desc "sync", "sync nodes"
124
+ def sync
125
+ some.sync
126
+ end
127
+
128
+ desc "batch 'command1, command2'", "batch execute"
129
+ def batch(arg)
130
+ commands = arg.split(";")
131
+ commands.each do |x|
132
+ system("some #{x}")
133
+ end
134
+ end
135
+
136
+ desc "volumes", "list all volumes"
137
+ def volumes
138
+ some.volumes.each do |v|
139
+ printf "%-10s %4sGB %10s %15s %15s\n", v[:volume_id], v[:size], v[:status], v[:instance], v[:device]
140
+ end
141
+ end
142
+ desc "start [<instance_id or hostname>]", "start specified instance or first available"
143
+ option :wait, :aliases => '-w', :desc => 'wait to start'
144
+ def start(id=nil)
145
+ inst = some.find(id) || (some.stopped).first || abort("No stopped instances")
146
+ task('Start instance') { some.start(inst[:instance_id]); "done" }
147
+ task('Wait to start') { some.wait_for_hostname(inst[:instance_id]) } if options[:wait]
148
+ end
149
+
150
+ desc "stop [<instance_id or hostname>]", "stop specified instance or first available"
151
+ option :wait, :aliases => '-w', :desc => 'wait to stop'
152
+ def stop(id=nil)
153
+ inst = some.find(id) || (some.running | some.pending).first || abort("No running or pending instances")
154
+ task('Stop Instance') { some.stop(inst[:instance_id]); "done" }
155
+ task('Wait to stop') { some.wait_to_stop(inst[:instance_id]) } if options[:wait]
156
+ end
157
+
158
+
159
+ desc "create_volume [<instance_id or hostname>]", "create a volume"
160
+ option :name, :type => :string, :aliases => '-n', :desc => 'volume name'
161
+ def create_volume(id=nil)
162
+ inst = some.find(id) || (some.running | some.pending).first || abort("No running or pending instances")
163
+
164
+ orig_status = inst[:status]
165
+ if inst[:status] == 'running'
166
+ task('Stop Instance') { some.stop(inst[:instance_id]); "done" }
167
+ task("Wait to stop") { some.wait_to_stop(inst[:instance_id]) }
168
+ end
169
+
170
+ task("Create 100GB volume") { some.create_volume(options[:name], inst[:instance_id]) }
171
+
172
+ some.reload
173
+ inst = some.find(inst[:instance_id])
174
+ if inst[:status] == 'stopped' && orig_status == 'running'
175
+ task('Start instance') { some.start(inst[:instance_id]); "done" }
176
+ task("Wait to start") { some.wait_for_hostname(inst[:instance_id]) }
177
+ end
178
+ end
179
+
180
+ desc "destroy_volume [<volume_id>]", "destroy a volume"
181
+ def destroy_volume(volume=nil)
182
+ vol_id = (some.find_volume(volume) || some.nondestroyed_volumes.first || abort("No volumes"))[:volume_id]
183
+ task("Destroy volume") { some.destroy_volume(vol_id) }
184
+ end
185
+
186
+ desc "attach [<volume_id>] [<instance_id or hostname>] [<device>]", "attach volume to running instance"
187
+ def attach(volume=nil, inst_id=nil, device=nil)
188
+ vol_id = (some.find_volume(volume) || some.available_volumes.first || abort("No available volumes"))[:volume_id]
189
+ inst = (some.find(inst_id) || some.running.first || abort("No running instances"))
190
+
191
+ orig_status = inst[:status]
192
+ if inst[:status] == 'running'
193
+ task('Stop Instance') { some.stop(inst[:instance_id]); "done" }
194
+ task("Wait to stop") { some.wait_to_stop(inst[:instance_id]) }
195
+ end
196
+
197
+ device ||= '/dev/sdc1'
198
+ task("Attach #{vol_id} to #{inst_id} as #{device}") do
199
+ some.attach(vol_id, inst[:instance_id], device)
200
+ end
201
+
202
+ some.reload
203
+ inst = some.find(inst[:instance_id])
204
+ if inst[:status] == 'stopped' && orig_status == 'running'
205
+ task("Wait to start") do
206
+ task('Start instance') { some.start(inst[:instance_id]); "done" }
207
+ task("Wait to start") { some.wait_for_hostname(inst[:instance_id]) }
208
+ end
209
+ end
210
+ end
211
+
212
+ desc "detach [<volume_id>]", "detach volume from instance"
213
+ def detach(volume=nil)
214
+ volume = (some.find_volume(volume) || some.attached_volumes.first || abort("No attached volumes"))
215
+ vol_id = volume[:volume_id]
216
+ inst = some.find(volume[:instance_id])
217
+
218
+ orig_status = inst[:status]
219
+ if inst[:status] == 'running'
220
+ task('Stop Instance') { some.stop(inst[:instance_id]); "done" }
221
+ task("Wait to stop") { some.wait_to_stop(inst[:instance_id]) }
222
+ end
223
+
224
+ task("Detach #{vol_id}") { some.detach(vol_id) }
225
+
226
+ some.reload
227
+ inst = some.find(inst[:instance_id])
228
+ if inst[:status] == 'stopped' && orig_status == 'running'
229
+ task("Wait to start") do
230
+ some.start(inst[:instance_id])
231
+ some.wait_for_hostname(inst[:instance_id])
232
+ end
233
+ end
234
+ end
235
+
106
236
  no_tasks do
107
237
  def some
108
238
  @some ||= Some.new
data/lib/some.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'net/scp'
1
2
  require 'net/ssh'
2
3
  require 'NIFTY'
3
4
  require 'yaml'
@@ -33,6 +34,19 @@ class Some
33
34
  @list ||= fetch_list
34
35
  end
35
36
 
37
+ def reload
38
+ @list = fetch_list
39
+ end
40
+
41
+ def capfile
42
+ [
43
+ %Q(set :user, "#{config["user"]}"),
44
+ %Q(ssh_options[:keys] = "#{keypair_file}"),
45
+ %Q(ssh_options[:passphrase] = "#{config["password"]}"),
46
+ list.map {|inst| %Q(server "#{inst[:public_ip]}", "#{inst[:instance_id]}")}
47
+ ].flatten.join("\n")
48
+ end
49
+
36
50
  def firewall_list
37
51
  security_group = find_security_group
38
52
  return [] if security_group.nil? || security_group.ipPermissions.nil?
@@ -43,7 +57,7 @@ class Some
43
57
  :from_port => row["fromPort"],
44
58
  :in_out => row["inOut"],
45
59
  :group => row["groupName"],
46
- :cidr => (row["ipRanges"]["item"].first["cidrIp"] rescue nil)
60
+ :cidr => (row["ipRanges"]["item"].first["cidrIp"] rescue nil)
47
61
  }
48
62
  end
49
63
  end
@@ -102,10 +116,12 @@ class Some
102
116
  "done"
103
117
  end
104
118
 
105
- def create_volume(size)
119
+ def create_volume(vol_id, inst_id)
106
120
  result = api.create_volume(
121
+ :volume_id => vol_id,
122
+ :instance_id => inst_id,
107
123
  :availability_zone => config['availability_zone'],
108
- :size => size.to_s
124
+ :size => 1
109
125
  )
110
126
  result["volumeId"]
111
127
  end
@@ -116,6 +132,7 @@ class Some
116
132
  end
117
133
 
118
134
  def fetch_list
135
+ return YAML.load File.read(ENV["HOME"] + "/.some/cache") if File.exists?(ENV["HOME"] + "/.some/cache")
119
136
  result = api.describe_instances
120
137
  return [] unless result.reservationSet
121
138
 
@@ -135,6 +152,43 @@ class Some
135
152
  instances
136
153
  end
137
154
 
155
+ def cache_list
156
+ File.open(ENV["HOME"] + "/.some/cache", "w") do |f|
157
+ f.write(fetch_list.to_yaml)
158
+ end
159
+ end
160
+
161
+ def sync
162
+ # TODO: raise error if ohai is not installed on the server
163
+ ohai = Hash.new{|h,k| h[k] = ""}
164
+ list.each do |inst|
165
+ puts "-----> getting ohai.json from #{inst[:instance_id]}"
166
+ Net::SSH.start(inst[:hostname], config['user'], :keys => [keypair_file], :passphrase => config['password']) do |ssh|
167
+ File.open("#{ENV['HOME']}/.some/ssh.log", 'w') do |f|
168
+ ssh.exec!("mkdir -p /var/chef/data_bags/node && ohai --log_level fatal") do |ch, stream, data|
169
+ ohai[inst[:instance_id]] += data if stream == :stdout
170
+ end
171
+ end
172
+ end
173
+ end
174
+ list.each do |inst|
175
+ puts "-----> pushing ohai.json list to #{inst[:instance_id]}"
176
+ Net::SCP.start(inst[:hostname], config['user'], :keys => [keypair_file], :passphrase => config['password']) do |scp|
177
+ list.each do |inst|
178
+ json = {
179
+ "id" => inst[:instance_id],
180
+ "name" => inst[:instance_id],
181
+ "chef_environment" => "_default",
182
+ "json_class" => "Chef::Node",
183
+ "run_list" => [],
184
+ "automatic" => JSON.parse(ohai[inst[:instance_id]])
185
+ }
186
+ scp.upload! StringIO.new(JSON.pretty_generate(json)), "/var/chef/data_bags/node/#{inst[:instance_id]}.json"
187
+ end
188
+ end
189
+ end
190
+ end
191
+
138
192
  def find(id_or_hostname)
139
193
  return unless id_or_hostname
140
194
  id_or_hostname = id_or_hostname.strip.downcase
@@ -161,6 +215,10 @@ class Some
161
215
  list_by_status('pending')
162
216
  end
163
217
 
218
+ def stopped
219
+ list_by_status('stopped')
220
+ end
221
+
164
222
  def list_by_status(status)
165
223
  list.select { |i| i[:status] == status }
166
224
  end
@@ -185,7 +243,6 @@ class Some
185
243
 
186
244
  def wait_to_stop(instance_id)
187
245
  raise ArgumentError unless instance_id
188
- api.stop_instances(:instance_id => [ instance_id ])
189
246
  loop do
190
247
  if inst = instance_info(instance_id)
191
248
  if inst[:status] == 'stopped'
@@ -229,8 +286,8 @@ class Some
229
286
  end
230
287
 
231
288
  def setup_role(hostname, role)
232
- dna = JSON.parse(config['role'][role])
233
- dna.update("some" => {"instances" => list})
289
+ dna = JSON.parse(config['role'][role])
290
+ dna.update("some" => {"instances" => list})
234
291
  commands = [
235
292
  "echo \'#{JSON.pretty_generate(dna)}\' > /etc/chef/dna.json",
236
293
  "chef-solo -r #{config['cookbooks_url']}"
@@ -250,6 +307,14 @@ class Some
250
307
  end
251
308
  end
252
309
 
310
+ def start(instance_id)
311
+ api.start_instances(:instance_id => [ instance_id ])
312
+ end
313
+
314
+ def stop(instance_id)
315
+ api.stop_instances(:instance_id => [ instance_id ])
316
+ end
317
+
253
318
  def terminate(instance_id)
254
319
  api.terminate_instances(:instance_id => [ instance_id ])
255
320
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: some
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-09 00:00:00.000000000Z
12
+ date: 2013-04-14 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nifty-cloud-sdk
16
- requirement: &75940320 !ruby/object:Gem::Requirement
16
+ requirement: &199117720 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - =
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.11.beta1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *75940320
24
+ version_requirements: *199117720
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: thor
27
- requirement: &75939740 !ruby/object:Gem::Requirement
27
+ requirement: &199117200 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *75939740
35
+ version_requirements: *199117200
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: net-ssh
38
- requirement: &75939160 !ruby/object:Gem::Requirement
38
+ requirement: &199116680 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,18 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *75939160
46
+ version_requirements: *199116680
47
+ - !ruby/object:Gem::Dependency
48
+ name: capistrano
49
+ requirement: &199116180 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *199116180
47
58
  description: sumo clone for NIFTY Cloud
48
59
  email: tidnlyam@gmail.com
49
60
  executables: