Class: Pocketknife::Node
- Inherits:
-
Object
- Object
- Pocketknife::Node
- Defined in:
- lib/pocketknife/node.rb
Overview
Node
A node represents a remote computer that will be managed with Pocketknife and chef-solo
. It can connect to a node, execute commands on it, install the stack, and upload and apply configurations to it.
Instance Attribute Summary collapse
-
#connection_cache ⇒ Object
Instance of Rye::Box connection, cached by #connection.
-
#name ⇒ Object
String name of the node.
-
#platform_cache ⇒ Object
Hash with information about platform, cached by #platform.
-
#pocketknife ⇒ Object
Instance of a Pocketknife.
Class Method Summary collapse
-
.cleanup_upload ⇒ Object
Cleans up cache of shared files uploaded to all nodes.
-
.prepare_upload { ... } ⇒ Object
Prepares an upload, by creating a cache of shared files used by all nodes.
Instance Method Summary collapse
-
#apply ⇒ Object
Applies the configuration to the node.
-
#connection ⇒ Object
Returns a Rye::Box connection.
- #deploy ⇒ Object
-
#execute(commands, immediate = false) ⇒ Rye::Rap
Executes commands on the external node.
-
#has_executable?(executable) ⇒ Boolean
Does this node have the given executable?.
-
#initialize(name, pocketknife) ⇒ Node
constructor
Initialize a new node.
-
#install ⇒ Object
Installs Chef and its dependencies on a node if needed.
-
#install_chef ⇒ Object
Installs Chef on the remote node.
-
#install_ruby ⇒ Object
Installs Ruby on the remote node.
-
#install_rubygems ⇒ Object
Installs Rubygems on the remote node.
-
#local_node_json_pathname ⇒ Pathname
Returns path to this node’s
nodes/NAME.json
file, used asnode.json
bychef-solo
. -
#platform ⇒ Hash<String, Object] Return a hash describing the node, see above.
Returns information describing the node.
-
#say(message, importance = nil) ⇒ Object
Displays status message.
-
#upload ⇒ Object
Uploads configuration information to node.
Constructor Details
#initialize(name, pocketknife) ⇒ Node
Initialize a new node.
22 23 24 25 26 |
# File 'lib/pocketknife/node.rb', line 22 def initialize(name, pocketknife) self.name = name self.pocketknife = pocketknife self.connection_cache = nil end |
Instance Attribute Details
#connection_cache ⇒ Object
Instance of Rye::Box connection, cached by #connection.
13 14 15 |
# File 'lib/pocketknife/node.rb', line 13 def connection_cache @connection_cache end |
#name ⇒ Object
String name of the node.
7 8 9 |
# File 'lib/pocketknife/node.rb', line 7 def name @name end |
#platform_cache ⇒ Object
Hash with information about platform, cached by #platform.
16 17 18 |
# File 'lib/pocketknife/node.rb', line 16 def platform_cache @platform_cache end |
#pocketknife ⇒ Object
Instance of a Pocketknife.
10 11 12 |
# File 'lib/pocketknife/node.rb', line 10 def pocketknife @pocketknife end |
Class Method Details
.cleanup_upload ⇒ Object
Cleans up cache of shared files uploaded to all nodes. This cache is created by the prepare_upload method.
220 221 222 223 224 225 226 227 228 |
# File 'lib/pocketknife/node.rb', line 220 def self.cleanup_upload [ TMP_TARBALL, TMP_SOLO_RB, TMP_CHEF_SOLO_APPLY ].each do |path| path.unlink if path.exist? end end |
.prepare_upload { ... } ⇒ Object
Prepares an upload, by creating a cache of shared files used by all nodes.
IMPORTANT: This will create files and leave them behind. You should use the block syntax or manually call cleanup_upload when done.
If an optional block is supplied, calls cleanup_upload automatically when done. This is typically used like:
Node.prepare_upload do
mynode.upload
end
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/pocketknife/node.rb', line 188 def self.prepare_upload(&block) begin # TODO either do this in memory or scope this to the PID to allow concurrency TMP_SOLO_RB.open("w") {|h| h.write(SOLO_RB_CONTENT)} TMP_CHEF_SOLO_APPLY.open("w") {|h| h.write(CHEF_SOLO_APPLY_CONTENT)} TMP_TARBALL.open("w") do |handle| Archive::Tar::Minitar.pack( [ VAR_POCKETKNIFE_COOKBOOKS.basename.to_s, VAR_POCKETKNIFE_SITE_COOKBOOKS.basename.to_s, VAR_POCKETKNIFE_ROLES.basename.to_s, TMP_SOLO_RB.to_s, TMP_CHEF_SOLO_APPLY.to_s ], handle ) end rescue Exception => e cleanup_upload raise e end if block begin yield(self) ensure cleanup_upload end end end |
Instance Method Details
#apply ⇒ Object
Applies the configuration to the node. Installs Chef, Ruby and Rubygems if needed.
265 266 267 268 269 270 271 272 273 |
# File 'lib/pocketknife/node.rb', line 265 def apply self.install self.say("Applying configuration...", true) command = "chef-solo -j #{NODE_JSON}" command << " -l debug" if self.pocketknife.verbosity == true self.execute(command, true) self.say("Finished applying!") end |
#connection ⇒ Object
Returns a Rye::Box connection.
Caches result to #connection_cache.
31 32 33 34 35 36 37 |
# File 'lib/pocketknife/node.rb', line 31 def connection return self.connection_cache ||= begin rye = Rye::Box.new(self.name, :user => "root") rye.disable_safe_mode rye end end |
#deploy ⇒ Object
276 277 278 279 |
# File 'lib/pocketknife/node.rb', line 276 def deploy self.upload self.apply end |
#execute(commands, immediate = false) ⇒ Rye::Rap
Executes commands on the external node.
287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/pocketknife/node.rb', line 287 def execute(commands, immediate=false) self.say("Executing:\n#{commands}", false) if immediate self.connection.stdout_hook {|line| puts line} end return self.connection.execute("(#{commands}) 2>&1") rescue Rye::Err => e raise Pocketknife::ExecutionError.new(self.name, commands, e, immediate) ensure self.connection.stdout_hook = nil end |
#has_executable?(executable) ⇒ Boolean
Does this node have the given executable?
58 59 60 61 62 63 64 65 |
# File 'lib/pocketknife/node.rb', line 58 def has_executable?(executable) begin self.connection.execute(%{which "#{executable}" && test -x `which "#{executable}"`}) return true rescue Rye::Err return false end end |
#install ⇒ Object
Installs Chef and its dependencies on a node if needed.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/pocketknife/node.rb', line 105 def install unless self.has_executable?("chef-solo") case self.pocketknife.can_install when nil # Prompt for installation print "? #{self.name}: Chef not found. Install it and its dependencies? (Y/n) " STDOUT.flush answer = STDIN.gets.chomp case answer when /^y/i, '' # Continue with install else raise NotInstalling.new("Chef isn't installed on node '#{self.name}', but user doesn't want to install it.", self.name) end when true # User wanted us to install else # Don't install raise NotInstalling.new("Chef isn't installed on node '#{self.name}', but user doesn't want to install it.", self.name) end unless self.has_executable?("ruby") self.install_ruby end unless self.has_executable?("gem") self.install_rubygems end self.install_chef end end |
#install_chef ⇒ Object
Installs Chef on the remote node.
139 140 141 142 143 |
# File 'lib/pocketknife/node.rb', line 139 def install_chef self.say("Installing chef...") self.execute("gem install --no-rdoc --no-ri chef", true) self.say("Installed chef", false) end |
#install_ruby ⇒ Object
Installs Ruby on the remote node.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/pocketknife/node.rb', line 161 def install_ruby command = \ case self.platform[:distributor].downcase when /ubuntu/, /debian/, /gnu\/linux/ "DEBIAN_FRONTEND=noninteractive apt-get --yes install ruby ruby-dev libopenssl-ruby irb build-essential wget ssl-cert" when /centos/, /red hat/, /scientific linux/ "yum -y install ruby ruby-shadow gcc gcc-c++ ruby-devel wget" else raise UnsupportedInstallationPlatform.new("Can't install on node '#{self.name}' with unknown distrubtor: `#{self.platform[:distrubtor]}`", self.name) end self.say("Installing ruby...") self.execute(command, true) self.say("Installed ruby", false) end |
#install_rubygems ⇒ Object
Installs Rubygems on the remote node.
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/pocketknife/node.rb', line 146 def install_rubygems self.say("Installing rubygems...") self.execute(<<-HERE, true) cd /root && rm -rf rubygems-1.3.7 rubygems-1.3.7.tgz && wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz && tar zxf rubygems-1.3.7.tgz && cd rubygems-1.3.7 && ruby setup.rb --no-format-executable && rm -rf rubygems-1.3.7 rubygems-1.3.7.tgz HERE self.say("Installed rubygems", false) end |
#local_node_json_pathname ⇒ Pathname
Returns path to this node’s nodes/NAME.json
file, used as node.json
by chef-solo
.
50 51 52 |
# File 'lib/pocketknife/node.rb', line 50 def local_node_json_pathname return Pathname.new("nodes") + "#{self.name}.json" end |
#platform ⇒ Hash<String, Object] Return a hash describing the node, see above.
Returns information describing the node.
The information is formatted similar to this:
{
:distributor=>"Ubuntu", # String with distributor name
:codename=>"maverick", # String with release codename
:release=>"10.10", # String with release number
:version=>10.1 # Float with release number
}
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/pocketknife/node.rb', line 79 def platform return self.platform_cache ||= begin lsb_release = "/etc/lsb-release" begin output = self.connection.cat(lsb_release).to_s result = {} result[:distributor] = output[/DISTRIB_ID\s*=\s*(.+?)$/, 1] result[:release] = output[/DISTRIB_RELEASE\s*=\s*(.+?)$/, 1] result[:codename] = output[/DISTRIB_CODENAME\s*=\s*(.+?)$/, 1] result[:version] = result[:release].to_f if result[:distributor] && result[:release] && result[:codename] && result[:version] return result else raise UnsupportedInstallationPlatform.new("Can't install on node '#{self.name}' with invalid '#{lsb_release}' file", self.name) end rescue Rye::Err raise UnsupportedInstallationPlatform.new("Can't install on node '#{self.name}' without '#{lsb_release}'", self.name) end end end |
#say(message, importance = nil) ⇒ Object
Displays status message.
43 44 45 |
# File 'lib/pocketknife/node.rb', line 43 def say(, importance=nil) self.pocketknife.say("* #{self.name}: #{}", importance) end |
#upload ⇒ Object
Uploads configuration information to node.
IMPORTANT: You must first call prepare_upload to create the shared files that will be uploaded.
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/pocketknife/node.rb', line 233 def upload self.say("Uploading configuration...") self.say("Removing old files...", false) self.execute <<-HERE umask 0377 && rm -rf "#{ETC_CHEF}" "#{VAR_POCKETKNIFE}" "#{VAR_POCKETKNIFE_CACHE}" "#{CHEF_SOLO_APPLY}" "#{CHEF_SOLO_APPLY_ALIAS}" && mkdir -p "#{ETC_CHEF}" "#{VAR_POCKETKNIFE}" "#{VAR_POCKETKNIFE_CACHE}" "#{CHEF_SOLO_APPLY.dirname}" HERE self.say("Uploading new files...", false) self.connection.file_upload(self.local_node_json_pathname.to_s, NODE_JSON.to_s) self.connection.file_upload(TMP_TARBALL.to_s, VAR_POCKETKNIFE_TARBALL.to_s) self.say("Installing new files...", false) self.execute <<-HERE, true cd "#{VAR_POCKETKNIFE_CACHE}" && tar xf "#{VAR_POCKETKNIFE_TARBALL}" && chmod -R u+rwX,go= . && chown -R root:root . && mv "#{TMP_SOLO_RB}" "#{SOLO_RB}" && mv "#{TMP_CHEF_SOLO_APPLY}" "#{CHEF_SOLO_APPLY}" && chmod u+x "#{CHEF_SOLO_APPLY}" && ln -s "#{CHEF_SOLO_APPLY.basename}" "#{CHEF_SOLO_APPLY_ALIAS}" && rm "#{VAR_POCKETKNIFE_TARBALL}" && mv * "#{VAR_POCKETKNIFE}" HERE self.say("Finished uploading!", false) end |