some 0.0.0

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.rdoc +123 -0
  2. data/Rakefile +25 -0
  3. data/VERSION +1 -0
  4. data/bin/some +174 -0
  5. data/lib/some.rb +297 -0
  6. data/spec/base.rb +21 -0
  7. data/spec/some_spec.rb +31 -0
  8. metadata +75 -0
@@ -0,0 +1,123 @@
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
+
@@ -0,0 +1,25 @@
1
+ require 'jeweler'
2
+
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "some"
5
+ s.description = "sumo clone for NIFTY Cloud"
6
+ s.summary = s.description
7
+ s.author = "tily"
8
+ s.email = "tidnlyam@gmail.com"
9
+ s.homepage = "http://github.com/tily/some"
10
+ s.rubyforge_project = "some"
11
+ s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
12
+ s.executables = %w(some)
13
+ s.add_dependency "nifty-cloud-sdk"
14
+ s.add_dependency "thor"
15
+ end
16
+
17
+ Jeweler::RubyforgeTasks.new
18
+
19
+ desc 'Run specs'
20
+ task :spec do
21
+ sh 'bacon -s spec/*_spec.rb'
22
+ end
23
+
24
+ task :default => :spec
25
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require File.dirname(__FILE__) + '/../lib/some'
5
+
6
+ require 'thor'
7
+
8
+ class CLI < Thor
9
+ desc "launch [<role>]", "launch an instance as role, or omit to ssh to vanilla instance"
10
+ def launch(role=nil)
11
+ id = task("Launch instance") { some.launch }
12
+ host = task("Acquire hostname") { some.wait_for_hostname(id) }
13
+ task("Wait for ssh") { some.wait_for_ssh(host) }
14
+
15
+ if role
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)
32
+ else
33
+ puts "\nLogging you in via ssh. Type 'exit' or Ctrl-D to return to your local system."
34
+ puts '-' * 78
35
+ connect_ssh(host)
36
+ end
37
+ end
38
+
39
+ desc "ssh [<instance_id or hostname>]", "ssh to a specified instance or first available"
40
+ def ssh(id=nil)
41
+ inst = some.find(id) || some.running.first || abort("No running instances")
42
+ hostname = inst[:hostname] || wait_for_hostname(inst[:instance_id])
43
+ connect_ssh hostname
44
+ end
45
+
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
+ desc "bootstrap", "bootstrap chef and cookbooks"
54
+ def bootstrap(id=nil)
55
+ inst = some.find(id) || some.running.first || abort("No running instances")
56
+ task "Bootstrap chef" do
57
+ some.bootstrap_chef(inst[:hostname])
58
+ end
59
+ end
60
+
61
+ desc "role", "setup instance as a role"
62
+ def role(role, id=nil)
63
+ inst = some.find(id) || some.running.first || abort("No running instances")
64
+ task "Setup #{role}" do
65
+ some.setup_role(inst[:hostname], role)
66
+ end
67
+ end
68
+
69
+ desc "list", "list running instances"
70
+ def list
71
+ some.list.each do |inst|
72
+ printf "%-50s %-12s %s\n", inst[:hostname], inst[:instance_id], inst[:status]
73
+ end
74
+ end
75
+
76
+ desc "console [<instance_id or hostname>]", "get console output for instance or first available"
77
+ def console(id=nil)
78
+ inst = some.find(id) || (some.running | some.pending).first || abort("No running or pending instances")
79
+
80
+ puts some.console_output(inst[:instance_id]).inspect
81
+ end
82
+
83
+ desc "terminate [<instance_id or hostname>]", "terminate specified instance or first available"
84
+ def terminate(id=nil)
85
+ inst = some.find(id) || (some.running | some.pending).first || abort("No running or pending instances")
86
+
87
+ some.terminate(inst[:instance_id])
88
+ puts "#{inst[:hostname] || inst[:instance_id]} scheduled for termination"
89
+ end
90
+
91
+ desc "terminate_all", "terminate all instances"
92
+ def terminate_all
93
+ instances = (some.running | some.pending)
94
+ abort("No running or pending instances") if instances.empty?
95
+ instances.each do |inst|
96
+ some.terminate(inst[:instance_id])
97
+ puts "#{inst[:hostname] || inst[:instance_id]} scheduled for termination"
98
+ end
99
+ end
100
+
101
+ desc "volumes", "list all volumes"
102
+ def volumes
103
+ some.volumes.each do |v|
104
+ printf "%-10s %4sGB %10s %15s %15s\n", v[:volume_id], v[:size], v[:status], v[:instance], v[:device]
105
+ end
106
+ end
107
+
108
+ desc "create_volume [<megabytes>]", "create a volume"
109
+ def create_volume(size=5)
110
+ task("Create #{size}GB volume") { some.create_volume(size) }
111
+ end
112
+
113
+ desc "destroy_volume [<volume_id>]", "destroy a volume"
114
+ def destroy_volume(volume=nil)
115
+ vol_id = (some.find_volume(volume) || some.nondestroyed_volumes.first || abort("No volumes"))[:volume_id]
116
+ task("Destroy volume") { some.destroy_volume(vol_id) }
117
+ end
118
+
119
+ desc "attach [<volume_id>] [<instance_id or hostname>] [<device>]", "attach volume to running instance"
120
+ def attach(volume=nil, inst_id=nil, device=nil)
121
+ vol_id = (some.find_volume(volume) || some.available_volumes.first || abort("No available volumes"))[:volume_id]
122
+ inst_id = (some.find(inst_id) || some.running.first || abort("No running instances"))[:instance_id]
123
+ device ||= '/dev/sdc1'
124
+ task("Attach #{vol_id} to #{inst_id} as #{device}") do
125
+ some.attach(vol_id, inst_id, device)
126
+ end
127
+ end
128
+
129
+ desc "detach [<volume_id>]", "detach volume from instance"
130
+ def detach(volume=nil)
131
+ vol_id = (some.find_volume(volume) || some.attached_volumes.first || abort("No attached volumes"))[:volume_id]
132
+ task("Detach #{vol_id}") { some.detach(vol_id) }
133
+ end
134
+
135
+ no_tasks do
136
+ def some
137
+ @some ||= Some.new
138
+ end
139
+
140
+ def config
141
+ some.config
142
+ end
143
+
144
+ def task(msg, &block)
145
+ printf "---> %-24s ", "#{msg}..."
146
+ start = Time.now
147
+ result = block.call || 'done'
148
+ finish = Time.now
149
+ time = sprintf("%0.1f", finish - start)
150
+ puts "#{result} (#{time}s)"
151
+ result
152
+ end
153
+
154
+ def connect_ssh(hostname)
155
+ some.wait_for_ssh(hostname)
156
+ system "ssh -i #{some.keypair_file} #{config['user']}@#{hostname}"
157
+ if $?.success?
158
+ puts "\nType 'some terminate' if you're done with this instance."
159
+ end
160
+ end
161
+
162
+ def display_resources(host)
163
+ resources = some.resources(host)
164
+ unless resources.empty?
165
+ puts "Your instance is exporting the following resources:"
166
+ resources.each do |resource|
167
+ puts " #{resource}"
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ CLI.start
@@ -0,0 +1,297 @@
1
+ require 'NIFTY'
2
+ require 'yaml'
3
+ require 'socket'
4
+
5
+ class Some
6
+ def launch
7
+ ami = config['ami']
8
+ raise "No AMI selected" unless ami
9
+
10
+ create_keypair unless File.exists? keypair_file
11
+
12
+ create_security_group
13
+ open_firewall(22)
14
+
15
+ result = api.run_instances(
16
+ :image_id => ami,
17
+ :instance_type => config['instance_size'] || 'mini',
18
+ :key_name => 'something',
19
+ :security_group => 'something',
20
+ :availability_zone => config['availability_zone'],
21
+ :disable_api_termination => false
22
+ )
23
+ result.instancesSet.item[0].instanceId
24
+ end
25
+
26
+ def list
27
+ @list ||= fetch_list
28
+ end
29
+
30
+ def volumes
31
+ result = api.describe_volumes
32
+ return [] unless result.volumeSet
33
+
34
+ result.volumeSet.item.map do |row|
35
+ {
36
+ :volume_id => row["volumeId"],
37
+ :size => row["size"],
38
+ :status => row["status"],
39
+ :device => (row["attachmentSet"]["item"].first["device"] rescue ""),
40
+ :instance_id => (row["attachmentSet"]["item"].first["instanceId"] rescue ""),
41
+ }
42
+ end
43
+ end
44
+
45
+ def available_volumes
46
+ volumes.select { |vol| vol[:status] == 'available' }
47
+ end
48
+
49
+ def attached_volumes
50
+ volumes.select { |vol| vol[:status] == 'in-use' }
51
+ end
52
+
53
+ def nondestroyed_volumes
54
+ volumes.select { |vol| vol[:status] != 'deleting' }
55
+ end
56
+
57
+ def attach(volume, instance, device)
58
+ result = api.attach_volume(
59
+ :volume_id => volume,
60
+ :instance_id => instance,
61
+ :device => device
62
+ )
63
+ "done"
64
+ end
65
+
66
+ def detach(volume)
67
+ result = api.detach_volume(:volume_id => volume, :force => "true")
68
+ "done"
69
+ end
70
+
71
+ def create_volume(size)
72
+ result = api.create_volume(
73
+ :availability_zone => config['availability_zone'],
74
+ :size => size.to_s
75
+ )
76
+ result["volumeId"]
77
+ end
78
+
79
+ def destroy_volume(volume)
80
+ api.delete_volume(:volume_id => volume)
81
+ "done"
82
+ end
83
+
84
+ def fetch_list
85
+ result = api.describe_instances
86
+ return [] unless result.reservationSet
87
+
88
+ instances = []
89
+ result.reservationSet.item.each do |r|
90
+ r.instancesSet.item.each do |item|
91
+ instances << {
92
+ :instance_id => item.instanceId,
93
+ :status => item.instanceState.name,
94
+ :hostname => item.dnsName
95
+ }
96
+ end
97
+ end
98
+ instances
99
+ end
100
+
101
+ def find(id_or_hostname)
102
+ return unless id_or_hostname
103
+ id_or_hostname = id_or_hostname.strip.downcase
104
+ list.detect do |inst|
105
+ inst[:hostname] == id_or_hostname or
106
+ inst[:instance_id] == id_or_hostname
107
+ end
108
+ end
109
+
110
+ def find_volume(volume_id)
111
+ return unless volume_id
112
+ volume_id = volume_id.strip.downcase
113
+ volumes.detect do |volume|
114
+ volume[:volume_id] == volume_id or
115
+ volume[:volume_id].gsub(/^vol-/, '') == volume_id
116
+ end
117
+ end
118
+
119
+ def running
120
+ list_by_status('running')
121
+ end
122
+
123
+ def pending
124
+ list_by_status('pending')
125
+ end
126
+
127
+ def list_by_status(status)
128
+ list.select { |i| i[:status] == status }
129
+ end
130
+
131
+ def instance_info(instance_id)
132
+ fetch_list.detect do |inst|
133
+ inst[:instance_id] == instance_id
134
+ end
135
+ end
136
+
137
+ def wait_for_hostname(instance_id)
138
+ raise ArgumentError unless instance_id
139
+ loop do
140
+ if inst = instance_info(instance_id)
141
+ if hostname = inst[:hostname]
142
+ return hostname
143
+ end
144
+ end
145
+ sleep 1
146
+ end
147
+ end
148
+
149
+ def wait_for_ssh(hostname)
150
+ raise ArgumentError unless hostname
151
+ loop do
152
+ begin
153
+ Timeout::timeout(4) do
154
+ TCPSocket.new(hostname, 22)
155
+ return
156
+ end
157
+ rescue SocketError, Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
158
+ end
159
+ end
160
+ end
161
+
162
+ def bootstrap_chef(hostname)
163
+ commands = [
164
+ "curl -L https://www.opscode.com/chef/install.sh | bash",
165
+ "mkdir -p /var/chef/cookbooks /etc/chef",
166
+ "echo json_attribs \\'/etc/chef/dna.json\\' > /etc/chef/solo.rb"
167
+ ]
168
+ ssh(hostname, commands)
169
+ end
170
+
171
+ def setup_role(hostname, role)
172
+ commands = [
173
+ "echo \'#{config['role'][role]}\' > /etc/chef/dna.json",
174
+ "chef-solo -r #{config['cookbooks_url']}"
175
+ ]
176
+ ssh(hostname, commands)
177
+ end
178
+
179
+ def ssh(hostname, cmds)
180
+ IO.popen("ssh -i #{keypair_file} #{config['user']}@#{hostname} > ~/.some/ssh.log 2>&1", "w") do |pipe|
181
+ pipe.puts cmds.join(' && ')
182
+ end
183
+ unless $?.success?
184
+ abort "failed\nCheck ~/.some/ssh.log for the output"
185
+ end
186
+ end
187
+
188
+ def resources(hostname)
189
+ @resources ||= {}
190
+ @resources[hostname] ||= fetch_resources(hostname)
191
+ end
192
+
193
+ def fetch_resources(hostname)
194
+ cmd = "ssh -i #{keypair_file} #{config['user']}@#{hostname} 'cat /root/resources' 2>&1"
195
+ out = IO.popen(cmd, 'r') { |pipe| pipe.read }
196
+ abort "failed to read resources, output:\n#{out}" unless $?.success?
197
+ parse_resources(out, hostname)
198
+ end
199
+
200
+ def parse_resources(raw, hostname)
201
+ raw.split("\n").map do |line|
202
+ line.gsub(/localhost/, hostname)
203
+ end
204
+ end
205
+
206
+ def terminate(instance_id)
207
+ inst = instance_info(instance_id)
208
+ if inst[:status] != 'stopped'
209
+ api.stop_instances(:instance_id => [ instance_id ])
210
+ puts "waiting for #{instance_id} to stop "
211
+ loop do
212
+ print '.'
213
+ if inst = instance_info(instance_id)
214
+ if inst[:status] == 'stopped'
215
+ break
216
+ end
217
+ end
218
+ sleep 5
219
+ end
220
+ end
221
+ api.terminate_instances(:instance_id => [ instance_id ])
222
+ end
223
+
224
+ def console_output(instance_id)
225
+ api.get_console_output(:instance_id => instance_id)["output"]
226
+ end
227
+
228
+ def config
229
+ @config ||= default_config.merge read_config
230
+ end
231
+
232
+ def default_config
233
+ {
234
+ 'user' => 'root',
235
+ 'ami' => 'ami-ed46a784',
236
+ 'availability_zone' => 'east-12',
237
+ 'password' => 'password'
238
+ }
239
+ end
240
+
241
+ def some_dir
242
+ "#{ENV['HOME']}/.some"
243
+ end
244
+
245
+ def read_config
246
+ YAML.load File.read("#{some_dir}/config.yml")
247
+ rescue Errno::ENOENT
248
+ raise "Some is not configured, please fill in ~/.some/config.yml"
249
+ end
250
+
251
+ def keypair_file
252
+ "#{some_dir}/keypair.pem"
253
+ end
254
+
255
+ def create_keypair
256
+ keypair = api.create_key_pair(:key_name => "something", :password => config['password']).keyMaterial
257
+ File.open(keypair_file, 'w') { |f| f.write Base64.decode64(keypair) }
258
+ File.chmod 0600, keypair_file
259
+ end
260
+
261
+ def create_security_group
262
+ api.create_security_group(:group_name => 'something', :group_description => 'Something')
263
+ rescue NIFTY::ResponseError => e
264
+ if e.message != "The groupName 'something' already exists."
265
+ raise e
266
+ end
267
+ end
268
+
269
+ def open_firewall(port)
270
+ api.authorize_security_group_ingress(
271
+ :group_name => 'something',
272
+ :ip_permissions => {
273
+ :ip_protocol => 'tcp',
274
+ :from_port => port,
275
+ :to_port => port,
276
+ :cidr_ip => '0.0.0.0/0'
277
+ }
278
+ )
279
+ rescue NIFTY::ResponseError => e
280
+ raise e
281
+ end
282
+
283
+ def api
284
+ @api ||= NIFTY::Cloud::Base.new(
285
+ :access_key => config['access_key'],
286
+ :secret_key => config['secret_key'],
287
+ :server => server,
288
+ :path => '/api'
289
+ )
290
+ end
291
+
292
+ def server
293
+ zone = config['availability_zone']
294
+ host = zone.slice(0, zone.length - 1)
295
+ "#{host}.cp.cloud.nifty.com"
296
+ end
297
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/../lib/some'
2
+
3
+ require 'bacon'
4
+ require 'mocha/standalone'
5
+ require 'mocha/object'
6
+
7
+ class Bacon::Context
8
+ include Mocha::API
9
+
10
+ def initialize(name, &block)
11
+ @name = name
12
+ @before, @after = [
13
+ [lambda { mocha_setup }],
14
+ [lambda { mocha_verify ; mocha_teardown }]
15
+ ]
16
+ @block = block
17
+ end
18
+
19
+ def xit(desc, &bk)
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ require 'fileutils'
4
+
5
+ describe Some do
6
+ before do
7
+ @work_path = "/tmp/spec_#{Process.pid}/"
8
+ FileUtils.mkdir_p(@work_path)
9
+ File.open("#{@work_path}/config.yml", "w") do |f|
10
+ f.write YAML.dump({})
11
+ end
12
+
13
+ @some = Some.new
14
+ @some.stubs(:some_dir).returns(@work_path)
15
+ end
16
+
17
+ after do
18
+ FileUtils.rm_rf(@work_path)
19
+ end
20
+
21
+ it "defaults to user root if none is specified in the config" do
22
+ @some.config['user'].should == 'root'
23
+ end
24
+
25
+ it "uses specified user if one is in the config" do
26
+ File.open("#{@work_path}/config.yml", "w") do |f|
27
+ f.write YAML.dump('user' => 'joe')
28
+ end
29
+ @some.config['user'].should == 'joe'
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: some
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - tily
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-04 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nifty-cloud-sdk
16
+ requirement: &110762780 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *110762780
25
+ - !ruby/object:Gem::Dependency
26
+ name: thor
27
+ requirement: &110762200 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *110762200
36
+ description: sumo clone for NIFTY Cloud
37
+ email: tidnlyam@gmail.com
38
+ executables:
39
+ - some
40
+ extensions: []
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - README.rdoc
45
+ - Rakefile
46
+ - VERSION
47
+ - bin/some
48
+ - lib/some.rb
49
+ - spec/base.rb
50
+ - spec/some_spec.rb
51
+ homepage: http://github.com/tily/some
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project: some
71
+ rubygems_version: 1.8.13
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: sumo clone for NIFTY Cloud
75
+ test_files: []