pocketknife 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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)
@@ -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