some 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/README.md +112 -0
  2. data/Rakefile +1 -1
  3. data/TODO +5 -2
  4. data/VERSION +1 -1
  5. data/bin/some +20 -37
  6. data/lib/some.rb +80 -56
  7. metadata +10 -10
  8. data/README.rdoc +0 -123
@@ -0,0 +1,112 @@
1
+ # some - sumo clone for NIFTY Cloud
2
+
3
+ ## 概要
4
+
5
+ [adamwiggins/sumo](http://github.com/adamwiggins/sumo) の NIFTY Cloud バージョンです。
6
+ NIFTY Cloud 上で手軽にサーバーを立ち上げることができます。
7
+
8
+ $ some launch
9
+ ---> Launch instance... 4acef29d (7.9s)
10
+ ---> Acquire hostname... XXX.XXX.XXX.XXX (79.2s)
11
+ ---> Wait for ssh... done (0.0s)
12
+
13
+ Logging you in via ssh. Type 'exit' or Ctrl-D to return to your local system.
14
+ ------------------------------------------------------------------------------
15
+ Enter passphrase for key '/home/tily/.some/keypair.pem':
16
+ [root@localhost ~]#
17
+
18
+ 要らなくなったらすぐに削除できます。
19
+
20
+ $ some terminate XXX.XXX.XXX.XXX
21
+ ---> Wait to stop... done (16.2s)
22
+ XXX.XXX.XXX.XXX scheduled for termination
23
+
24
+ 一覧を取得して SSH ログインしたりも簡単にできます。
25
+
26
+ $ some list
27
+ XXX.XXX.XXX.XXX 21b61298 running
28
+ YYY.YYY.YYY.YYY 923d7772 running
29
+ ZZZ.ZZZ.ZZZ.ZZZ dec83cd3 running
30
+ $ some ssh 21b61298
31
+ Enter passphrase for key '/home/tily/.some/keypair.pem':
32
+ Last login: Fri Apr 5 16:24:02 2013 from AAA.AAA.AAA.AAA
33
+ [root@localhost ~]#
34
+
35
+ ## インストール・設定
36
+
37
+ 下記コマンドでインストールできます。
38
+
39
+ gem install some
40
+
41
+ インストールが終わったら ~/.sumo/config.yml に設定ファイルを作成しましょう。
42
+ 最小限下記を書けば使えます。
43
+
44
+ ---
45
+ access_key: (ここにアクセスキーを書く)
46
+ secret_key: (ここにシークレットキーを書く)
47
+
48
+ 設定できる項目をフルで書くとこんな感じになります。
49
+
50
+ ---
51
+ access_key: (デフォルト値なし)
52
+ secret_key: (デフォルト値なし)
53
+ user: root (デフォルト値)
54
+ password: password (デフォルト値)
55
+ instance_size: mini (デフォルト値)
56
+ ami: 26 (デフォルト値)
57
+ availability_zone: west-11 (デフォルト値)
58
+ cookbooks_url: (デフォルト値なし、次節参照)
59
+ role: (デフォルト値なし、次節参照)
60
+
61
+ ## Chef でサービスをインストールする
62
+
63
+ 少し頑張れば vagrant みたいなこともできます。
64
+ まずはグローバルからアクセスできる場所に cookbooks.tgz を置きましょう。
65
+ 例:http://some.ncss.nifty.com/cookbooks.tgz
66
+
67
+ 次に設定ファイル (~/.sumo/config.yml) にロールを定義しておきます。
68
+
69
+ cookbooks_url: http://some.ncss.nifty.com/cookbooks.tgz
70
+ role:
71
+ mysql: |
72
+ {
73
+ "run_list": [
74
+ "recipe[mysql::server]"
75
+ ],
76
+ "mysql": {
77
+ "server_root_password": "mysql",
78
+ "server_debian_password": "mysql",
79
+ "server_repl_password": "mysql"
80
+ }
81
+ }
82
+
83
+ ここまでやればコマンド一発でサーバー作成・Chef インストール・Chef 実行まで行えます。
84
+
85
+ $ some launch mysql
86
+ ---> Launch instance... d7f764b7 (7.6s)
87
+ ---> Acquire hostname... 175.184.23.139 (89.4s)
88
+ ---> Wait for ssh... done (0.0s)
89
+ ---> Bootstrap chef... done (42.8s)
90
+ ---> Setup mysql... done (195.0s)
91
+
92
+ なお、上記を個別に実施することも可能です。
93
+
94
+ $ some launch
95
+ ---> Launch instance... 923d7772 (8.0s)
96
+ ---> Acquire hostname... XXX.XXX.XXX.XXX (90.5s)
97
+ ---> Wait for ssh... done (0.0s)
98
+
99
+ $ some bootstrap 923d7772
100
+ ---> Bootstrap chef... done (46.0s)
101
+
102
+ $ some role mysql 923d7772
103
+ ---> Setup mysql... done (184.6s)
104
+
105
+ ## 詳細
106
+
107
+ * サーバー作成の際に something という名前の SSH キーと FW を作成します
108
+ * SSH キーは ~/.some/keypair.pem に保存されます
109
+
110
+ ## ライセンス
111
+
112
+ [sumo](http://github.com/adamwiggins/sumo) と同じく MIT ライセンスで公開します。
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ Jeweler::Tasks.new do |s|
10
10
  s.rubyforge_project = "some"
11
11
  s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
12
12
  s.executables = %w(some)
13
- s.add_dependency "nifty-cloud-sdk"
13
+ s.add_dependency "nifty-cloud-sdk", "1.11.beta1"
14
14
  s.add_dependency "thor"
15
15
  end
16
16
 
data/TODO CHANGED
@@ -1,2 +1,5 @@
1
- * インデント直す
2
- * ポート開ける用の cookbooks 作る
1
+ *
2
+ ----------------------------------------
3
+ * motd
4
+ * hostname
5
+ * nifty_cloud
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/bin/some CHANGED
@@ -14,21 +14,7 @@ class CLI < Thor
14
14
 
15
15
  if role
16
16
  task("Bootstrap chef") { some.bootstrap_chef(host) }
17
- role.split(',').each do |role|
18
- task("Setup #{role}") { some.setup_role(host, role) }
19
- end
20
-
21
- resources = some.resources(host)
22
- unless resources.empty?
23
- task("Open firewall") do
24
- ports = resources.map { |r| r.match(/:(\d+)\//)[1] }
25
- ports.each { |port| some.open_firewall(port) }
26
- "ports " + ports.join(", ")
27
- end
28
- end
29
-
30
- puts
31
- display_resources(host)
17
+ task("Setup #{role}") { some.setup_role(host, role) }
32
18
  else
33
19
  puts "\nLogging you in via ssh. Type 'exit' or Ctrl-D to return to your local system."
34
20
  puts '-' * 78
@@ -43,13 +29,6 @@ class CLI < Thor
43
29
  connect_ssh hostname
44
30
  end
45
31
 
46
- desc "resources [<instance_id or hostname>]", "show resources exported by an instance"
47
- def resources(id=nil)
48
- inst = some.find(id) || some.running.first || abort("No running instances")
49
- hostname = inst[:hostname] || wait_for_hostname(inst[:instance_id])
50
- display_resources(inst[:hostname])
51
- end
52
-
53
32
  desc "bootstrap", "bootstrap chef and cookbooks"
54
33
  def bootstrap(id=nil)
55
34
  inst = some.find(id) || some.running.first || abort("No running instances")
@@ -76,15 +55,29 @@ class CLI < Thor
76
55
  desc "terminate [<instance_id or hostname>]", "terminate specified instance or first available"
77
56
  def terminate(id=nil)
78
57
  inst = some.find(id) || (some.running | some.pending).first || abort("No running or pending instances")
79
- if inst[:status] != 'stopped'
80
- task 'Wait to stop' do
81
- some.wait_to_stop(inst[:instance_id])
82
- end
83
- end
58
+ if inst[:status] != 'stopped'
59
+ task 'Wait to stop' do
60
+ some.wait_to_stop(inst[:instance_id])
61
+ end
62
+ end
84
63
  some.terminate(inst[:instance_id])
85
64
  puts "#{inst[:hostname] || inst[:instance_id]} scheduled for termination"
86
65
  end
87
66
 
67
+ desc "openfw <port>", "open firewall"
68
+ def openfw(port)
69
+ raise ArgumentError unless port
70
+ some.open_firewall(port)
71
+ puts "port #{port} scheduled for open"
72
+ end
73
+
74
+ desc "closefw <port>", "close firewall"
75
+ def closefw(port)
76
+ raise ArgumentError unless port
77
+ some.close_firewall(port)
78
+ puts "port #{port} scheduled for open"
79
+ end
80
+
88
81
  no_tasks do
89
82
  def some
90
83
  @some ||= Some.new
@@ -111,16 +104,6 @@ class CLI < Thor
111
104
  puts "\nType 'some terminate' if you're done with this instance."
112
105
  end
113
106
  end
114
-
115
- def display_resources(host)
116
- resources = some.resources(host)
117
- unless resources.empty?
118
- puts "Your instance is exporting the following resources:"
119
- resources.each do |resource|
120
- puts " #{resource}"
121
- end
122
- end
123
- end
124
107
  end
125
108
  end
126
109
 
@@ -19,7 +19,8 @@ class Some
19
19
  :key_name => 'something',
20
20
  :security_group => 'something',
21
21
  :availability_zone => config['availability_zone'],
22
- :disable_api_termination => false
22
+ :disable_api_termination => false,
23
+ :accounting_type => 2
23
24
  )
24
25
  result.instancesSet.item[0].instanceId
25
26
  end
@@ -149,13 +150,11 @@ class Some
149
150
 
150
151
  def wait_to_stop(instance_id)
151
152
  raise ArgumentError unless instance_id
152
- api.stop_instances(:instance_id => [ instance_id ])
153
- puts "waiting for #{instance_id} to stop "
153
+ api.stop_instances(:instance_id => [ instance_id ])
154
154
  loop do
155
- print '.'
156
155
  if inst = instance_info(instance_id)
157
156
  if inst[:status] == 'stopped'
158
- break
157
+ break
159
158
  end
160
159
  end
161
160
  sleep 5
@@ -177,45 +176,30 @@ class Some
177
176
 
178
177
  def bootstrap_chef(hostname)
179
178
  commands = [
180
- "curl -L https://www.opscode.com/chef/install.sh | bash",
181
- "mkdir -p /var/chef/cookbooks /etc/chef",
182
- "echo json_attribs \\'/etc/chef/dna.json\\' > /etc/chef/solo.rb"
179
+ "curl -L https://www.opscode.com/chef/install.sh | bash",
180
+ "mkdir -p /var/chef/cookbooks /etc/chef",
181
+ "echo json_attribs \\'/etc/chef/dna.json\\' > /etc/chef/solo.rb"
183
182
  ]
184
183
  ssh(hostname, commands)
185
184
  end
186
185
 
187
186
  def setup_role(hostname, role)
188
187
  commands = [
189
- "echo \'#{config['role'][role]}\' > /etc/chef/dna.json",
188
+ "echo \'#{config['role'][role]}\' > /etc/chef/dna.json",
190
189
  "chef-solo -r #{config['cookbooks_url']}"
191
190
  ]
192
191
  ssh(hostname, commands)
193
192
  end
194
193
 
195
194
  def ssh(hostname, cmds)
196
- Net::SSH.start(hostname, config['user'], :keys => [keypair_file], :passphrase => config['password']) do |ssh|
197
- File.open("#{ENV['HOME']}/.some/ssh.log", 'w') do |f|
198
- f.write(ssh.exec!(cmds.join(' && ')))
199
- end
200
- end
201
- # TODO: abort "failed\nCheck ~/.some/ssh.log for the output"
202
- end
203
-
204
- def resources(hostname)
205
- @resources ||= {}
206
- @resources[hostname] ||= fetch_resources(hostname)
207
- end
208
-
209
- def fetch_resources(hostname)
210
- cmd = "ssh -i #{keypair_file} #{config['user']}@#{hostname} 'cat /root/resources' 2>&1"
211
- out = IO.popen(cmd, 'r') { |pipe| pipe.read }
212
- abort "failed to read resources, output:\n#{out}" unless $?.success?
213
- parse_resources(out, hostname)
214
- end
215
-
216
- def parse_resources(raw, hostname)
217
- raw.split("\n").map do |line|
218
- line.gsub(/localhost/, hostname)
195
+ STDOUT.puts
196
+ Net::SSH.start(hostname, config['user'], :keys => [keypair_file], :passphrase => config['password']) do |ssh|
197
+ File.open("#{ENV['HOME']}/.some/ssh.log", 'w') do |f|
198
+ ssh.exec!(cmds.join(' && ')) do |ch, stream, data|
199
+ f.write(data)
200
+ STDOUT.print data
201
+ end
202
+ end
219
203
  end
220
204
  end
221
205
 
@@ -232,7 +216,7 @@ class Some
232
216
  'user' => 'root',
233
217
  'ami' => 26,
234
218
  'availability_zone' => 'east-12',
235
- 'password' => 'password'
219
+ 'password' => 'password'
236
220
  }
237
221
  end
238
222
 
@@ -259,37 +243,77 @@ class Some
259
243
  def create_security_group
260
244
  api.create_security_group(:group_name => 'something', :group_description => 'Something')
261
245
  rescue NIFTY::ResponseError => e
262
- if e.message != "The groupName 'something' already exists."
263
- raise e
264
- end
246
+ if e.message != "The groupName 'something' already exists."
247
+ raise e
248
+ end
265
249
  end
266
250
 
267
251
  def open_firewall(port)
268
- api.authorize_security_group_ingress(
252
+ target = {
269
253
  :group_name => 'something',
270
- :ip_permissions => {
271
- :ip_protocol => 'tcp',
272
- :from_port => port,
273
- :to_port => port,
274
- :cidr_ip => '0.0.0.0/0'
275
- }
276
- )
277
- rescue NIFTY::ResponseError => e
278
- raise e
254
+ :ip_permissions => {
255
+ :ip_protocol => 'TCP',
256
+ :in_out => 'IN',
257
+ :from_port => port,
258
+ :to_port => port,
259
+ :cidr_ip => '0.0.0.0/0'
260
+ }
261
+ }
262
+ return if find_security_group_ingress(target)
263
+ api.authorize_security_group_ingress(target)
264
+ end
265
+
266
+ def close_firewall(port)
267
+ target = {
268
+ :group_name => 'something',
269
+ :ip_permissions => {
270
+ :ip_protocol => 'TCP',
271
+ :in_out => 'IN',
272
+ :from_port => port,
273
+ :to_port => port,
274
+ :cidr_ip => '0.0.0.0/0'
275
+ }
276
+ }
277
+ return unless find_security_group_ingress(target)
278
+ api.revoke_security_group_ingress(target)
279
+ end
280
+
281
+ def find_security_group_ingress(target)
282
+ res = api.describe_security_groups
283
+ security_group = res.securityGroupInfo.item.find {|security_group|
284
+ security_group.groupName == target[:group_name]
285
+ }
286
+ return nil if !security_group || !security_group.ipPermissions
287
+ security_group.ipPermissions.item.find {|ip_permission|
288
+ flag = (
289
+ ip_permission.ipProtocol == target[:ip_permissions][:ip_protocol] &&
290
+ ip_permission.inOut == target[:ip_permissions][:in_out]
291
+ )
292
+ # also compare from_port when ip_protocol is not ICMP but TCP or UDP
293
+ if target[:ip_permissions][:ip_protocol] != 'ICMP'
294
+ flag = flag && (ip_permission.fromPort == target[:ip_permissions][:from_port].to_s)
295
+ end
296
+ if ip_permission.groups
297
+ flag = flag && (ip_permission.groups.item.first.groupName == target[:ip_permissions][:group_name])
298
+ else
299
+ flag = flag && (ip_permission.ipRanges.item.first.cidrIp == target[:ip_permissions][:cidr_ip])
300
+ end
301
+ flag
302
+ }
279
303
  end
280
304
 
281
305
  def api
282
- @api ||= NIFTY::Cloud::Base.new(
283
- :access_key => config['access_key'],
284
- :secret_key => config['secret_key'],
285
- :server => server,
286
- :path => '/api'
287
- )
306
+ @api ||= NIFTY::Cloud::Base.new(
307
+ :access_key => config['access_key'],
308
+ :secret_key => config['secret_key'],
309
+ :server => server,
310
+ :path => '/api'
311
+ )
288
312
  end
289
313
 
290
314
  def server
291
- zone = config['availability_zone']
292
- host = zone.slice(0, zone.length - 1)
293
- "#{host}.cp.cloud.nifty.com"
294
- end
315
+ zone = config['availability_zone']
316
+ host = zone.slice(0, zone.length - 1)
317
+ "#{host}.cp.cloud.nifty.com"
318
+ end
295
319
  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.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-05 00:00:00.000000000Z
12
+ date: 2013-04-06 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nifty-cloud-sdk
16
- requirement: &259249220 !ruby/object:Gem::Requirement
16
+ requirement: &75338600 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - =
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 1.11.beta1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *259249220
24
+ version_requirements: *75338600
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: thor
27
- requirement: &259248640 !ruby/object:Gem::Requirement
27
+ requirement: &75337980 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,17 +32,17 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *259248640
35
+ version_requirements: *75337980
36
36
  description: sumo clone for NIFTY Cloud
37
37
  email: tidnlyam@gmail.com
38
38
  executables:
39
39
  - some
40
40
  extensions: []
41
41
  extra_rdoc_files:
42
- - README.rdoc
42
+ - README.md
43
43
  - TODO
44
44
  files:
45
- - README.rdoc
45
+ - README.md
46
46
  - Rakefile
47
47
  - TODO
48
48
  - VERSION
@@ -1,123 +0,0 @@
1
- = Tired of wrestling with server provisioning? Sumo!
2
-
3
- Want to fire up a one-off EC2 instance, pronto? ec2-run-instances got you down? Try Sumo.
4
-
5
- $ sumo launch
6
- ---> Launching instance... i-4f809c26 (1.5s)
7
- ---> Acquiring hostname... ec2-67-202-17-178.compute-1.amazonaws.com (26.7s)
8
-
9
- Logging you in via ssh. Type 'exit' or Ctrl-D to return to your local system.
10
- ------------------------------------------------------------------------------
11
- Linux domU-12-31-39-04-31-37 2.6.21.7-2.fc8xen #1 SMP Fri Feb 15 12:39:36 EST 2008 i686
12
- ...
13
- root@domU-12-31-39-04-31-37:~#
14
-
15
- Later...
16
-
17
- $ sumo terminate
18
- ec2-67-202-17-178.compute-1.amazonaws.com scheduled for termination
19
-
20
- You can manage multiple instances via "sumo list" and specifying hostname or instance id as arguments to the ssh or terminate commands.
21
-
22
- == Service installation with Chef
23
-
24
- The launch command takes an argument, which is a server role (from roles/#{role}.json inside your cookbooks repo):
25
-
26
- $ sumo launch redis
27
- ---> Launch instance... i-b96c73d0 (1.3s)
28
- ---> Acquire hostname... ec2-75-101-191-220.compute-1.amazonaws.com (36.1s)
29
- ---> Wait for ssh... done (9.0s)
30
- ---> Bootstrap chef... done (61.3s)
31
- ---> Setup redis... done (11.9s)
32
- ---> Opening firewall... ports 6379 (5.2s)
33
-
34
- Your instance is exporting the following resources:
35
- Redis: redis://:8452cdd98f428c972f08@ec2-75-101-191-220.compute-1.amazonaws.com:6379/0
36
-
37
- The instance can assume multiple roles if you like:
38
-
39
- $ sumo launch redis,solr,couchdb
40
-
41
- == Setup
42
-
43
- Dependencies:
44
-
45
- $ sudo gem install amazon-ec2 thor
46
-
47
- Then create ~/.sumo/config.yml containing:
48
-
49
- ---
50
- access_id: <your amazon access key id>
51
- access_secret: <your amazon secret access key>
52
-
53
- Optional config you can include any of the following in your config.yml:
54
-
55
- user: root
56
- ami: ami-ed46a784
57
- availability_zone: us-east-1b
58
- cookbooks_url: git://github.com/adamwiggins/chef-cookbooks.git
59
-
60
- You'll need Bacon and Mocha if you want to run the specs, and Jewler if you want to create gems.
61
-
62
- == Managing volumes
63
-
64
- Create and attach a volume to your running instance:
65
-
66
- $ sumo create_volume
67
- ---> Create 5MB volume... vol-8a9c6ae3 (1.1s)
68
- $ sumo volumes
69
- vol-8a9c6ae3 5MB available
70
- $ sumo attach
71
- ---> Attach vol-8a9c6ae3 to i-bc32cbd4 as /dev/sdc1... done (0.6s)
72
-
73
- Log in to format and mount the volume:
74
-
75
- $ sumo ssh
76
- root@ip-10-251-122-175:~# mkfs.ext3 /dev/sdc1
77
- mke2fs 1.41.4 (27-Jan-2009)
78
- Filesystem label=
79
- OS type: Linux
80
- Block size=4096 (log=2)
81
- ...
82
- $ mkdir /myvol
83
- $ mount /dev/sdc1 /myvol
84
- $ echo "I'm going to persist to a volume" > /myvol/hello.txt
85
-
86
- To detach from a running instance (perhaps so you can attach elsewhere):
87
-
88
- $ sumo detatch
89
- ---> Detach vol-8a9c6ae3... done (0.6s)
90
-
91
- Destroy it if you no longer want the data stored on it:
92
-
93
- $ sumo destroy_volume
94
- ---> Destroy volume... done (0.8s)
95
-
96
- == Some details you might want to know
97
-
98
- Sumo creates its own keypair named sumo, which is stored in ~/.ssh/keypair.pem. Amazon doesn't let you upload your own ssh public key, which is lame, so this is the best option for making the launch-and-connect process a single step.
99
-
100
- It will also create an Amazon security group called sumo, so that it can lower the firewall for services you configure via cookbook roles.
101
-
102
- If you run any production machines from your EC2 account, I recommend setting up a separate account for use with Sumo. It does not prompt for confirmation when terminating an instance or differentiate between instances started by it vs. instances started by other tools.
103
-
104
- == Anti-features
105
-
106
- Sumo is not a cloud management tool, a monitor tool, or anything more than a way to get an instance up right quick. If you're looking for a way to manage a cluster of production instances, try one of these fine tools.
107
-
108
- * Pool Party
109
- * RightScale
110
- * Engine Yard Cloud
111
- * Cloudkick
112
-
113
- == Meta
114
-
115
- Created by Adam Wiggins
116
-
117
- Patches contributed by Orion Henry, Blake Mizerany, Jesse Newland, Gert Goet,
118
- and Tim Lossen
119
-
120
- Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
121
-
122
- http://github.com/adamwiggins/sumo
123
-