small_wonder 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ ## Small Wonder
2
+
3
+ Small Wonder is a deployment tool.
4
+
5
+ Specifically it's a lightweight wrapper for Salticid, which is a tool that will run a bunch of commands on a bunch of machines for you. Kinda like capistrano. To a large extent Small Wonder is a means for using Salticid and Chef together to deploy applications. Salticid "roles" and "tasks" are Ruby code and can use the Chef Ruby APIs to search and access anything Chef Client or Knife can (by consuming your knife config). Small Wonder has a notion of an application object as well, this object contains data like the name, version, status and even the databag config data for an application. Name, version and status are saved back to the Chef node so you can use application specific details in your recipes, searches and etc.
6
+
7
+ ### License
8
+
9
+ Apache License Version 2.0
10
+
11
+ ### Usage
12
+
13
+ #### Configure
14
+
15
+ $ mkdir ~/.small_wonder
16
+ $ $EDITOR ~/.small_wonder/small_wonder.rb
17
+
18
+
19
+ chef_repo_path "/path/to/chef-repo" # the path to your chef-repo
20
+ ssh_user "user" # the ssh user you want Small Wonder and Salticid to use
21
+ application_deployment_attribute "deployed_applications" # the attribute Small Wonder will save data back to chef using
22
+ config_template_working_directory "/tmp/small_wonder" # the local directory temporary template work will be done in
23
+ remote_working_dir "/tmp/small_wonder_#{Time.now.to_i}" # the remote directory temporary template work will be done in
24
+ application_deployments_dir "/path/to/chef-repo/application_deployments" # path to your role files
25
+ application_templates_dir "/path/to/chef-repo/application_templates" # path to your template files
26
+ databag "apps" # the databag that contains application configuration data items
27
+
28
+ In addition to a Small Wonder config, you need a working Knife configuration file.
29
+
30
+ #### Expectations
31
+
32
+ Small Wonder expects the names of things to match up. Specifically the application name should be the same as the Salticid role name, databag item and template directory. Note that if an application name has a hyphen in it Small Wonder will convert it to an underscore internally with regards to Salticid roles since they need to be legit Ruby method names.
33
+
34
+ #### Roles and tasks
35
+
36
+ Define "roles" using ruby code and splitting deployment operations in to "tasks".
37
+
38
+ Check out the examples directory for ... well examples.
39
+
40
+ #### Running small_wonder
41
+
42
+ A role name should match the name of the application, Chef recipe and databag item your app uses for configuration.
43
+
44
+ To run the role use:
45
+
46
+ $ small_wonder -p example
47
+
48
+ If you have a specific Chef search query you want to use to as the source of nodes to deploy to:
49
+
50
+ $ small_wonder -p example -q "some:attribute"
51
+
52
+ The default search query is "recipes:example".
53
+
54
+ You can also specify a version number to pass to the application object:
55
+
56
+ $ small_wonder -p example -V 123
57
+
58
+ #### Chef Data
59
+
60
+ During the deployment Small Wonder will save data back to the Chef node. Moreover, status is updated to the task currently being run, so you can see live deployment progress in your searches.
61
+
62
+ If you want to find out details about an app use a knife search like:
63
+
64
+ $ knife search node "deployed_applications:example" -a deployed_applications
65
+
66
+ The output should be something like:
67
+
68
+ deployed_applications:
69
+ example:
70
+ status: final
71
+ version: 851
72
+ id: your.server.com
73
+
74
+ #### Templating
75
+
76
+ Small wonder supports some basic erb templating just like Chef. All erb files in your 'application_templates_dir' can get populated with data from various Chef objects and attributes. Those files then get scp'd to the server and "overlayed" (read: `cp -R`) on to the installation directory of the application.
77
+
78
+ Files and directories with 'VERSION' in the name will be convered to have the actual appclication.version variable in place of 'VERSION'.
79
+
80
+ The examples directory has some examples.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'small_wonder'))
4
+
5
+ SmallWonder.main
@@ -0,0 +1,87 @@
1
+ require 'rubygems'
2
+
3
+ require 'socket'
4
+ require 'excon'
5
+ require 'mixlib/config'
6
+ require 'mixlib/cli'
7
+ require 'mixlib/log'
8
+ require 'yajl/json_gem'
9
+ require 'salticid'
10
+ require 'chef'
11
+ require 'highline/import'
12
+ require 'erb'
13
+ require 'net/scp'
14
+
15
+ __DIR__ = File.dirname(__FILE__)
16
+
17
+ $LOAD_PATH.unshift __DIR__ unless
18
+ $LOAD_PATH.include?(__DIR__) ||
19
+ $LOAD_PATH.include?(File.expand_path(__DIR__))
20
+
21
+ require 'small_wonder/log'
22
+ require 'small_wonder/cli'
23
+ require 'small_wonder/config'
24
+ require 'small_wonder/utils'
25
+ require 'small_wonder/deploy'
26
+ require 'small_wonder/application'
27
+ require 'small_wonder/configuratorator'
28
+
29
+ require 'small_wonder/salticid_monkeypatch'
30
+
31
+ module SmallWonder
32
+ class << self
33
+
34
+ def salticid=(h)
35
+ @salticid = h
36
+ end
37
+
38
+ def salticid
39
+ @salticid
40
+ end
41
+
42
+ def main()
43
+
44
+ cli = SmallWonder::CLI.new
45
+ cli.parse_options
46
+
47
+ # consume small wonder config
48
+ SmallWonder::Utils.consume_config_file(cli, cli.config[:config_file])
49
+
50
+ unless SmallWonder::Utils.sane_working_dir?(SmallWonder::Config.remote_working_dir)
51
+ SmallWonder::Log.error("Your remote working dir looks strange (#{SmallWonder::Config.remote_working_dir})")
52
+ exit(1)
53
+ end
54
+
55
+ unless SmallWonder::Utils.sane_working_dir?(SmallWonder::Config.config_template_working_directory)
56
+ SmallWonder::Log.error("Your local working dir looks strange (#{SmallWonder::Config.config_template_working_directory})")
57
+ exit(1)
58
+ end
59
+
60
+ SmallWonder::Utils.consume_config_file(cli, cli.config[:knife_config_file])
61
+
62
+ # inintialize chef/knife config
63
+ Chef::Config[:node_name] = SmallWonder::Config.node_name
64
+ Chef::Config[:client_key] = SmallWonder::Config.client_key
65
+ Chef::Config[:chef_server_url] = SmallWonder::Config.chef_server_url
66
+
67
+ # Set up salticid
68
+ self.salticid = Salticid.new
69
+ self.salticid.load(File.expand_path(File.join(
70
+ SmallWonder::Config.application_deployments_dir, '**', '*.rb')))
71
+
72
+ case SmallWonder::Config.action
73
+ when "none"
74
+ SmallWonder::Log.info("No action specified, assuming deploy ...")
75
+ SmallWonder::Deploy.run
76
+ when "deploy"
77
+ SmallWonder::Deploy.run
78
+ when "vicki"
79
+ system("open http://www.youtube.com/watch?v=ukSvjqwJixw")
80
+ else
81
+ SmallWonder::Log.error("Supported action \"#{SmallWonder::Config.action}\"!")
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,109 @@
1
+ module SmallWonder
2
+ class Application
3
+
4
+ ## In small wonder an application object has the following constituent parts:
5
+ #### node name
6
+ #### application name
7
+ #### version
8
+ #### status
9
+ #### databag-based config data
10
+
11
+ attr_accessor :version, :status
12
+ attr_reader :node_name, :application_name, :config_data
13
+
14
+ def initialize(node_name, application_name, opts = {})
15
+ @node_name = node_name
16
+ @application_name = application_name
17
+
18
+ @config_data = Chef::DataBagItem.load(SmallWonder::Config.databag, application_name).raw_data
19
+
20
+ data = Chef::Node.load(node_name)
21
+
22
+ unless data[SmallWonder::Config.application_deployment_attribute]
23
+ create_application_deployment_attribute(node_name, application_name)
24
+ data = Chef::Node.load(node_name)
25
+ end
26
+
27
+ unless data[SmallWonder::Config.application_deployment_attribute].has_key?(application_name)
28
+ create_application_deployment_attribute_child(node_name, application_name)
29
+ data = Chef::Node.load(node_name)
30
+ end
31
+
32
+ unless data[SmallWonder::Config.application_deployment_attribute][application_name]
33
+ update_application_data(node_name, application_name, "version", "0")
34
+ update_application_data(node_name, application_name, "status", "new")
35
+ end
36
+
37
+ # set version to application version if we have one
38
+ @version = opts[:version] || nil
39
+
40
+ # set status to status before we update for deploy
41
+ @status = opts[:status] || get_status(node_name, application_name)
42
+
43
+ # save the data back to the chef node
44
+ update_application_data(node_name, application_name, "status", "initialized")
45
+ end
46
+
47
+ def version=(version)
48
+ @version = version
49
+ set_version(@node_name, @application_name, version)
50
+ end
51
+
52
+ def status=(status)
53
+ @status = status
54
+ set_status(@node_name, @application_name, status)
55
+ end
56
+
57
+ private
58
+
59
+ def get_existing_version(node, application)
60
+ version = get_chef_data_value(node, application, "version")
61
+
62
+ unless version
63
+ version = "0"
64
+ end
65
+
66
+ version
67
+ end
68
+
69
+ def set_version(node, application, version)
70
+ update_application_data(node, application, "version", version)
71
+ end
72
+
73
+ def get_status(node, application)
74
+ get_chef_data_value(node, application, "status")
75
+ end
76
+
77
+ def set_status(node, application, status)
78
+ update_application_data(node, application, "status", status)
79
+ end
80
+
81
+ def get_chef_data_value(node, application, key)
82
+ data = Chef::Node.load(node)
83
+ data[SmallWonder::Config.application_deployment_attribute][application][key]
84
+ end
85
+
86
+ def create_application_deployment_attribute(node, application)
87
+ data = Chef::Node.load(node)
88
+ data[SmallWonder::Config.application_deployment_attribute] = {}
89
+ data[SmallWonder::Config.application_deployment_attribute][application] = {}
90
+ data.save
91
+ end
92
+
93
+ def create_application_deployment_attribute_child(node, application)
94
+ data = Chef::Node.load(node)
95
+ data[SmallWonder::Config.application_deployment_attribute][application] = {}
96
+ data.save
97
+ end
98
+
99
+ def update_application_data(node, application, key, value)
100
+ data = Chef::Node.load(node)
101
+ data[SmallWonder::Config.application_deployment_attribute][application][key] = value
102
+ data.save
103
+
104
+ #SmallWonder::Log.info("[#{node} // #{application}] #{key} was updated to #{value}")
105
+ end
106
+
107
+ end
108
+ end
109
+
@@ -0,0 +1,64 @@
1
+ module SmallWonder
2
+ class CLI
3
+ include Mixlib::CLI
4
+
5
+ option :config_file,
6
+ :short => "-c CONFIG",
7
+ :long => "--config CONFIG",
8
+ :default => "#{ENV['HOME']}/.small_wonder/small_wonder.rb",
9
+ :description => "The configuration file to use"
10
+
11
+ option :knife_config_file,
12
+ :short => "-k CONFIG",
13
+ :long => "--knife CONFIG",
14
+ :default => "#{ENV['HOME']}/.chef/knife.rb",
15
+ :description => "The knife configuration file to use"
16
+
17
+ option :action,
18
+ :short => "-a ACTION",
19
+ :long => "--action ACTION",
20
+ :description => "The action you want small wonder to take [deploy]",
21
+ :default => "none"
22
+
23
+ option :app,
24
+ :short => "-p APP",
25
+ :long => "--app APP",
26
+ :description => "The app you want small wonder to do something with",
27
+ :default => nil
28
+
29
+ option :version,
30
+ :short => "-V VERSION",
31
+ :long => "--version VERSION",
32
+ :description => "The version of app you want small wonder to do something with",
33
+ :default => nil
34
+
35
+ option :query,
36
+ :short => "-q query",
37
+ :long => "--query query",
38
+ :description => "The query you want small wonder to do something with",
39
+ :default => nil
40
+
41
+ option :write_node_file,
42
+ :short => "-w",
43
+ :long => "--write",
44
+ :description => "true/false write the node json file to your chef-repo at end of deploy",
45
+ :default => false
46
+
47
+ option :log_level,
48
+ :short => "-l LEVEL",
49
+ :long => "--log_level LEVEL",
50
+ :description => "Set the log level (debug, info, warn, error, fatal)",
51
+ :default => :info,
52
+ :proc => Proc.new { |l| l.to_sym }
53
+
54
+ option :help,
55
+ :short => "-h",
56
+ :long => "--help",
57
+ :description => "All the help you need ...",
58
+ :on => :tail,
59
+ :boolean => true,
60
+ :show_options => true,
61
+ :exit => 0
62
+
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ module SmallWonder
2
+ class Config
3
+ extend Mixlib::Config
4
+
5
+ configure do |c|
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,88 @@
1
+ module SmallWonder
2
+ class Configuratorator
3
+
4
+ def self.generate_and_upload_files(application, path, opts = {})
5
+ config_template_dir = "#{SmallWonder::Config.application_templates_dir}/#{application.application_name}"
6
+
7
+ templates = Dir["#{config_template_dir}/**/*.erb"]
8
+
9
+ file_list = []
10
+
11
+ templates.each do |file|
12
+ filename = File.basename(file, '.*')
13
+ filename.gsub!("VERSION", application.version)
14
+
15
+ reldir = File.dirname(file).gsub("#{config_template_dir}", ".")
16
+ reldir.gsub!("VERSION", application.version)
17
+
18
+ file_list << "#{reldir}/#{filename}"
19
+
20
+ SmallWonder::Log.info("Generating #{reldir}/#{filename}")
21
+
22
+ generated_file = generate_file(file, application, config_template_dir, opts)
23
+
24
+ file_dir = "#{SmallWonder::Config.config_template_working_directory}/#{application.application_name}/#{reldir}"
25
+
26
+ FileUtils.mkdir_p(file_dir)
27
+
28
+ SmallWonder::Utils.write_file(generated_file, "#{file_dir}/#{filename}")
29
+ end
30
+
31
+ upload_files(application.node_name, application.application_name)
32
+ copy_files_to_install_dir(application.node_name, application.application_name, path)
33
+ cleanup_working_directories(application.node_name, application.application_name)
34
+
35
+ file_list
36
+ end
37
+
38
+ private
39
+
40
+ def self.generate_file(file, application, config_template_dir, opts)
41
+ @deploy_config = application.config_data
42
+ @options = @deploy_config
43
+ @node = Chef::Node.load(application.node_name)
44
+ node = Chef::Node.load(application.node_name)
45
+
46
+ if opts[:app_options]
47
+ @app_options = opts[:app_options]
48
+ end
49
+
50
+ template_file = File.read(file)
51
+
52
+ begin
53
+ template = ERB.new(template_file)
54
+ rescue Exception => e
55
+ SmallWonder::Log.error("Error generating file (#{file}).")
56
+ SmallWonder::Log.error(e)
57
+ exit(1)
58
+ end
59
+
60
+ template.result(binding)
61
+ end
62
+
63
+ def self.upload_files(node_name, application)
64
+ Net::SSH.start(node_name, SmallWonder::Config.ssh_user) do |ssh|
65
+ ssh.exec!("mkdir -p #{SmallWonder::Config.remote_working_dir}")
66
+ end
67
+
68
+ Net::SCP.start(node_name, SmallWonder::Config.ssh_user) do |scp|
69
+ scp.upload!("#{SmallWonder::Config.config_template_working_directory}/#{application}", SmallWonder::Config.remote_working_dir, {:recursive => true})
70
+ end
71
+ end
72
+
73
+ def self.copy_files_to_install_dir(node_name, application, path)
74
+ Net::SSH.start(node_name, SmallWonder::Config.ssh_user) do |ssh|
75
+ ssh.exec!("echo \"#{@sudo_password}\n\" | sudo -S cp -R #{SmallWonder::Config.remote_working_dir}/#{application}/* /#{path}/")
76
+ end
77
+ end
78
+
79
+ def self.cleanup_working_directories(node_name, application)
80
+ FileUtils.rm_rf("#{SmallWonder::Config.config_template_working_directory}/#{application}")
81
+
82
+ Net::SSH.start(node_name, SmallWonder::Config.ssh_user) do |ssh|
83
+ ssh.exec!("rm -rf #{SmallWonder::Config.remote_working_dir}")
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,100 @@
1
+ module SmallWonder
2
+ class Deploy
3
+ def self.run()
4
+ if SmallWonder::Config.app
5
+ nodes = SmallWonder::Deploy.node_query()
6
+ SmallWonder::Deploy.deploy(SmallWonder::Config.app, nodes)
7
+ else
8
+ SmallWonder::Log.error("No application was specified for your deploy, use the '-p' switch.")
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def self.node_query()
15
+ nodes = []
16
+
17
+ if SmallWonder::Config.query
18
+ query = SmallWonder::Config.query
19
+ else
20
+ query = "recipes:#{SmallWonder::Config.app}"
21
+ end
22
+
23
+ Chef::Search::Query.new.search(:node, "#{query}") do |n|
24
+ nodes << n[:fqdn]
25
+ end
26
+
27
+ nodes
28
+ end
29
+
30
+ def self.deploy(application_name, nodes)
31
+ if nodes.length > 0
32
+ SmallWonder::Log.info("Found the following nodes via your search.")
33
+
34
+ nodes.each do |node|
35
+ SmallWonder::Log.info("* #{node}")
36
+ end
37
+
38
+ input = ::HighLine.agree("Are you sure you want to deploy to these nodes [yes|no]?")
39
+
40
+ if input
41
+ SmallWonder::Log.info("Commencing deployment.")
42
+
43
+ nodes.each do |node|
44
+ if SmallWonder::Config.version
45
+ SmallWonder::Log.info("Got version #{SmallWonder::Config.version} from a command line option, using it as the current version for #{SmallWonder::Config.app}.")
46
+ application = SmallWonder::Application.new(node, application_name, {:version => SmallWonder::Config.version})
47
+ else
48
+ SmallWonder::Log.info("Did not get a app version to deploy on the command line, assuming you will set it during the deploy.")
49
+ application = SmallWonder::Application.new(node, application_name)
50
+ end
51
+
52
+ deploy_application(application)
53
+ end
54
+ end
55
+ else
56
+ SmallWonder::Log.info("No nodes found for your search.")
57
+ end
58
+ end
59
+
60
+ def self.deploy_application(application)
61
+ run_salticid_task(application)
62
+
63
+ if SmallWonder::Config.write_node_file
64
+ SmallWonder::Utils.write_node_data_file(application.node_name)
65
+ end
66
+ end
67
+
68
+ ## deploy step
69
+ # Creates a new salticid host for node, and calls <app>.deploy on it.
70
+ def self.run_salticid_task(application)
71
+ host = SmallWonder.salticid.host application.node_name
72
+ host.on_log do |message|
73
+ begin
74
+ level = {'stderr' => 'fatal', nil => 'info'}
75
+ level.default = 'info'
76
+ severity = message.severity || 'info'
77
+ SmallWonder::Log.send(level[message.severity], message.text)
78
+ rescue => e
79
+ p e
80
+ end
81
+ end
82
+
83
+ host.application = application
84
+
85
+ host.user SmallWonder::Config.ssh_user
86
+ @sudo_password = ::HighLine.ask("Your sudo password please: ") { |q| q.echo = false }
87
+ host.password = @sudo_password
88
+
89
+ # sub hyphens for underscores to work around having hyphens in method names
90
+
91
+ host.role application.application_name.gsub("-", "_")
92
+
93
+ host.send(application.application_name.gsub("-", "_")).deploy
94
+
95
+ # set the application status to final since the deploy is done
96
+ application.status = "final"
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,30 @@
1
+ module SmallWonder
2
+ require 'colorize'
3
+
4
+ class Log
5
+ COLORS = {
6
+ 'FATAL' => :light_red,
7
+ 'ERROR' => :light_red,
8
+ 'WARN' => :light_yellow,
9
+ 'INFO' => nil,
10
+ 'DEBUG' => :light_blue
11
+ }
12
+
13
+ class Formatter < Mixlib::Log::Formatter
14
+ def call(severity, time, progname, msg)
15
+ str = format("[%s] %s\n",
16
+ time.strftime("%H:%M:%S"),
17
+ msg).colorize(COLORS[severity])
18
+ end
19
+ end
20
+
21
+ extend Mixlib::Log
22
+
23
+ def self.init(*opts)
24
+ super *opts
25
+
26
+ @logger.formatter = Formatter.new() if @logger.respond_to?(:formatter=)
27
+ @logger
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ class ::Salticid::Host
2
+ def application
3
+ @application
4
+ end
5
+
6
+ def application=(app)
7
+ @application = app
8
+ end
9
+ end
10
+
11
+ class Salticid::Task
12
+ alias_method :stock_run, :run
13
+
14
+ def run(context = nil, *args)
15
+ if context.respond_to? :application
16
+ context.application.status = name
17
+ end
18
+
19
+ SmallWonder::Log.info("Running task: #{name}")
20
+ stock_run(context, *args)
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ module SmallWonder
2
+ class Utils
3
+
4
+ def self.consume_config_file(cli, file)
5
+ if File.exists?(file)
6
+ SmallWonder::Config.from_file(file)
7
+ SmallWonder::Config.merge!(cli.config)
8
+
9
+ SmallWonder::Log.level(SmallWonder::Config.log_level)
10
+ else
11
+ SmallWonder::Log.error("#{file} doesn't exist!")
12
+ exit(1)
13
+ end
14
+ end
15
+
16
+ def self.write_node_data_file(node)
17
+ data = Chef::Node.load(node)
18
+
19
+ file = "#{SmallWonder::Config.chef_repo_path}/nodes/#{node}.json"
20
+
21
+ SmallWonder::Log.info("Writing node file (#{file}).")
22
+
23
+ write_file(JSON.pretty_generate(data), file)
24
+ end
25
+
26
+ def self.write_file(data, path)
27
+ begin
28
+ file = File.new(path,"w")
29
+ file.puts data
30
+ rescue Exception => e
31
+ SmallWonder::Log.error("Error writing file (#{path}).")
32
+ SmallWonder::Log.error(e)
33
+ exit(1)
34
+ ensure
35
+ file.close
36
+ end
37
+ end
38
+
39
+ def self.sane_working_dir?(dir)
40
+ sane = []
41
+ sane << starts_with?(dir, "/tmp/small_wonder")
42
+
43
+ if dir.include?("..")
44
+ sane << false
45
+ else
46
+ sane << true
47
+ end
48
+
49
+ if sane.include?(false)
50
+ false
51
+ else
52
+ true
53
+ end
54
+ end
55
+
56
+ def self.starts_with?(string, prefix)
57
+ prefix = prefix.to_s
58
+ string[0, prefix.length] == prefix
59
+ end
60
+
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: small_wonder
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Joe Williams
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-07-27 00:00:00 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: mixlib-config
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: mixlib-cli
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ hash: 3
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ type: :runtime
46
+ version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: mixlib-log
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :runtime
60
+ version_requirements: *id003
61
+ - !ruby/object:Gem::Dependency
62
+ name: excon
63
+ prerelease: false
64
+ requirement: &id004 !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ type: :runtime
74
+ version_requirements: *id004
75
+ - !ruby/object:Gem::Dependency
76
+ name: yajl-ruby
77
+ prerelease: false
78
+ requirement: &id005 !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ type: :runtime
88
+ version_requirements: *id005
89
+ - !ruby/object:Gem::Dependency
90
+ name: chef
91
+ prerelease: false
92
+ requirement: &id006 !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ type: :runtime
102
+ version_requirements: *id006
103
+ - !ruby/object:Gem::Dependency
104
+ name: highline
105
+ prerelease: false
106
+ requirement: &id007 !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ hash: 3
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ type: :runtime
116
+ version_requirements: *id007
117
+ - !ruby/object:Gem::Dependency
118
+ name: net-ssh
119
+ prerelease: false
120
+ requirement: &id008 !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ hash: 3
126
+ segments:
127
+ - 0
128
+ version: "0"
129
+ type: :runtime
130
+ version_requirements: *id008
131
+ - !ruby/object:Gem::Dependency
132
+ name: net-scp
133
+ prerelease: false
134
+ requirement: &id009 !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ hash: 3
140
+ segments:
141
+ - 0
142
+ version: "0"
143
+ type: :runtime
144
+ version_requirements: *id009
145
+ - !ruby/object:Gem::Dependency
146
+ name: salticid
147
+ prerelease: false
148
+ requirement: &id010 !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ hash: 3
154
+ segments:
155
+ - 0
156
+ version: "0"
157
+ type: :runtime
158
+ version_requirements: *id010
159
+ description:
160
+ email: j@boundary.com
161
+ executables:
162
+ - small_wonder
163
+ extensions: []
164
+
165
+ extra_rdoc_files:
166
+ - README.md
167
+ files:
168
+ - bin/small_wonder
169
+ - lib/small_wonder/application.rb
170
+ - lib/small_wonder/cli.rb
171
+ - lib/small_wonder/config.rb
172
+ - lib/small_wonder/configuratorator.rb
173
+ - lib/small_wonder/deploy.rb
174
+ - lib/small_wonder/log.rb
175
+ - lib/small_wonder/salticid_monkeypatch.rb
176
+ - lib/small_wonder/utils.rb
177
+ - lib/small_wonder.rb
178
+ - README.md
179
+ homepage: https://github.com/boundary/small_wonder
180
+ licenses: []
181
+
182
+ post_install_message:
183
+ rdoc_options: []
184
+
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ none: false
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ hash: 3
193
+ segments:
194
+ - 0
195
+ version: "0"
196
+ required_rubygems_version: !ruby/object:Gem::Requirement
197
+ none: false
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ hash: 3
202
+ segments:
203
+ - 0
204
+ version: "0"
205
+ requirements: []
206
+
207
+ rubyforge_project:
208
+ rubygems_version: 1.8.24
209
+ signing_key:
210
+ specification_version: 3
211
+ summary: A Deployment Tool
212
+ test_files: []
213
+