ssp 0.1
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.
- data/LICENSE +19 -0
- data/README.md +1 -0
- data/bin/ssp +6 -0
- data/lib/ssp/app.rb +106 -0
- data/lib/ssp/application/bags.rb +67 -0
- data/lib/ssp/application/node.rb +128 -0
- data/lib/ssp/application/pair.rb +107 -0
- data/lib/ssp/config.rb +7 -0
- data/lib/ssp/version.rb +3 -0
- metadata +105 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Secret Sauce Partners, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
The SSP gem provides various command line tools that we use internally. It might be useful to others as well, but we're not trying to make them general.
|
data/bin/ssp
ADDED
data/lib/ssp/app.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/group'
|
3
|
+
|
4
|
+
module SSP
|
5
|
+
class App < Thor
|
6
|
+
namespace :app
|
7
|
+
map "-T" => :list
|
8
|
+
|
9
|
+
# Override Thor#help so it can give information about any class and any method.
|
10
|
+
#
|
11
|
+
def help(meth=nil)
|
12
|
+
if meth && !self.respond_to?(meth)
|
13
|
+
initialize_commands
|
14
|
+
klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
|
15
|
+
if klass
|
16
|
+
klass.start(["-h", task].compact, :shell => self.shell)
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# If a task is not found on SSP::App, method missing is invoked and
|
26
|
+
# SSP::App is then responsable for finding the task in all classes.
|
27
|
+
#
|
28
|
+
def method_missing(meth, *args)
|
29
|
+
meth = meth.to_s
|
30
|
+
initialize_commands
|
31
|
+
klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
|
32
|
+
args.unshift(task) if task
|
33
|
+
klass.start(args, :shell => self.shell)
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "version", "Show SSP version"
|
37
|
+
def version
|
38
|
+
require 'ssp/version'
|
39
|
+
say "SSP #{SSP::VERSION}"
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "list [SEARCH]", "List the available ssp tasks (--substring means .*SEARCH)"
|
43
|
+
method_options :substring => :boolean, :group => :string, :all => :boolean
|
44
|
+
def list(search="")
|
45
|
+
initialize_commands
|
46
|
+
|
47
|
+
search = ".*#{search}" if options["substring"]
|
48
|
+
search = /^#{search}.*/i
|
49
|
+
group = options[:group] || "standard"
|
50
|
+
|
51
|
+
klasses = Thor::Base.subclasses.select do |k|
|
52
|
+
(options[:all] || k.group == group) && k.namespace =~ search
|
53
|
+
end
|
54
|
+
|
55
|
+
display_klasses(klasses)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
private
|
60
|
+
def initialize_commands
|
61
|
+
unless @_commands_initialized
|
62
|
+
Dir[File.join(File.dirname(__FILE__), "application", "*.rb")].each do |command|
|
63
|
+
require(command)
|
64
|
+
end
|
65
|
+
@_commands_initialized = true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Display information about the given klasses.
|
70
|
+
#
|
71
|
+
def display_klasses(klasses=Thor::Base.subclasses)
|
72
|
+
klasses -= [Thor, SSP::App, Thor::Group]
|
73
|
+
|
74
|
+
raise Error, "No Thor tasks available" if klasses.empty?
|
75
|
+
|
76
|
+
# Remove subclasses
|
77
|
+
klasses.dup.each do |klass|
|
78
|
+
klasses -= Thor::Util.thor_classes_in(klass)
|
79
|
+
end
|
80
|
+
|
81
|
+
list = Hash.new { |h,k| h[k] = [] }
|
82
|
+
groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
|
83
|
+
|
84
|
+
# Get classes which inherit from Thor
|
85
|
+
(klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
|
86
|
+
|
87
|
+
# Get classes which inherit from Thor::Base
|
88
|
+
groups.map! { |k| k.printable_tasks(false).first }
|
89
|
+
list["root"] = groups
|
90
|
+
|
91
|
+
# Order namespaces with default coming first
|
92
|
+
list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
|
93
|
+
list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
|
94
|
+
end
|
95
|
+
|
96
|
+
def display_tasks(namespace, list) #:nodoc:
|
97
|
+
list.sort!{ |a,b| a[0] <=> b[0] }
|
98
|
+
|
99
|
+
say shell.set_color(namespace, :blue, true)
|
100
|
+
say "-" * namespace.size
|
101
|
+
|
102
|
+
print_table(list, :truncate => true)
|
103
|
+
say
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
class SSP::App::Bags < Thor
|
5
|
+
namespace :bags
|
6
|
+
|
7
|
+
class_option :knife_config, :aliases => "-c",
|
8
|
+
:desc => "Location of the knife configuration file",
|
9
|
+
:default => File.join(ENV['HOME'], '.chef', 'knife.rb')
|
10
|
+
|
11
|
+
class_option :path, :aliases => "-p",
|
12
|
+
:desc => "Root path for the data bag files",
|
13
|
+
:default => "./databags"
|
14
|
+
|
15
|
+
|
16
|
+
desc "download", "Downloads data bags from the server"
|
17
|
+
def download
|
18
|
+
bags = JSON.parse(knife_cmd("list"))
|
19
|
+
bags.each do |bag|
|
20
|
+
say "Downloading #{shell.set_color(bag, :bold)}: "
|
21
|
+
bag_path = data_bag_root.join(bag)
|
22
|
+
bag_path.mkpath
|
23
|
+
items = JSON.parse(knife_cmd("show #{bag}"))
|
24
|
+
items.each_with_index do |item, i|
|
25
|
+
say "#{", " unless i == 0}#{item}", nil, false
|
26
|
+
knife_cmd "show #{bag} #{item} > #{bag_path.join("#{item}.json")}"
|
27
|
+
end
|
28
|
+
puts
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "upload [BAG [ITEM]]", "Uploads data bags to the server"
|
33
|
+
def upload(selected_bag = nil, selected_item = nil)
|
34
|
+
bags = if selected_bag
|
35
|
+
[data_bag_root.join(selected_bag)]
|
36
|
+
else
|
37
|
+
Pathname.glob(data_bag_root.join("*")).select {|p| p.directory?}
|
38
|
+
end
|
39
|
+
bags.each do |bag_path|
|
40
|
+
bag = bag_path.basename
|
41
|
+
say "Uploading #{shell.set_color(bag, :bold)}: "
|
42
|
+
knife_cmd "create #{bag} > /dev/null"
|
43
|
+
current_items = JSON.parse(knife_cmd("show #{bag}"))
|
44
|
+
items = if selected_bag and selected_item
|
45
|
+
[data_bag_root.join(selected_bag, "#{selected_item}.json")]
|
46
|
+
else
|
47
|
+
Pathname.glob(bag_path.join("*.json"))
|
48
|
+
end
|
49
|
+
items.each_with_index do |item, i|
|
50
|
+
key = item.basename.to_s.split(".").first
|
51
|
+
say "#{", " unless i == 0}#{key}", nil, false
|
52
|
+
command = current_items.include?(key) ? "edit" : "create"
|
53
|
+
knife_cmd "#{command} -e 'cat #{item} >' #{bag} #{key}"
|
54
|
+
end
|
55
|
+
puts
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def knife_cmd(cmd)
|
61
|
+
%x{knife data bag -c #{options[:knife_config]} #{cmd}}
|
62
|
+
end
|
63
|
+
|
64
|
+
def data_bag_root
|
65
|
+
Pathname.new(options[:path])
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'chef/config'
|
3
|
+
require 'chef/knife/ssh'
|
4
|
+
require 'ssp/config'
|
5
|
+
|
6
|
+
class SSP::App::Node < Thor
|
7
|
+
namespace :node
|
8
|
+
|
9
|
+
class_option :api_key,
|
10
|
+
:desc => "Rackspace API key",
|
11
|
+
:default => SSP::Config[:rackspace][:api_key]
|
12
|
+
|
13
|
+
class_option :api_username,
|
14
|
+
:desc => "Rackspace API username",
|
15
|
+
:default => SSP::Config[:rackspace][:username]
|
16
|
+
|
17
|
+
class_option :chef_config, :aliases => "-c",
|
18
|
+
:desc => "Location of the chef configuration file",
|
19
|
+
:default => File.join(ENV['HOME'], '.chef', 'knife.rb')
|
20
|
+
|
21
|
+
|
22
|
+
desc "create FQDN [ROLES...]", "Creates a new chef client on Rackspace"
|
23
|
+
method_option :flavor, :aliases => "-f", :type => :numeric,
|
24
|
+
:desc => "Sets the flavor of the server (1: 256MB, 2: 512MB, 3: 1024MB, ...)",
|
25
|
+
:default => 2
|
26
|
+
method_option :image, :aliases => "-i", :type => :numeric,
|
27
|
+
:desc => "Sets the image to be used (49: Ubuntu 10.04, 4151016: chef-node)",
|
28
|
+
:default => 4151016
|
29
|
+
def create(fqdn, *roles)
|
30
|
+
server = connection.servers.new
|
31
|
+
|
32
|
+
# Convert roles array to proper run list
|
33
|
+
run_list = roles.map { |r| r =~ /recipe\[(.*)\]/ ? $1 : "role[#{r}]" }
|
34
|
+
|
35
|
+
# Script for creating an /etc/hosts file that will result in correct
|
36
|
+
# fqdn and will point to the temporary chef server if we're testing
|
37
|
+
setup_node_script = <<-EOH
|
38
|
+
#!/bin/bash
|
39
|
+
|
40
|
+
IP=$(ifconfig eth0 | sed -ne 's/.*inet addr:\\([0-9\\.]*\\).*/\\1/p')
|
41
|
+
|
42
|
+
echo "127.0.0.1 localhost localhost.localdomain" > /etc/hosts
|
43
|
+
echo "$IP #{fqdn} #{fqdn.split(".").first}" >> /etc/hosts
|
44
|
+
EOH
|
45
|
+
if chef_config[:chef_server_url] == "https://chef-server.rackspace"
|
46
|
+
chef_server_ip = %x{dscl . read Hosts/chef-server.rackspace 2>/dev/null}[/[\d\.]+/]
|
47
|
+
setup_node_script << %{echo "#{chef_server_ip} chef.secretsaucepartners.com" >> /etc/hosts\n}
|
48
|
+
chef_config[:chef_server_url] = "https://chef.secretsaucepartners.com"
|
49
|
+
end
|
50
|
+
setup_node_script << "exit 0\n"
|
51
|
+
|
52
|
+
# Always include the chef-client role in the run list
|
53
|
+
unless run_list.include? "role[chef-client]"
|
54
|
+
run_list.unshift "role[chef-client]"
|
55
|
+
end
|
56
|
+
|
57
|
+
server.flavor_id = options[:flavor]
|
58
|
+
server.image_id = options[:image]
|
59
|
+
server.name = fqdn
|
60
|
+
server.personality = [
|
61
|
+
{
|
62
|
+
'path' => '/etc/setup-node',
|
63
|
+
'contents' => setup_node_script
|
64
|
+
},
|
65
|
+
{
|
66
|
+
'path' => "/etc/chef/validation.pem",
|
67
|
+
'contents' => IO.read(chef_config[:validation_key])
|
68
|
+
},
|
69
|
+
{
|
70
|
+
'path' => "/etc/chef/client.rb",
|
71
|
+
'contents' => <<-EOH
|
72
|
+
log_level :info
|
73
|
+
log_location STDOUT
|
74
|
+
chef_server_url "#{chef_config[:chef_server_url]}"
|
75
|
+
validation_client_name "#{chef_config[:validation_client_name]}"
|
76
|
+
EOH
|
77
|
+
},
|
78
|
+
{
|
79
|
+
'path' => "/etc/chef/first-boot.json",
|
80
|
+
'contents' => { "run_list" => run_list }.to_json
|
81
|
+
},
|
82
|
+
]
|
83
|
+
|
84
|
+
server.save
|
85
|
+
|
86
|
+
say_status "Name: ", server.name, :cyan
|
87
|
+
say_status "Flavor: ", server.flavor_id, :cyan
|
88
|
+
say_status "Image: ", server.image_id, :cyan
|
89
|
+
say_status "Public IP: ", server.addresses["public"], :cyan
|
90
|
+
say_status "Private IP: ", server.addresses["private"], :cyan
|
91
|
+
say_status "Password: ", server.password, :cyan
|
92
|
+
|
93
|
+
say "\nRequesting server", :magenta
|
94
|
+
saved_password = server.password
|
95
|
+
|
96
|
+
# wait for it to be ready to do stuff
|
97
|
+
server.wait_for { print "."; ready? }
|
98
|
+
|
99
|
+
say "\nServer ready, waiting 15 seconds to bootstrap."
|
100
|
+
sleep 15
|
101
|
+
|
102
|
+
say "\nBootstrapping #{shell.set_color(server.name, :bold)}..."
|
103
|
+
|
104
|
+
ssh = Chef::Knife::Ssh.new
|
105
|
+
ssh.name_args = [ server.addresses["public"][0], "/bin/bash /etc/setup-node && /usr/local/bin/chef-client -j /etc/chef/first-boot.json" ]
|
106
|
+
ssh.config[:ssh_user] = "root"
|
107
|
+
ssh.config[:manual] = true
|
108
|
+
ssh.config[:password] = saved_password
|
109
|
+
ssh.password = saved_password
|
110
|
+
ssh.run
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
def connection
|
115
|
+
@connection ||= Fog::Rackspace::Servers.new(
|
116
|
+
:rackspace_api_key => options[:api_key],
|
117
|
+
:rackspace_username => options[:api_username]
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def chef_config
|
122
|
+
unless defined?(@_chef_config_loaded)
|
123
|
+
Chef::Config.from_file(options[:chef_config])
|
124
|
+
@_chef_config_loaded = true
|
125
|
+
end
|
126
|
+
Chef::Config
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
class SSP::App::Pair < Thor
|
5
|
+
namespace :pair
|
6
|
+
|
7
|
+
default_task :set_author
|
8
|
+
desc "set_author [PAIR]", "Sets the commit author for the repository to a pair or just yourself"
|
9
|
+
method_option :devsfile,
|
10
|
+
:desc => "Location of the SSP developers file",
|
11
|
+
:default => File.join(ENV['HOME'], '.sspdevs')
|
12
|
+
method_option :pair_email,
|
13
|
+
:desc => "Base email to use for constructing pair emails",
|
14
|
+
:default => "dev@secretsaucepartners.com"
|
15
|
+
def set_author(pair=nil)
|
16
|
+
chek_dot_git!
|
17
|
+
|
18
|
+
me = %x{git config --global user.name}.chomp
|
19
|
+
my_email = sspdevs[me]
|
20
|
+
devs = sspdevs.map {|name,_| name}.sort
|
21
|
+
devs.delete(me)
|
22
|
+
|
23
|
+
if pair.nil?
|
24
|
+
say "Pairing?", :bold
|
25
|
+
devs.each_with_index do |name, index|
|
26
|
+
say_status "#{index + 1}:", name, :cyan
|
27
|
+
end
|
28
|
+
print "Enter the index of your pair or leave blank if not pairing: "
|
29
|
+
|
30
|
+
while (choice = File.new("/dev/tty").readline.chomp) !~ /\A\d*\Z/
|
31
|
+
say "Bad input `#{choice}'"
|
32
|
+
end
|
33
|
+
elsif pair =~ /^(|x|me|not)$/i
|
34
|
+
choice = ""
|
35
|
+
else
|
36
|
+
pair = pair.downcase
|
37
|
+
pair = devs.detect do |dev|
|
38
|
+
initial = dev.split(" ").map {|x| x[0..0]}.join.downcase
|
39
|
+
initial == pair or dev.downcase.split(" ").include?(pair)
|
40
|
+
end
|
41
|
+
raise Thor::Error, "No such pair" unless pair
|
42
|
+
choice = devs.index(pair) + 1
|
43
|
+
end
|
44
|
+
|
45
|
+
commit_name, commit_email = if choice == ''
|
46
|
+
[me, my_email]
|
47
|
+
else
|
48
|
+
pair = devs[choice.to_i - 1]
|
49
|
+
["#{me} & #{pair}", options[:pair_email].sub('@', initials(me, pair)+'@')]
|
50
|
+
end
|
51
|
+
|
52
|
+
say "Setting #{shell.set_color("commit author", :bold)} to #{shell.set_color(commit_name, :green)}"
|
53
|
+
%x{git config user.name '#{commit_name}'}
|
54
|
+
say "Setting #{shell.set_color("commit email", :bold)} to #{shell.set_color(commit_email, :green)}"
|
55
|
+
%x{git config user.email '#{commit_email}'}
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "status", "Displays current commit author and email and asks for confirmation (used by the install hook)"
|
59
|
+
def status
|
60
|
+
chek_dot_git!
|
61
|
+
|
62
|
+
commit_name = %x{git config user.name}.chomp
|
63
|
+
commit_email = %x{git config user.email}.chomp
|
64
|
+
say "Current #{shell.set_color("commit author", :bold)} is #{shell.set_color(commit_name, :yellow, true)}"
|
65
|
+
say "Current #{shell.set_color("commit email", :bold)} is #{shell.set_color(commit_email, :yellow, true)}"
|
66
|
+
|
67
|
+
say "Is this ok? [Y/n] "
|
68
|
+
unless File.new("/dev/tty").readline.chomp =~ /^(y(es)?)?$/i
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "install_hook", "Installs a git pre-commit hook which checks commit author and email"
|
74
|
+
def install_hook
|
75
|
+
chek_dot_git!
|
76
|
+
|
77
|
+
hook_file = ".git/hooks/pre-commit"
|
78
|
+
raise Thor::Error, "a pre-commit hook file already exists" if File.exists?(hook_file)
|
79
|
+
|
80
|
+
File.open(hook_file, "w") { |f| f.write "#!/bin/sh\nssp pair:status\n" }
|
81
|
+
File.chmod(0755, hook_file)
|
82
|
+
|
83
|
+
say "pre-commit hook is installed in #{hook_file}"
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def chek_dot_git!
|
88
|
+
unless File.exists?(".git")
|
89
|
+
raise Thor::Error, "This doesn't look like a git repository."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def sspdevs
|
94
|
+
@sspdevs ||= begin
|
95
|
+
unless File.exists?(options[:devsfile])
|
96
|
+
File.open(options[:devsfile], "w") do |f|
|
97
|
+
f.write(open("https://gist.github.com/bba2eeabf72da8d61254.txt").read)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
YAML.load_file(options[:devsfile])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def initials(*names)
|
105
|
+
"+" + names.map {|n| n.split(" ").map {|x| x[0..0]}.join}.join("+").downcase
|
106
|
+
end
|
107
|
+
end
|
data/lib/ssp/config.rb
ADDED
data/lib/ssp/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ssp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 9
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: "0.1"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- !binary |
|
13
|
+
TMOhc3psw7MgQsOhY3Np
|
14
|
+
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-06-30 00:00:00 +02:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: thor
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 17
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
- 13
|
34
|
+
version: "0.13"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: chef
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 25
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 9
|
49
|
+
version: "0.9"
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
description: " The SSP gem provides various command line tools that\n Secret Sauce Partners, Inc. uses internally.\n"
|
53
|
+
email: dev@secretsaucepartners.com
|
54
|
+
executables:
|
55
|
+
- ssp
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files: []
|
59
|
+
|
60
|
+
files:
|
61
|
+
- README.md
|
62
|
+
- LICENSE
|
63
|
+
- bin/ssp
|
64
|
+
- lib/ssp/app.rb
|
65
|
+
- lib/ssp/application/bags.rb
|
66
|
+
- lib/ssp/application/node.rb
|
67
|
+
- lib/ssp/application/pair.rb
|
68
|
+
- lib/ssp/config.rb
|
69
|
+
- lib/ssp/version.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/sspinc/ssp
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.7
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Various command line tools for Secret Sauce Partners, Inc.
|
104
|
+
test_files: []
|
105
|
+
|