small_wonder 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +80 -0
- data/bin/small_wonder +5 -0
- data/lib/small_wonder.rb +87 -0
- data/lib/small_wonder/application.rb +109 -0
- data/lib/small_wonder/cli.rb +64 -0
- data/lib/small_wonder/config.rb +9 -0
- data/lib/small_wonder/configuratorator.rb +88 -0
- data/lib/small_wonder/deploy.rb +100 -0
- data/lib/small_wonder/log.rb +30 -0
- data/lib/small_wonder/salticid_monkeypatch.rb +22 -0
- data/lib/small_wonder/utils.rb +62 -0
- metadata +213 -0
data/README.md
ADDED
@@ -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.
|
data/bin/small_wonder
ADDED
data/lib/small_wonder.rb
ADDED
@@ -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,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
|
+
|