Class: Chef::Node

Inherits:
Object
  • Object
show all
Includes:
IndexQueue::Indexable, Mixin::CheckHelper, Mixin::FromFile, Mixin::LanguageIncludeAttribute, Mixin::ParamsValidate
Defined in:
lib/chef/node.rb,
lib/chef/node/attribute.rb

Defined Under Namespace

Classes: Attribute

Constant Summary collapse

DESIGN_DOCUMENT =
{
  "version" => 9,
  "language" => "javascript",
  "views" => {
    "all" => {
      "map" => <<-EOJS
      function(doc) {
        if (doc.chef_type == "node") {
          emit(doc.name, doc);
        }
      }
      EOJS
    },
    "all_id" => {
      "map" => <<-EOJS
      function(doc) {
        if (doc.chef_type == "node") {
          emit(doc.name, doc.name);
        }
      }
      EOJS
    },
    "status" => {
      "map" => <<-EOJS
        function(doc) {
          if (doc.chef_type == "node") {
            var to_emit = { "name": doc.name };
            if (doc["attributes"]["fqdn"]) {
              to_emit["fqdn"] = doc["attributes"]["fqdn"];
            } else {
              to_emit["fqdn"] = "Undefined";
            }
            if (doc["attributes"]["ipaddress"]) {
              to_emit["ipaddress"] = doc["attributes"]["ipaddress"];
            } else {
              to_emit["ipaddress"] = "Undefined";
            }
            if (doc["attributes"]["ohai_time"]) {
              to_emit["ohai_time"] = doc["attributes"]["ohai_time"];
            } else {
              to_emit["ohai_time"] = "Undefined";
            }
            if (doc["attributes"]["uptime"]) {
              to_emit["uptime"] = doc["attributes"]["uptime"];
            } else {
              to_emit["uptime"] = "Undefined";
            }
            if (doc["attributes"]["platform"]) {
              to_emit["platform"] = doc["attributes"]["platform"];
            } else {
              to_emit["platform"] = "Undefined";
            }
            if (doc["attributes"]["platform_version"]) {
              to_emit["platform_version"] = doc["attributes"]["platform_version"];
            } else {
              to_emit["platform_version"] = "Undefined";
            }
            if (doc["run_list"]) {
              to_emit["run_list"] = doc["run_list"];
            } else {
              to_emit["run_list"] = "Undefined";
            }
            emit(doc.name, to_emit);
          }
        }
      EOJS
    },
    "by_run_list" => {
      "map" => <<-EOJS
        function(doc) {
          if (doc.chef_type == "node") {
            if (doc['run_list']) {
              for (var i=0; i < doc.run_list.length; i++) {
                emit(doc['run_list'][i], doc.name);
              }
            }
          }
        }
      EOJS
    }
  },
}

Instance Attribute Summary collapse

Attributes included from IndexQueue::Indexable

#index_id

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IndexQueue::Indexable

#add_to_index, #delete_from_index, included, #index_object_type, #with_indexer_metadata

Methods included from Mixin::LanguageIncludeAttribute

#include_attribute

Methods included from Mixin::ParamsValidate

#set_or_return, #validate

Methods included from Mixin::FromFile

#class_from_file, #from_file

Methods included from Mixin::CheckHelper

#set_if_args

Constructor Details

#initialize(couchdb = nil) ⇒ Node

Create a new Chef::Node object.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/chef/node.rb', line 139

def initialize(couchdb=nil)
  @name = nil
  
  @normal_attrs = Mash.new
  @override_attrs = Mash.new
  @default_attrs = Mash.new
  @automatic_attrs = Mash.new
  @run_list = Chef::RunList.new

  @couchdb_rev = nil
  @couchdb_id = nil
  @couchdb = couchdb || Chef::CouchDB.new

  @run_state = {
    :template_cache => Hash.new,
    :seen_recipes => Hash.new,
    :seen_attributes => Hash.new
  }
  # TODO: 5/20/2010 need this here as long as other objects try to access
  # the cookbook collection via Node, otherwise get NoMethodError on nil.
  @cookbook_collection = CookbookCollection.new
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object

Encouraged to only get used for lookups - while you can do sets from here, it’s not as explicit as using the normal/default/override interface.



317
318
319
320
# File 'lib/chef/node.rb', line 317

def method_missing(symbol, *args)
  attrs = construct_attributes
  attrs.send(symbol, *args)
end

Instance Attribute Details

#automatic_attrsObject

Returns the value of attribute automatic_attrs.



41
42
43
# File 'lib/chef/node.rb', line 41

def automatic_attrs
  @automatic_attrs
end

#cookbook_collectionObject

TODO: 5/18/2010 cw/timh. cookbook_collection should be removed from here and for any place it’s needed, it should be accessed through a Chef::RunContext



47
48
49
# File 'lib/chef/node.rb', line 47

def cookbook_collection
  @cookbook_collection
end

#couchdbObject

Returns the value of attribute couchdb.



40
41
42
# File 'lib/chef/node.rb', line 40

def couchdb
  @couchdb
end

#couchdb_idObject

Returns the value of attribute couchdb_id.



42
43
44
# File 'lib/chef/node.rb', line 42

def couchdb_id
  @couchdb_id
end

#couchdb_revObject

Returns the value of attribute couchdb_rev.



40
41
42
# File 'lib/chef/node.rb', line 40

def couchdb_rev
  @couchdb_rev
end

#default_attrsObject

Returns the value of attribute default_attrs.



41
42
43
# File 'lib/chef/node.rb', line 41

def default_attrs
  @default_attrs
end

#normal_attrsObject

Returns the value of attribute normal_attrs.



41
42
43
# File 'lib/chef/node.rb', line 41

def normal_attrs
  @normal_attrs
end

#override_attrsObject

Returns the value of attribute override_attrs.



41
42
43
# File 'lib/chef/node.rb', line 41

def override_attrs
  @override_attrs
end

#recipe_listObject

Returns the value of attribute recipe_list.



40
41
42
# File 'lib/chef/node.rb', line 40

def recipe_list
  @recipe_list
end

#run_list(*args) ⇒ Object

Returns an Array of roles and recipes, in the order they will be applied. If you call it with arguments, they will become the new list of roles and recipes.



341
342
343
# File 'lib/chef/node.rb', line 341

def run_list
  @run_list
end

#run_stateObject

Returns the value of attribute run_state.



40
41
42
# File 'lib/chef/node.rb', line 40

def run_state
  @run_state
end

Class Method Details

.build(node_name) ⇒ Object



517
518
519
520
521
# File 'lib/chef/node.rb', line 517

def self.build(node_name)
  node = new
  node.name(node_name)
  node
end

.cdb_list(inflate = false, couchdb = nil) ⇒ Object

List all the Chef::Node objects in the CouchDB. If inflate is set to true, you will get the full list of all Nodes, fully inflated.



478
479
480
481
482
# File 'lib/chef/node.rb', line 478

def self.cdb_list(inflate=false, couchdb=nil)
  rs =(couchdb || Chef::CouchDB.new).list("nodes", inflate)
  lookup = (inflate ? "value" : "key")
  rs["rows"].collect { |r| r[lookup] }
end

.cdb_load(name, couchdb = nil) ⇒ Object

Load a node by name from CouchDB



497
498
499
# File 'lib/chef/node.rb', line 497

def self.cdb_load(name, couchdb=nil)
  (couchdb || Chef::CouchDB.new).load("node", name)
end

.create_design_document(couchdb = nil) ⇒ Object

Set up our CouchDB design document



563
564
565
# File 'lib/chef/node.rb', line 563

def self.create_design_document(couchdb=nil)
  (couchdb || Chef::CouchDB.new).create_design_document("nodes", DESIGN_DOCUMENT)
end

.exists?(nodename, couchdb) ⇒ Boolean

Returns:

  • (Boolean)


501
502
503
504
505
506
507
# File 'lib/chef/node.rb', line 501

def self.exists?(nodename, couchdb)
  begin
    self.cdb_load(nodename, couchdb)
  rescue Chef::Exceptions::CouchDBNotFound
    nil
  end
end

.find_or_create(node_name) ⇒ Object



509
510
511
512
513
514
515
# File 'lib/chef/node.rb', line 509

def self.find_or_create(node_name)
  load(node_name)
rescue Net::HTTPServerException => e
  raise unless e.response.code == '404'
  node = build(node_name)
  node.create
end

.json_create(o) ⇒ Object

Create a Chef::Node from JSON



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/chef/node.rb', line 453

def self.json_create(o)
  node = new
  node.name(o["name"])

  if o.has_key?("attributes")
    node.normal_attrs = o["attributes"]
  end
  node.automatic_attrs = Mash.new(o["automatic"]) if o.has_key?("automatic")
  node.normal_attrs = Mash.new(o["normal"]) if o.has_key?("normal")
  node.default_attrs = Mash.new(o["default"]) if o.has_key?("default")
  node.override_attrs = Mash.new(o["override"]) if o.has_key?("override")

  if o.has_key?("run_list")
    node.run_list.reset!(o["run_list"])
  else
    o["recipes"].each { |r| node.recipes << r }
  end
  node.couchdb_rev = o["_rev"] if o.has_key?("_rev")
  node.couchdb_id = o["_id"] if o.has_key?("_id")
  node.index_id = node.couchdb_id
  node
end

.list(inflate = false) ⇒ Object



484
485
486
487
488
489
490
491
492
493
494
# File 'lib/chef/node.rb', line 484

def self.list(inflate=false)
  if inflate
    response = Hash.new
    Chef::Search::Query.new.search(:node) do |n|
      response[n.name] = n unless n.nil?
    end
    response
  else
    Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes")
  end
end

.load(name) ⇒ Object

Load a node by name



524
525
526
# File 'lib/chef/node.rb', line 524

def self.load(name)
  Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes/#{name}")
end

Instance Method Details

#[](attrib) ⇒ Object

Return an attribute of this node. Returns nil if the attribute is not found.



223
224
225
# File 'lib/chef/node.rb', line 223

def [](attrib)
  construct_attributes[attrib]
end

#[]=(attrib, value) ⇒ Object

Set an attribute of this node



228
229
230
# File 'lib/chef/node.rb', line 228

def []=(attrib, value)
  construct_attributes[attrib] = value
end

#attributeObject

Used by the DSL



210
211
212
# File 'lib/chef/node.rb', line 210

def attribute
  construct_attributes
end

#attribute=(value) ⇒ Object



218
219
220
# File 'lib/chef/node.rb', line 218

def attribute=(value)
  self.normal_attrs = value
end

#attribute?(attrib) ⇒ Boolean

Return true if this Node has a given attribute, false if not. Takes either a symbol or a string.

Only works on the top level. Preferred way is to use the normal [] style lookup and call attribute?()

Returns:

  • (Boolean)


301
302
303
# File 'lib/chef/node.rb', line 301

def attribute?(attrib)
  construct_attributes.attribute?(attrib)
end

#cdb_destroyObject

Remove this node from the CouchDB



529
530
531
# File 'lib/chef/node.rb', line 529

def cdb_destroy
  couchdb.delete("node", name, couchdb_rev)
end

#cdb_saveObject

Save this node to the CouchDB



539
540
541
# File 'lib/chef/node.rb', line 539

def cdb_save
  @couchdb_rev = couchdb.store("node", name, self)["rev"]
end

#chef_server_restObject



172
173
174
# File 'lib/chef/node.rb', line 172

def chef_server_rest
  Chef::REST.new(Chef::Config[:chef_server_url])
end

#construct_attributesObject



214
215
216
# File 'lib/chef/node.rb', line 214

def construct_attributes
  Chef::Node::Attribute.new(normal_attrs, default_attrs, override_attrs, automatic_attrs)
end

#consume_attributes(attrs) ⇒ Object

Consumes the combined run_list and other attributes in attrs



369
370
371
372
373
374
# File 'lib/chef/node.rb', line 369

def consume_attributes(attrs)
  normal_attrs_to_merge = consume_run_list(attrs)
  Chef::Log.debug("Applying attributes from json file")
  @normal_attrs = Chef::Mixin::DeepMerge.merge(@normal_attrs,normal_attrs_to_merge)
  self[:tags] = Array.new unless attribute?(:tags)
end

#consume_external_attrs(ohai_data, json_cli_attrs) ⇒ Object

Consume data from ohai and Attributes provided as JSON on the command line.



356
357
358
359
360
361
362
363
364
365
366
# File 'lib/chef/node.rb', line 356

def consume_external_attrs(ohai_data, json_cli_attrs)
  Chef::Log.debug("Extracting run list from JSON attributes provided on command line")
  consume_attributes(json_cli_attrs)

  @automatic_attrs = ohai_data

  platform, version = Chef::Platform.find_platform_and_version(self)
  Chef::Log.debug("Platform is #{platform} version #{version}")
  @automatic_attrs[:platform] = platform
  @automatic_attrs[:platform_version] = version
end

#consume_run_list(attrs) ⇒ Object

Extracts the run list from attrs and applies it. Returns the remaining attributes



377
378
379
380
381
382
383
384
385
386
387
# File 'lib/chef/node.rb', line 377

def consume_run_list(attrs)
  attrs = attrs ? attrs.dup : {}
  if new_run_list = attrs.delete("recipes") || attrs.delete("run_list")
    if attrs.key?("recipes") || attrs.key?("run_list")
      raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only."
    end
    Chef::Log.info("Setting the run_list to #{new_run_list.inspect} from JSON")
    run_list(new_run_list)
  end
  attrs
end

#createObject

Create the node via the REST API



557
558
559
560
# File 'lib/chef/node.rb', line 557

def create
  chef_server_rest.post_rest("nodes", self)
  self
end

#defaultObject

Set a default of this node, but auto-vivifiy any Mashes that might be missing



260
261
262
263
264
265
# File 'lib/chef/node.rb', line 260

def default 
  attrs = construct_attributes
  attrs.set_type = :default
  attrs.auto_vivifiy_on_read = true
  attrs
end

#default_unlessObject

Set a default attribute of this node, auto-vivifiying any mashes that are missing, but if the final value already exists, don’t set it



269
270
271
272
273
274
275
# File 'lib/chef/node.rb', line 269

def default_unless
  attrs = construct_attributes
  attrs.set_type = :default
  attrs.auto_vivifiy_on_read = true
  attrs.set_unless_value_present = true
  attrs
end

#destroyObject

Remove this node via the REST API



534
535
536
# File 'lib/chef/node.rb', line 534

def destroy
  chef_server_rest.delete_rest("nodes/#{name}")
end

#each(&block) ⇒ Object

Yield each key of the top level to the block.



306
307
308
# File 'lib/chef/node.rb', line 306

def each(&block)
  construct_attributes.each(&block)
end

#each_attribute(&block) ⇒ Object

Iterates over each attribute, passing the attribute and value to the block.



311
312
313
# File 'lib/chef/node.rb', line 311

def each_attribute(&block)
  construct_attributes.each_attribute(&block)
end

#expand!Object

Expands the node’s run list and deep merges the default and override attributes. Also applies stored attributes (from json provided on the command line)

Returns the fully-expanded list of recipes.

TODO: timh/cw, 5-14-2010: Should this method exist? Should we instead modify default_attrs and override_attrs whenever our run_list is mutated? Or perhaps do something smarter like on-demand generation of default_attrs and override_attrs, invalidated only when run_list is mutated?



407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/chef/node.rb', line 407

def expand!
  # This call should only be called on a chef-client run.
  expansion = run_list.expand('server')
  raise Chef::Exceptions::MissingRole if expansion.errors?

  self[:tags] = Array.new unless attribute?(:tags)
  @default_attrs = Chef::Mixin::DeepMerge.merge(default_attrs, expansion.default_attrs)
  @override_attrs = Chef::Mixin::DeepMerge.merge(override_attrs, expansion.override_attrs)

  @automatic_attrs[:recipes] = expansion.recipes
  @automatic_attrs[:roles] = expansion.roles

  expansion.recipes
end

#find_file(fqdn) ⇒ Object

Find a recipe for this Chef::Node by fqdn. Will search first for Chef::Config/fqdn.rb, then hostname.rb, then default.rb.

Returns a new Chef::Node object.

Raises an ArgumentError if it cannot find the node.

Raises:

  • (ArgumentError)


182
183
184
185
186
187
188
189
190
191
192
# File 'lib/chef/node.rb', line 182

def find_file(fqdn)
  host_parts = fqdn.split(".")
  hostname = host_parts[0]

  [fqdn, hostname, "default"].each { |fname|
   node_file = File.join(Chef::Config[:node_path], "#{fname.to_s}.rb")
   return self.from_file(node_file) if File.exists?(node_file)
 }

  raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!"
end

#load_attribute_by_short_filename(name, src_cookbook_name) ⇒ Object

Used by DSL. Loads the attribute file specified by the short name of the file, e.g., loads specified cookbook’s

"attributes/mailservers.rb"

if passed

"mailservers"


588
589
590
591
592
593
594
595
596
597
# File 'lib/chef/node.rb', line 588

def load_attribute_by_short_filename(name, src_cookbook_name)
  src_cookbook = cookbook_collection[src_cookbook_name]
  raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{src_cookbook_name} while loading attribute #{name}" unless src_cookbook
  
  attribute_filename = src_cookbook.attribute_filenames_by_short_filename[name]
  raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{name} in cookbook #{src_cookbook_name}" unless attribute_filename

  self.from_file(attribute_filename)
  self
end

#load_attributesObject

Load all attribute files for all cookbooks associated with this node.



573
574
575
576
577
578
579
580
# File 'lib/chef/node.rb', line 573

def load_attributes
  cookbook_collection.values.each do |cookbook|
    cookbook.segment_filenames(:attributes).each do |segment_filename|
      Chef::Log.debug("node #{name} loading cookbook #{cookbook.name}'s attribute file #{segment_filename}")
      self.from_file(segment_filename)
    end
  end
end

#name(arg = nil) ⇒ Object

Set the name of this Node, or return the current name.



195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/chef/node.rb', line 195

def name(arg=nil)
  if arg != nil
    validate(
             {:name => arg },
             {:name => { :kind_of => String,
                 :cannot_be => :blank,
                 :regex => /^[\-[:alnum:]_:.]+$/}
             })
    @name = arg
  else
    @name
  end
end

#nodeObject

Used by DSL



168
169
170
# File 'lib/chef/node.rb', line 168

def node
  self
end

#normalObject Also known as: set

Set a normal attribute of this node, but auto-vivifiy any Mashes that might be missing



238
239
240
241
242
243
# File 'lib/chef/node.rb', line 238

def normal 
  attrs = construct_attributes
  attrs.set_type = :normal
  attrs.auto_vivifiy_on_read = true
  attrs
end

#normal_unlessObject Also known as: set_unless

Set a normal attribute of this node, auto-vivifiying any mashes that are missing, but if the final value already exists, don’t set it



249
250
251
252
253
254
255
# File 'lib/chef/node.rb', line 249

def normal_unless
  attrs = construct_attributes
  attrs.set_type = :normal
  attrs.auto_vivifiy_on_read = true
  attrs.set_unless_value_present = true
  attrs
end

#overrideObject

Set an override attribute of this node, but auto-vivifiy any Mashes that might be missing



279
280
281
282
283
284
# File 'lib/chef/node.rb', line 279

def override 
  attrs = construct_attributes
  attrs.set_type = :override
  attrs.auto_vivifiy_on_read = true
  attrs
end

#override_unlessObject

Set an override attribute of this node, auto-vivifiying any mashes that are missing, but if the final value already exists, don’t set it



288
289
290
291
292
293
294
# File 'lib/chef/node.rb', line 288

def override_unless
  attrs = construct_attributes
  attrs.set_type = :override
  attrs.auto_vivifiy_on_read = true
  attrs.set_unless_value_present = true
  attrs
end

#recipe?(recipe_name) ⇒ Boolean

Returns true if this Node expects a given recipe, false if not.

First, the run list is consulted to see whether the recipe is explicitly included. If it’s not there, it looks in run_state, which is populated by include_recipe statements in the DSL (and thus would not be in the run list).

NOTE: It’s used by cookbook authors

Returns:

  • (Boolean)


330
331
332
# File 'lib/chef/node.rb', line 330

def recipe?(recipe_name)
  run_list.include?(recipe_name) || run_state[:seen_recipes].include?(recipe_name)
end

#recipes(*args) ⇒ Object



345
346
347
348
# File 'lib/chef/node.rb', line 345

def recipes(*args)
  Chef::Log.warn "Chef::Node#recipes method is deprecated.  Please use Chef::Node#run_list"
  run_list(*args)
end

#reset_defaults_and_overridesObject

Clear defaults and overrides, so that any deleted attributes between runs are still gone.



391
392
393
394
# File 'lib/chef/node.rb', line 391

def reset_defaults_and_overrides
  @default_attrs = Mash.new
  @override_attrs = Mash.new
end

#role?(role_name) ⇒ Boolean

Returns true if this Node expects a given role, false if not.

Returns:

  • (Boolean)


335
336
337
# File 'lib/chef/node.rb', line 335

def role?(role_name)
  run_list.include?("role[#{role_name}]")
end

#run_list?(item) ⇒ Boolean

Returns true if this Node expects a given role, false if not.

Returns:

  • (Boolean)


351
352
353
# File 'lib/chef/node.rb', line 351

def run_list?(item)
  run_list.detect { |r| r == item } ? true : false
end

#saveObject

Save this node via the REST API



544
545
546
547
548
549
550
551
552
553
554
# File 'lib/chef/node.rb', line 544

def save
  # Try PUT. If the node doesn't yet exist, PUT will return 404,
  # so then POST to create.
  begin
    chef_server_rest.put_rest("nodes/#{name}", self)
  rescue Net::HTTPServerException => e
    raise e unless e.response.code == "404"
    chef_server_rest.post_rest("nodes", self)
  end
  self
end

#store(attrib, value) ⇒ Object



232
233
234
# File 'lib/chef/node.rb', line 232

def store(attrib, value)
  self[attrib] = value
end

#to_hashObject

Transform the node to a Hash



423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/chef/node.rb', line 423

def to_hash
  index_hash = Hash.new
  index_hash["chef_type"] = "node"
  index_hash["name"] = name
  attribute.each do |key, value|
    index_hash[key] = value
  end
  index_hash["recipe"] = run_list.recipe_names if run_list.recipe_names.length > 0
  index_hash["role"] = run_list.role_names if run_list.role_names.length > 0
  index_hash["run_list"] = run_list.run_list if run_list.run_list.length > 0
  index_hash
end

#to_json(*a) ⇒ Object

Serialize this object as a hash



437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/chef/node.rb', line 437

def to_json(*a)
  result = {
    "name" => name,
    'json_class' => self.class.name,
    "automatic" => automatic_attrs,
    "normal" => normal_attrs,
    "chef_type" => "node",
    "default" => default_attrs,
    "override" => override_attrs,
    "run_list" => run_list.run_list
  }
  result["_rev"] = couchdb_rev if couchdb_rev
  result.to_json(*a)
end

#to_sObject



567
568
569
# File 'lib/chef/node.rb', line 567

def to_s
  "node[#{name}]"
end