class Chef::Node

Constants

NULL_ARG

Attributes

logger[R]
override_runlist[W]
recipe_list[RW]
run_context[RW]

RunContext will set itself as #run_context via this setter when initialized. This is needed so DSL::IncludeAttribute (in particular, include_recipe) can access the #run_context to determine if an attributes file has been seen yet.

run_state[RW]

Public Class Methods

build(node_name) click to toggle source
# File lib/chef/node.rb, line 581
def self.build(node_name)
  node = new
  node.name(node_name)
  node.chef_environment(Chef::Config[:environment]) unless Chef::Config[:environment].nil? || Chef::Config[:environment].chomp.empty?
  node
end
find_or_create(node_name) click to toggle source
# File lib/chef/node.rb, line 573
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
from_hash(o) click to toggle source
# File lib/chef/node.rb, line 519
def self.from_hash(o)
  return o if o.kind_of? Chef::Node
  node = new
  node.name(o["name"])

  node.policy_name = o["policy_name"] if o.has_key?("policy_name")
  node.policy_group = o["policy_group"] if o.has_key?("policy_group")

  unless node.policy_group.nil?
    node.chef_environment(o["policy_group"])
  else
    node.chef_environment(o["chef_environment"])
  end

  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"])
  elsif o.has_key?("recipes")
    o["recipes"].each { |r| node.recipes << r }
  end

  node
end
list(inflate = false) click to toggle source
# File lib/chef/node.rb, line 560
def self.list(inflate = false)
  if inflate
    response = Hash.new
    Chef::Search::Query.new.search(:node) do |n|
      n = Chef::Node.from_hash(n)
      response[n.name] = n unless n.nil?
    end
    response
  else
    Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes")
  end
end
list_by_environment(environment, inflate = false) click to toggle source
# File lib/chef/node.rb, line 550
def self.list_by_environment(environment, inflate = false)
  if inflate
    response = Hash.new
    Chef::Search::Query.new.search(:node, "chef_environment:#{environment}") { |n| response[n.name] = n unless n.nil? }
    response
  else
    Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("environments/#{environment}/nodes")
  end
end
load(name) click to toggle source

Load a node by name

# File lib/chef/node.rb, line 589
def self.load(name)
  from_hash(Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes/#{name}"))
end
new(chef_server_rest: nil, logger: nil) click to toggle source

Create a new Chef::Node object.

# File lib/chef/node.rb, line 71
def initialize(chef_server_rest: nil, logger: nil)
  @chef_server_rest = chef_server_rest
  @name = nil
  @logger = logger || Chef::Log.with_child(subsystem: "node")

  @chef_environment = "_default"
  @primary_runlist = Chef::RunList.new
  @override_runlist = Chef::RunList.new

  @policy_name = nil
  @policy_group = nil

  @attributes = Chef::Node::Attribute.new({}, {}, {}, {}, self)

  @run_state = {}
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/chef/node.rb, line 645
def <=>(other)
  name <=> other.name
end
==(other) click to toggle source
# File lib/chef/node.rb, line 637
def ==(other)
  if other.kind_of?(self.class)
    name == other.name
  else
    false
  end
end
[](attrib) click to toggle source

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

# File lib/chef/node.rb, line 195
def [](attrib)
  attributes[attrib]
end
apply_expansion_attributes(expansion) click to toggle source

Apply the default and overrides attributes from the expansion passed in, which came from roles.

# File lib/chef/node.rb, line 439
def apply_expansion_attributes(expansion)
  loaded_environment = if chef_environment == "_default"
                         Chef::Environment.new.tap { |e| e.name("_default") }
                       else
                         Chef::Environment.load(chef_environment)
                       end

  attributes.env_default = loaded_environment.default_attributes
  attributes.env_override = loaded_environment.override_attributes

  attribute.role_default = expansion.default_attrs
  attributes.role_override = expansion.override_attrs
end
attribute()
Alias for: attributes
attribute?(attrib) click to toggle source

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?()

# File lib/chef/node.rb, line 246
def attribute?(attrib)
  attributes.attribute?(attrib)
end
attributes() click to toggle source
# File lib/chef/node.rb, line 187
def attributes
  @attributes
end
Also aliased as: attribute, construct_attributes
automatic_attrs() click to toggle source
# File lib/chef/node.rb, line 233
def automatic_attrs
  attributes.automatic
end
automatic_attrs=(new_values) click to toggle source
# File lib/chef/node.rb, line 237
def automatic_attrs=(new_values)
  attributes.automatic = new_values
end
chef_environment(arg = nil) click to toggle source
# File lib/chef/node.rb, line 129
def chef_environment(arg = nil)
  set_or_return(
    :chef_environment,
    arg,
    { :regex => /^[\-[:alnum:]_]+$/, :kind_of => String }
  )
end
Also aliased as: environment
chef_environment=(environment) click to toggle source
# File lib/chef/node.rb, line 137
def chef_environment=(environment)
  chef_environment(environment)
end
chef_server_rest() click to toggle source
# File lib/chef/node.rb, line 102
def chef_server_rest
  # for saving node data we use validate_utf8: false which will not
  # raise an exception on bad utf8 data, but will replace the bad
  # characters and render valid JSON.
  @chef_server_rest ||= Chef::ServerAPI.new(
    Chef::Config[:chef_server_url],
    client_name: Chef::Config[:node_name],
    signing_key_filename: Chef::Config[:client_key],
    validate_utf8: false
  )
end
construct_attributes()
Alias for: attributes
consume_attributes(attrs) click to toggle source

Consumes the combined #run_list and other attributes in attrs

# File lib/chef/node.rb, line 348
def consume_attributes(attrs)
  normal_attrs_to_merge = consume_run_list(attrs)
  normal_attrs_to_merge = consume_chef_environment(normal_attrs_to_merge)
  # FIXME(log): should be trace
  logger.debug("Applying attributes from json file")
  self.normal_attrs = Chef::Mixin::DeepMerge.merge(normal_attrs, normal_attrs_to_merge)
  tags # make sure they're defined
end
consume_chef_environment(attrs) click to toggle source

#chef_environment when set in -j JSON will take precedence over -E ENVIRONMENT. Ideally, IMO, the order of precedence should be (lowest to

highest):
 config_file
 -j JSON
 -E ENVIRONMENT

so that users could reuse their JSON and override the #chef_environment configured within it with -E ENVIRONMENT. Because command line options are merged with Chef::Config there is currently no way to distinguish between an environment set via config from an environment set via command line.

# File lib/chef/node.rb, line 394
def consume_chef_environment(attrs)
  attrs = attrs ? attrs.dup : {}
  if env = attrs.delete("chef_environment")
    chef_environment(env)
  end
  attrs
end
consume_external_attrs(ohai_data, json_cli_attrs) click to toggle source

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

# File lib/chef/node.rb, line 326
def consume_external_attrs(ohai_data, json_cli_attrs)
  # FIXME(log): should be trace
  logger.debug("Extracting run list from JSON attributes provided on command line")
  consume_attributes(json_cli_attrs)

  self.automatic_attrs = ohai_data

  platform, version = Chef::Platform.find_platform_and_version(self)
  # FIXME(log): should be trace
  logger.debug("Platform is #{platform} version #{version}")
  automatic[:platform] = platform
  automatic[:platform_version] = version
  automatic[:chef_guid] = Chef::Config[:chef_guid]
  automatic[:name] = name
  automatic[:chef_environment] = chef_environment
end
consume_ohai_data(ohai_data) click to toggle source
# File lib/chef/node.rb, line 343
def consume_ohai_data(ohai_data)
  self.automatic_attrs = Chef::Mixin::DeepMerge.merge(automatic_attrs, ohai_data)
end
consume_run_list(attrs) click to toggle source

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

# File lib/chef/node.rb, line 372
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
    logger.info("Setting the run_list to #{new_run_list} from CLI options")
    run_list(new_run_list)
  end
  attrs
end
create() click to toggle source

Create the node via the REST API

# File lib/chef/node.rb, line 619
def create
  chef_server_rest.post("nodes", data_for_save)
  self
rescue Net::HTTPServerException => e
  # Chef Server before 12.3 rejects node JSON with 'policy_name' or
  # 'policy_group' keys, but 'policy_name' will be detected first.
  # Backcompat can be removed in 13.0
  if e.response.code == "400" && e.response.body.include?("Invalid key policy_name")
    chef_server_rest.post("nodes", data_for_save_without_policyfile_attrs)
  else
    raise
  end
end
default() click to toggle source

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

# File lib/chef/node.rb, line 207
def default
  attributes.default
end
Also aliased as: default_attrs
default_attrs()
Alias for: default
default_attrs=(new_values) click to toggle source
# File lib/chef/node.rb, line 225
def default_attrs=(new_values)
  attributes.default = new_values
end
destroy() click to toggle source

Remove this node via the REST API

# File lib/chef/node.rb, line 594
def destroy
  chef_server_rest.delete("nodes/#{name}")
end
display_hash() click to toggle source
# File lib/chef/node.rb, line 468
def display_hash
  display = {}
  display["name"]             = name
  display["chef_environment"] = chef_environment
  display["automatic"]        = automatic_attrs
  display["normal"]           = normal_attrs
  display["default"]          = attributes.combined_default
  display["override"]         = attributes.combined_override
  display["run_list"]         = run_list.run_list_items
  display
end
each(&block) click to toggle source

Yield each key of the top level to the block.

# File lib/chef/node.rb, line 251
def each(&block)
  attributes.each(&block)
end
each_attribute(&block) click to toggle source

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

# File lib/chef/node.rb, line 256
def each_attribute(&block)
  attributes.each_attribute(&block)
end
environment(arg = nil)
Alias for: chef_environment
expand!(data_source = "server") click to toggle source

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

Returns the fully-expanded list of recipes, a RunListExpansion.

# File lib/chef/node.rb, line 421
def expand!(data_source = "server")
  expansion = run_list.expand(chef_environment, data_source)
  raise Chef::Exceptions::MissingRole, expansion if expansion.errors?

  tags # make sure they're defined

  automatic_attrs[:recipes] = expansion.recipes.with_duplicate_names
  automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints
  automatic_attrs[:roles] = expansion.roles

  apply_expansion_attributes(expansion)

  automatic_attrs[:chef_environment] = chef_environment
  expansion
end
for_json() click to toggle source
# File lib/chef/node.rb, line 485
def for_json
  result = {
    "name" => name,
    "chef_environment" => chef_environment,
    "json_class" => self.class.name,
    "automatic" => attributes.automatic,
    "normal" => attributes.normal,
    "chef_type" => "node",
    "default" => attributes.combined_default,
    "override" => attributes.combined_override,
    #Render correctly for run_list items so malformed json does not result
    "run_list" => @primary_runlist.run_list.map { |item| item.to_s },
  }
  # Chef Server rejects node JSON with extra keys; prior to 12.3,
  # "policy_name" and "policy_group" are unknown; after 12.3 they are
  # optional, therefore only including them in the JSON if present
  # maximizes compatibility for most people.
  unless policy_group.nil? && policy_name.nil?
    result["policy_name"] = policy_name
    result["policy_group"] = policy_group
  end
  result
end
loaded_recipe(cookbook, recipe) click to toggle source

used by include_recipe to add recipes to the expanded #run_list to be saved back to the node and be searchable

# File lib/chef/node.rb, line 284
def loaded_recipe(cookbook, recipe)
  fully_qualified_recipe = "#{cookbook}::#{recipe}"

  automatic_attrs[:recipes] << fully_qualified_recipe unless Array(self[:recipes]).include?(fully_qualified_recipe)
end
method_missing(method, *args, &block) click to toggle source

Only works for attribute fetches, setting is no longer supported XXX: this should be deprecated

# File lib/chef/node.rb, line 262
def method_missing(method, *args, &block)
  attributes.public_send(method, *args, &block)
end
name(arg = nil) click to toggle source

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

# File lib/chef/node.rb, line 115
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
node() click to toggle source

Used by DSL

# File lib/chef/node.rb, line 98
def node
  self
end
normal() click to toggle source

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

# File lib/chef/node.rb, line 201
def normal
  attributes.normal
end
Also aliased as: normal_attrs
normal_attrs()
Alias for: normal
normal_attrs=(new_values) click to toggle source
# File lib/chef/node.rb, line 229
def normal_attrs=(new_values)
  attributes.normal = new_values
end
override() click to toggle source

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

# File lib/chef/node.rb, line 213
def override
  attributes.override
end
Also aliased as: override_attrs
override_attrs()
Alias for: override
override_attrs=(new_values) click to toggle source
# File lib/chef/node.rb, line 221
def override_attrs=(new_values)
  attributes.override = new_values
end
override_runlist(*args) click to toggle source
# File lib/chef/node.rb, line 300
def override_runlist(*args)
  args.length > 0 ? @override_runlist.reset!(args) : @override_runlist
end
policy_group(arg = NULL_ARG) click to toggle source

The `policy_group` for this node. Setting this to a non-nil value will enable policyfile mode when `chef-client` is run. If set in the config file or in node json, running `chef-client` will update this value.

@see Chef::PolicyBuilder::Dynamic @see Chef::PolicyBuilder::Policyfile

@param arg [String] the new #policy_group value @return [String] the current #policy_group, or the one you just set

# File lib/chef/node.rb, line 174
def policy_group(arg = NULL_ARG)
  return @policy_group if arg.equal?(NULL_ARG)
  validate({ policy_group: arg }, { policy_group: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
  @policy_group = arg
end
policy_group=(policy_group) click to toggle source

A “non-DSL-style” setter for `policy_group`

@see policy_group

# File lib/chef/node.rb, line 183
def policy_group=(policy_group)
  policy_group(policy_group)
end
policy_name(arg = NULL_ARG) click to toggle source

The `policy_name` for this node. Setting this to a non-nil value will enable policyfile mode when `chef-client` is run. If set in the config file or in node json, running `chef-client` will update this value.

@see Chef::PolicyBuilder::Dynamic @see Chef::PolicyBuilder::Policyfile

@param arg [String] the new #policy_name value @return [String] the current #policy_name, or the one you just set

# File lib/chef/node.rb, line 152
def policy_name(arg = NULL_ARG)
  return @policy_name if arg.equal?(NULL_ARG)
  validate({ policy_name: arg }, { policy_name: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
  @policy_name = arg
end
policy_name=(policy_name) click to toggle source

A “non-DSL-style” setter for `policy_name`

@see policy_name

# File lib/chef/node.rb, line 161
def policy_name=(policy_name)
  policy_name(policy_name)
end
primary_runlist() click to toggle source
# File lib/chef/node.rb, line 295
def primary_runlist
  @primary_runlist
end
recipe?(recipe_name) click to toggle source

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 `node`, which is populated when the #run_list is expanded

NOTE: It's used by cookbook authors

# File lib/chef/node.rb, line 278
def recipe?(recipe_name)
  run_list.include?(recipe_name) || Array(self[:recipes]).include?(recipe_name)
end
reset_defaults_and_overrides() click to toggle source

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

# File lib/chef/node.rb, line 404
def reset_defaults_and_overrides
  default.clear
  override.clear
end
respond_to_missing?(method, include_private = false) click to toggle source

Fix respond_to + method so that it works with #method_missing delegation

# File lib/chef/node.rb, line 267
def respond_to_missing?(method, include_private = false)
  attributes.respond_to?(method, false)
end
role?(role_name) click to toggle source

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

# File lib/chef/node.rb, line 291
def role?(role_name)
  run_list.include?("role[#{role_name}]")
end
run_list(*args) click to toggle source

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.

# File lib/chef/node.rb, line 310
def run_list(*args)
  rl = select_run_list
  args.length > 0 ? rl.reset!(args) : rl
end
run_list=(list) click to toggle source
# File lib/chef/node.rb, line 315
def run_list=(list)
  rl = select_run_list
  rl = list
end
run_list?(item) click to toggle source

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

# File lib/chef/node.rb, line 321
def run_list?(item)
  run_list.detect { |r| r == item } ? true : false
end
save() click to toggle source

Save this node via the REST API

# File lib/chef/node.rb, line 599
def save
  # Try PUT. If the node doesn't yet exist, PUT will return 404,
  # so then POST to create.
  begin
    if Chef::Config[:why_run]
      logger.warn("In why-run mode, so NOT performing node save.")
    else
      chef_server_rest.put("nodes/#{name}", data_for_save)
    end
  rescue Net::HTTPServerException => e
    if e.response.code == "404"
      chef_server_rest.post("nodes", data_for_save)
    else
      raise
    end
  end
  self
end
select_run_list() click to toggle source
# File lib/chef/node.rb, line 304
def select_run_list
  @override_runlist.empty? ? @primary_runlist : @override_runlist
end
set_cookbook_attribute() click to toggle source

after the #run_context has been set on the node, go through the cookbook_collection and setup the node attribute so that it is published in the node object

# File lib/chef/node.rb, line 90
def set_cookbook_attribute
  return unless run_context.cookbook_collection
  run_context.cookbook_collection.each do |cookbook_name, cookbook|
    automatic_attrs[:cookbooks][cookbook_name][:version] = cookbook.version
  end
end
tag(*args) click to toggle source
# File lib/chef/node.rb, line 363
def tag(*args)
  args.each do |tag|
    tags.push(tag.to_s) unless tags.include? tag.to_s
  end

  tags
end
tags() click to toggle source

Lazy initializer for tags attribute

# File lib/chef/node.rb, line 358
def tags
  normal[:tags] = Array(normal[:tags])
  normal[:tags]
end
to_hash() click to toggle source

Transform the node to a Hash

# File lib/chef/node.rb, line 454
def to_hash
  index_hash = Hash.new
  index_hash["chef_type"] = "node"
  index_hash["name"] = name
  index_hash["chef_environment"] = chef_environment
  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_items
  index_hash
end
to_json(*a) click to toggle source

Serialize this object as a hash

# File lib/chef/node.rb, line 481
def to_json(*a)
  Chef::JSONCompat.to_json(for_json, *a)
end
to_s() click to toggle source
# File lib/chef/node.rb, line 633
def to_s
  "node[#{name}]"
end
update_from!(o) click to toggle source
# File lib/chef/node.rb, line 509
def update_from!(o)
  run_list.reset!(o.run_list)
  self.automatic_attrs = o.automatic_attrs
  self.normal_attrs = o.normal_attrs
  self.override_attrs = o.override_attrs
  self.default_attrs = o.default_attrs
  chef_environment(o.chef_environment)
  self
end

Private Instance Methods

data_for_save() click to toggle source
# File lib/chef/node.rb, line 667
def data_for_save
  data = for_json
  %w{automatic default normal override}.each do |level|
    whitelist_config_option = "#{level}_attribute_whitelist".to_sym
    whitelist = Chef::Config[whitelist_config_option]
    unless whitelist.nil? # nil => save everything
      logger.info("Whitelisting #{level} node attributes for save.")
      data[level] = Chef::Whitelist.filter(data[level], whitelist)
    end

    blacklist_config_option = "#{level}_attribute_blacklist".to_sym
    blacklist = Chef::Config[blacklist_config_option]
    unless blacklist.nil? # nil => remove nothing
      logger.info("Blacklisting #{level} node attributes for save")
      data[level] = Chef::Blacklist.filter(data[level], blacklist)
    end
  end
  data
end
data_for_save_without_policyfile_attrs() click to toggle source
# File lib/chef/node.rb, line 660
def data_for_save_without_policyfile_attrs
  data_for_save.tap do |trimmed_data|
    trimmed_data.delete("policy_name")
    trimmed_data.delete("policy_group")
  end
end
save_without_policyfile_attrs() click to toggle source
# File lib/chef/node.rb, line 651
def save_without_policyfile_attrs
  trimmed_data = data_for_save_without_policyfile_attrs

  chef_server_rest.put("nodes/#{name}", trimmed_data)
rescue Net::HTTPServerException => e
  raise e unless e.response.code == "404"
  chef_server_rest.post("nodes", trimmed_data)
end