some 0.0.5 → 0.0.6

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.
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: