some 0.0.1 → 0.0.2

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 (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
-