pocketknife 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.yardopts +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +52 -0
- data/LICENSE.txt +23 -0
- data/README.md +115 -0
- data/Rakefile +56 -0
- data/bin/pocketknife +15 -0
- data/lib/pocketknife.rb +252 -0
- data/lib/pocketknife/errors.rb +85 -0
- data/lib/pocketknife/node.rb +355 -0
- data/lib/pocketknife/node_manager.rb +93 -0
- data/lib/pocketknife/version.rb +13 -0
- data/pocketknife.gemspec +82 -0
- data/spec/pocketknife_execution_error_spec.rb +61 -0
- data/spec/pocketknife_node_manager_spec.rb +37 -0
- data/spec/pocketknife_node_spec.rb +299 -0
- data/spec/pocketknife_spec.rb +94 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/libraries.rb +3 -0
- data/spec/support/mkproject.rb +32 -0
- metadata +214 -0
data/.document
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--main README.md --no-private --protected
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "archive-tar-minitar", "~> 0.5.0"
|
4
|
+
gem "rye", "~> 0.9.0"
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem "bluecloth", "~> 2.1.0"
|
8
|
+
gem "rspec", "~> 2.3.0"
|
9
|
+
gem "yard", "~> 0.6.0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.6.0"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
annoy (0.5.6)
|
5
|
+
highline (>= 1.5.0)
|
6
|
+
archive-tar-minitar (0.5.2)
|
7
|
+
bluecloth (2.1.0)
|
8
|
+
diff-lcs (1.1.2)
|
9
|
+
drydock (0.6.9)
|
10
|
+
git (1.2.5)
|
11
|
+
highline (1.6.1)
|
12
|
+
jeweler (1.6.0)
|
13
|
+
bundler (~> 1.0.0)
|
14
|
+
git (>= 1.2.5)
|
15
|
+
rake
|
16
|
+
net-scp (1.0.4)
|
17
|
+
net-ssh (>= 1.99.1)
|
18
|
+
net-ssh (2.1.4)
|
19
|
+
rake (0.8.7)
|
20
|
+
rcov (0.9.9)
|
21
|
+
rspec (2.3.0)
|
22
|
+
rspec-core (~> 2.3.0)
|
23
|
+
rspec-expectations (~> 2.3.0)
|
24
|
+
rspec-mocks (~> 2.3.0)
|
25
|
+
rspec-core (2.3.1)
|
26
|
+
rspec-expectations (2.3.0)
|
27
|
+
diff-lcs (~> 1.1.2)
|
28
|
+
rspec-mocks (2.3.0)
|
29
|
+
rye (0.9.4)
|
30
|
+
annoy
|
31
|
+
highline (>= 1.5.1)
|
32
|
+
net-scp (>= 1.0.2)
|
33
|
+
net-ssh (>= 2.0.13)
|
34
|
+
sysinfo (>= 0.7.3)
|
35
|
+
storable (0.8.7)
|
36
|
+
sysinfo (0.7.3)
|
37
|
+
drydock
|
38
|
+
storable
|
39
|
+
yard (0.6.8)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
archive-tar-minitar (~> 0.5.0)
|
46
|
+
bluecloth (~> 2.1.0)
|
47
|
+
bundler (~> 1.0.0)
|
48
|
+
jeweler (~> 1.6.0)
|
49
|
+
rcov
|
50
|
+
rspec (~> 2.3.0)
|
51
|
+
rye (~> 0.9.0)
|
52
|
+
yard (~> 0.6.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
http://www.opensource.org/licenses/mit-license.php
|
2
|
+
|
3
|
+
The MIT License
|
4
|
+
|
5
|
+
Copyright (c) 2011 Igal Koshevoy
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
9
|
+
in the Software without restriction, including without limitation the rights
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
12
|
+
furnished to do so, subject to the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
15
|
+
all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
pocketknife
|
2
|
+
===========
|
3
|
+
|
4
|
+
`pocketknife` is a devops tool for managing computers running `chef-solo`, powered by [Opscode Chef](http://www.opscode.com/chef/).
|
5
|
+
|
6
|
+
Using `pocketknife`, you create a project that describes the configuration of your computers and then deploy it to bring them to their intended state.
|
7
|
+
|
8
|
+
With `pocketknife`, you don't need to setup or manage a specialized `chef-server` node or rely on an unreliable network connection to a distant hosted service whose security you don't control, deal with managing `chef`'s security keys, or deal with manually synchronizing data with the `chef-server` datastore.
|
9
|
+
|
10
|
+
With `pocketknife`, all of your cookbooks, roles and nodes are stored in easy-to-use files that you can edit, share, backup and version control with tools you already have.
|
11
|
+
|
12
|
+
Comparisons
|
13
|
+
-----------
|
14
|
+
|
15
|
+
Why create another tool?
|
16
|
+
|
17
|
+
* `knife` is included with `chef` and is primarily used for managing client-server nodes. The `pocketknife` name plays off this by virtue that it's a smaller, more personal way of managing nodes.
|
18
|
+
* `chef-client` is included with `chef`, but you typically need to install another node to act as a `chef-server`, which takes more resources and effort. Using `chef` in client-server mode provides benefits like network-wide databags and pull-based updates, but if you can live without these, `pocketknife` can save you a lot of effort.
|
19
|
+
* `chef-solo` is included as part of `chef`, and `pocketknife` uses it. However, `chef-solo` is a low-level tool, and creating and deploying all the files it needs is a significant chore. It also provides no way of deploying or managing your shared and node-specific configuration files. `pocketknife` provides all the missing functionality for creating, managing and deploying, so you don't have to use `chef-solo` directly.
|
20
|
+
* `littlechef` is the inspiration for `pocketknife`, it's a great project that I've contributed to and you should definitely [evaluate it](https://github.com/tobami/littlechef). I feel that `pocketknife` offers a more robust, repeatable and automated mechanism for deploying remote nodes; has better documentation, default behavior and command-line support; has good tests and a clearer, more maintainable design; and is written in Ruby so you use the same stack as `chef`.
|
21
|
+
|
22
|
+
Usage
|
23
|
+
-----
|
24
|
+
|
25
|
+
Install the software on the machine you'll be running `pocketknife` on, this is a computer that will deploy configurations to other computers:
|
26
|
+
|
27
|
+
* Install Ruby: http://www.ruby-lang.org/
|
28
|
+
* Install Rubygems: http://rubygems.org/
|
29
|
+
* Install `pocketknife`: `gem install pocketknife`
|
30
|
+
|
31
|
+
Create a new *project*, a special directory that will contain your configuration files. For example, create the `swa` project directory by running:
|
32
|
+
|
33
|
+
pocketknife --create swa
|
34
|
+
|
35
|
+
Go into your new *project* directory:
|
36
|
+
|
37
|
+
cd swa
|
38
|
+
|
39
|
+
Create cookbooks in the `cookbooks` directory that describe how your computers should be configured. These are standard `chef` cookbooks, like the [opscode/cookbooks](https://github.com/opscode/cookbooks). For example, download a copy of [opscode/cookbooks/ntp](https://github.com/opscode/cookbooks/tree/master/ntp) as `cookbooks/ntp`.
|
40
|
+
|
41
|
+
Override cookbooks in the `site-cookbooks` directory. This has the same structure as `cookbooks`, but any files you put here will override the contents of `cookbooks`. This is useful for storing the original code of a third-party cookbook in `cookbooks` and putting your customizations in `site-cookbooks`.
|
42
|
+
|
43
|
+
Optionally define roles in the `roles` directory that describe common behavior and attributes of your computers using JSON syntax using [chef's documentation](http://wiki.opscode.com/display/chef/Roles#Roles-AsJSON). For example, define a role called `ntp_client` by creating a file called `roles/ntp_client.json` with this content:
|
44
|
+
|
45
|
+
{
|
46
|
+
"name": "ntp_client",
|
47
|
+
"chef_type": "role",
|
48
|
+
"json_class": "Chef::Role",
|
49
|
+
"run_list": [
|
50
|
+
"recipe[ntp]"
|
51
|
+
],
|
52
|
+
"override_attributes": {
|
53
|
+
"ntp": {
|
54
|
+
"servers": ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", "3.pool.ntp.org"]
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
Define a new node using the `chef` JSON syntax for [runlist](http://wiki.opscode.com/display/chef/Setting+the+run_list+in+JSON+during+run+time) and [attributes](http://wiki.opscode.com/display/chef/Attributes). For example, to define a node with the hostname `henrietta.swa.gov.it` create the `nodes/henrietta.swa.gov.it.json` file, and add the contents below so it uses the `ntp_client` role and overrides its attributes to use a local NTP server:
|
60
|
+
|
61
|
+
{
|
62
|
+
"run_list": [
|
63
|
+
"role[ntp_client]"
|
64
|
+
],
|
65
|
+
"override_attributes": {
|
66
|
+
"ntp": {
|
67
|
+
"servers": ["0.it.pool.ntp.org", "1.it.pool.ntp.org", "2.it.pool.ntp.org", "3.it.pool.ntp.org"]
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
Operations on remote nodes will be performed using SSH. You should consider [configuring ssh-agent](http://mah.everybody.org/docs/ssh) so you don't have to keep typing in your passwords.
|
73
|
+
|
74
|
+
Finally, deploy your configuration to the remote machine and see the results. For example, lets deploy the above configuration to the `henrietta.swa.gov.it` host, which can be abbreviated as `henrietta` when calling `pocketknife`:
|
75
|
+
|
76
|
+
pocketknife henrietta
|
77
|
+
|
78
|
+
When deploying a configuration to a node, `pocketknife` will check whether Chef and its dependencies are installed. It something is missing, it will prompt you for whether you'd like to have it install them automatically.
|
79
|
+
|
80
|
+
To always install Chef and its dependencies when they're needed, without prompts, use the `-i` option, e.g. `pocketknife -i henrietta`. Or to never install Chef and its dependencies, use the `-I` option, which will cause the program to quit with an error rather than prompting if Chef or its dependencies aren't installed.
|
81
|
+
|
82
|
+
If something goes wrong while deploying the configuration, you can display verbose logging from `pocketknife` and Chef by using the `-v` option. For example, deploy the configuration to `henrietta` with verbose logging:
|
83
|
+
|
84
|
+
pocketknife -v henrietta
|
85
|
+
|
86
|
+
If you really need to debug on the remote machine, you may be interested about some of the commands and paths:
|
87
|
+
|
88
|
+
* `chef-solo-apply` (/usr/local/sbin/chef-solo-apply) will apply the configuration to the machine. You can specify `-l debug` to make it more verbose. Run it with `-h` for help.
|
89
|
+
* `csa` (/usr/local/sbin/csa) is a shortcut for `chef-solo-apply` and accepts the same arguments.
|
90
|
+
* `/etc/chef/solo.rb` contains the `chef-solo` configuration settings.
|
91
|
+
* `/etc/chef/node.json` contains the node-specific configuration, like the `runlist` and attributes.
|
92
|
+
* `/var/local/pocketknife` contains the `cookbooks`, `site-cookbooks` and `roles` describing your configuration.
|
93
|
+
|
94
|
+
Contributing
|
95
|
+
------------
|
96
|
+
|
97
|
+
This software is published as open source at https://github.com/igal/pocketknife
|
98
|
+
|
99
|
+
You can view and file issues for this software at https://github.com/igal/pocketknife/issues
|
100
|
+
|
101
|
+
If you'd like to contribute code or documentation:
|
102
|
+
|
103
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
104
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
105
|
+
* Fork the project
|
106
|
+
* Start a feature/bugfix branch
|
107
|
+
* Commit and push until you are happy with your contribution
|
108
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
109
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
110
|
+
* Submit a pull request using github, this makes it easy for me to incorporate your code.
|
111
|
+
|
112
|
+
Copyright
|
113
|
+
---------
|
114
|
+
|
115
|
+
Copyright (c) 2011 Igal Koshevoy. See `LICENSE.txt` for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
load './lib/pocketknife/version.rb'
|
15
|
+
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |gem|
|
18
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
19
|
+
gem.version = Pocketknife::Version::STRING
|
20
|
+
gem.name = "pocketknife"
|
21
|
+
gem.homepage = "http://github.com/igal/pocketknife"
|
22
|
+
gem.license = "MIT"
|
23
|
+
gem.summary = %Q{pocketknife is a tool for managing chef-solo nodes.}
|
24
|
+
gem.description = %Q{pocketknife is a tool for managing chef-solo nodes.}
|
25
|
+
gem.email = "igal+pocketknife@pragmaticraft.com"
|
26
|
+
gem.authors = ["Igal Koshevoy"]
|
27
|
+
gem.executables += %w[
|
28
|
+
pocketknife
|
29
|
+
]
|
30
|
+
gem.files += %w[
|
31
|
+
Gemfile
|
32
|
+
LICENSE.txt
|
33
|
+
README.md
|
34
|
+
Rakefile
|
35
|
+
lib/*
|
36
|
+
spec/*
|
37
|
+
]
|
38
|
+
# dependencies defined in Gemfile
|
39
|
+
end
|
40
|
+
Jeweler::RubygemsDotOrgTasks.new
|
41
|
+
|
42
|
+
require 'rspec/core'
|
43
|
+
require 'rspec/core/rake_task'
|
44
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
45
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
46
|
+
end
|
47
|
+
|
48
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
49
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
50
|
+
spec.rcov = true
|
51
|
+
end
|
52
|
+
|
53
|
+
task :default => :spec
|
54
|
+
|
55
|
+
require 'yard'
|
56
|
+
YARD::Rake::YardocTask.new
|
data/bin/pocketknife
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
# Use local files if executed from a source code checkout directory, useful for development.
|
7
|
+
lib = Pathname.new($0).expand_path.dirname + '..' + 'lib' + 'pocketknife.rb'
|
8
|
+
if lib.exist?
|
9
|
+
require 'rubygems'
|
10
|
+
$LOAD_PATH.unshift(lib.dirname)
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'pocketknife'
|
14
|
+
|
15
|
+
Pocketknife::cli(ARGV)
|
data/lib/pocketknife.rb
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
# Standard libraries
|
2
|
+
require "pathname"
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
# Gem libraries
|
6
|
+
require "archive/tar/minitar"
|
7
|
+
require "rye"
|
8
|
+
|
9
|
+
# Related libraries
|
10
|
+
require "pocketknife/errors"
|
11
|
+
require "pocketknife/node"
|
12
|
+
require "pocketknife/node_manager"
|
13
|
+
require "pocketknife/version"
|
14
|
+
|
15
|
+
# = Pocketknife
|
16
|
+
#
|
17
|
+
# == About
|
18
|
+
#
|
19
|
+
# Pocketknife is a devops tool for managing computers running <tt>chef-solo</tt>. Using Pocketknife, you create a project that describes the configuration of your computers and then apply it to bring them to the intended state.
|
20
|
+
#
|
21
|
+
# For information on using the +pocketknife+ tool, please see the {file:README.md README.md} file. The rest of this documentation is intended for those writing code using the Pocketknife API.
|
22
|
+
#
|
23
|
+
# == Important methods
|
24
|
+
#
|
25
|
+
# * {cli} runs the command-line interpreter, whichi in turn executes the methods below.
|
26
|
+
# * {#initialize} creates a new Pocketknife instance.
|
27
|
+
# * {#create} creates a new project.
|
28
|
+
# * {#deploy} deploys configurations to nodes, which uploads and applies.
|
29
|
+
# * {#upload} uploads configurations to nodes.
|
30
|
+
# * {#apply} applies existing configurations to nodes.
|
31
|
+
# * {#node} finds a node to upload or apply configurations.
|
32
|
+
#
|
33
|
+
# == Important classes
|
34
|
+
#
|
35
|
+
# * {Pocketknife::Node} describes how to upload and apply configurations to nodes, which are remote computers.
|
36
|
+
# * {Pocketknife::NodeManager} finds, checks and manages nodes.
|
37
|
+
# * {Pocketknife::NodeError} describes errors encountered when using nodes.
|
38
|
+
class Pocketknife
|
39
|
+
# Runs the interpreter using arguments provided by the command-line. Run <tt>pocketknife -h</tt> or review the code below to see what command-line arguments are accepted.
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
# # Display command-line help:
|
43
|
+
# Pocketknife.cli('-h')
|
44
|
+
#
|
45
|
+
# @param [Array<String>] args A list of arguments from the command-line, which may include options (e.g. <tt>-h</tt>).
|
46
|
+
def self.cli(args)
|
47
|
+
pocketknife = Pocketknife.new
|
48
|
+
|
49
|
+
OptionParser.new do |parser|
|
50
|
+
parser.banner = <<-HERE
|
51
|
+
USAGE: pocketknife [options] [nodes]
|
52
|
+
|
53
|
+
EXAMPLES:
|
54
|
+
# Create a new project called PROJECT
|
55
|
+
pocketknife -c PROJECT
|
56
|
+
|
57
|
+
# Apply configuration to a node called NODE
|
58
|
+
pocketknife NODE
|
59
|
+
|
60
|
+
OPTIONS:
|
61
|
+
HERE
|
62
|
+
|
63
|
+
options = {}
|
64
|
+
|
65
|
+
parser.on("-c", "--create PROJECT", "Create project") do |name|
|
66
|
+
pocketknife.create(name)
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
parser.on("-V", "--version", "Display version number") do |name|
|
71
|
+
puts "Pocketknife #{Pocketknife::Version::STRING}"
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
parser.on("-v", "--verbose", "Display detailed status information") do |name|
|
76
|
+
pocketknife.verbosity = true
|
77
|
+
end
|
78
|
+
|
79
|
+
parser.on("-q", "--quiet", "Display minimal status information") do |v|
|
80
|
+
pocketknife.verbosity = false
|
81
|
+
end
|
82
|
+
|
83
|
+
parser.on("-u", "--upload", "Upload configuration, but don't apply it") do |v|
|
84
|
+
options[:upload] = true
|
85
|
+
end
|
86
|
+
|
87
|
+
parser.on("-a", "--apply", "Runs cheef to apply already-uploaded configuration") do |v|
|
88
|
+
options[:apply] = true
|
89
|
+
end
|
90
|
+
|
91
|
+
parser.on("-i", "--install", "Install Chef automatically") do |v|
|
92
|
+
pocketknife.can_install = true
|
93
|
+
end
|
94
|
+
|
95
|
+
parser.on("-I", "--noinstall", "Don't install Chef automatically") do |v|
|
96
|
+
pocketknife.can_install = false
|
97
|
+
end
|
98
|
+
|
99
|
+
begin
|
100
|
+
arguments = parser.parse!
|
101
|
+
rescue OptionParser::MissingArgument => e
|
102
|
+
puts parser
|
103
|
+
puts
|
104
|
+
puts "ERROR: #{e}"
|
105
|
+
exit -1
|
106
|
+
end
|
107
|
+
|
108
|
+
nodes = arguments
|
109
|
+
|
110
|
+
if nodes.empty?
|
111
|
+
puts parser
|
112
|
+
puts
|
113
|
+
puts "ERROR: No nodes specified."
|
114
|
+
exit -1
|
115
|
+
end
|
116
|
+
|
117
|
+
begin
|
118
|
+
if options[:upload]
|
119
|
+
pocketknife.upload(nodes)
|
120
|
+
end
|
121
|
+
|
122
|
+
if options[:apply]
|
123
|
+
pocketknife.apply(nodes)
|
124
|
+
end
|
125
|
+
|
126
|
+
if not options[:upload] and not options[:apply]
|
127
|
+
pocketknife.deploy(nodes)
|
128
|
+
end
|
129
|
+
rescue NodeError => e
|
130
|
+
puts "! #{e.node}: #{e}"
|
131
|
+
exit -1
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the software's version.
|
137
|
+
#
|
138
|
+
# @return [String] A version string.
|
139
|
+
def self.version
|
140
|
+
return "0.0.1"
|
141
|
+
end
|
142
|
+
|
143
|
+
# Amount of detail to display? true means verbose, nil means normal, false means quiet.
|
144
|
+
attr_accessor :verbosity
|
145
|
+
|
146
|
+
# Can chef and its dependencies be installed automatically if not found? true means perform installation without prompting, false means quit if chef isn't available, and nil means prompt the user for input.
|
147
|
+
attr_accessor :can_install
|
148
|
+
|
149
|
+
# {Pocketknife::NodeManager} instance.
|
150
|
+
attr_accessor :node_manager
|
151
|
+
|
152
|
+
# Instantiate a new Pocketknife.
|
153
|
+
#
|
154
|
+
# @option [Boolean] verbosity Amount of detail to display. +true+ means verbose, +nil+ means normal, +false+ means quiet.
|
155
|
+
# @option [Boolean] install Install Chef and its dependencies if needed? +true+ means do so automatically, +false+ means don't, and +nil+ means display a prompt to ask the user what to do.
|
156
|
+
def initialize(opts={})
|
157
|
+
self.verbosity = opts[:verbosity]
|
158
|
+
self.can_install = opts[:install]
|
159
|
+
|
160
|
+
self.node_manager = NodeManager.new(self)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Display a message, but only if it's important enough
|
164
|
+
#
|
165
|
+
# @param [String] message The message to display.
|
166
|
+
# @param [Boolean] importance How important is this? +true+ means important, +nil+ means normal, +false+ means unimportant.
|
167
|
+
def say(message, importance=nil)
|
168
|
+
display = \
|
169
|
+
case self.verbosity
|
170
|
+
when true
|
171
|
+
true
|
172
|
+
when nil
|
173
|
+
importance != false
|
174
|
+
else
|
175
|
+
importance == true
|
176
|
+
end
|
177
|
+
|
178
|
+
if display
|
179
|
+
puts message
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Creates a new project directory.
|
184
|
+
#
|
185
|
+
# @param [String] project The name of the project directory to create.
|
186
|
+
# @yield [path] Yields status information to the optionally supplied block.
|
187
|
+
# @yieldparam [String] path The path of the file or directory created.
|
188
|
+
def create(project)
|
189
|
+
self.say("* Creating project in directory: #{project}")
|
190
|
+
|
191
|
+
dir = Pathname.new(project)
|
192
|
+
|
193
|
+
%w[
|
194
|
+
nodes
|
195
|
+
roles
|
196
|
+
cookbooks
|
197
|
+
site-cookbooks
|
198
|
+
].each do |subdir|
|
199
|
+
target = (dir + subdir)
|
200
|
+
unless target.exist?
|
201
|
+
FileUtils.mkdir_p(target)
|
202
|
+
self.say("- #{target}/")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
return true
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns a Node instance.
|
210
|
+
#
|
211
|
+
# @param[String] name The name of the node.
|
212
|
+
def node(name)
|
213
|
+
return node_manager.find(name)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Deploys configuration to the nodes, calls {#upload} and {#apply}.
|
217
|
+
#
|
218
|
+
# @params[Array<String>] nodes A list of node names.
|
219
|
+
def deploy(nodes)
|
220
|
+
node_manager.assert_known(nodes)
|
221
|
+
|
222
|
+
Node.prepare_upload do
|
223
|
+
for node in nodes
|
224
|
+
node_manager.find(node).deploy
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Uploads configuration information to remote nodes.
|
230
|
+
#
|
231
|
+
# @param [Array<String>] nodes A list of node names.
|
232
|
+
def upload(nodes)
|
233
|
+
node_manager.assert_known(nodes)
|
234
|
+
|
235
|
+
Node.prepare_upload do
|
236
|
+
for node in nodes
|
237
|
+
node_manager.find(node).upload
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Applies configurations to remote nodes.
|
243
|
+
#
|
244
|
+
# @param [Array<String>] nodes A list of node names.
|
245
|
+
def apply(nodes)
|
246
|
+
node_manager.assert_known(nodes)
|
247
|
+
|
248
|
+
for node in nodes
|
249
|
+
node_manager.find(node).apply
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|