class ChefAPI::Resource::Base

Attributes

associations[R]

The list of associations.

@return [Hash]

Public Class Methods

all() click to toggle source

Return an array of all resources in the collection.

@note Unless you need the entire collection, please consider using the {size} and {each} methods instead as they are much more perforant.

@return [Array<Resource::Base>]

# File lib/chef-api/resources/base.rb, line 391
def all
  entries
end
build(attributes = {}, prefix = {}) click to toggle source

Build a new resource from the given attributes.

@see ChefAPI::Resource::Base#initialize for more information

@example build an empty resource

Bacon.build #=> #<ChefAPI::Resource::Bacon>

@example build a resource with attributes

Bacon.build(foo: 'bar') #=> #<ChefAPI::Resource::Baocn foo: bar>

@param [Hash] attributes

the list of attributes for the new resource - any attributes that
are not defined in the schema are silently ignored
# File lib/chef-api/resources/base.rb, line 273
def build(attributes = {}, prefix = {})
  new(attributes, prefix)
end
classname() click to toggle source

The name for this resource, minus the parent module.

@example

classname #=> Resource::Bacon

@return [String]

# File lib/chef-api/resources/base.rb, line 446
def classname
  name.split('::')[1..-1].join('::')
end
collection(prefix = {}) click to toggle source

The full collection list.

@param [Hash] prefix

any prefix options to use

@return [Array<Resource::Base>]

a list of resources in the collection
# File lib/chef-api/resources/base.rb, line 471
def collection(prefix = {})
  connection.get(expanded_collection_path(prefix))
end
collection_path(value = UNSET) click to toggle source

Get or set the name of the remote resource collection. This is most likely the remote API endpoint (such as /clients), without the leading slash.

@example Setting a base collection path

collection_path '/clients'

@example Setting a collection path with nesting

collection_path '/data/:name'

@param [Symbol] value

the value to use for the collection name.

@return [Symbol, String]

the name of the collection
# File lib/chef-api/resources/base.rb, line 116
def collection_path(value = UNSET)
  if value != UNSET
    @collection_path = value.to_s
  else
    @collection_path ||
      raise(ArgumentError, "collection_path not set for #{self.class}")
  end
end
connection() click to toggle source

The current connection object.

@return [ChefAPI::Connection]

# File lib/chef-api/resources/base.rb, line 524
def connection
  Thread.current['chefapi.connection'] || ChefAPI.connection
end
count(prefix = {}) click to toggle source

The total number of reosurces in the collection.

@return [Fixnum]

# File lib/chef-api/resources/base.rb, line 378
def count(prefix = {})
  collection(prefix).size
end
Also aliased as: size
create(attributes = {}, prefix = {}) click to toggle source

Create a new resource and save it to the Chef Server, raising any exceptions that might occur. This method will save the resource back to the Chef Server, raising any validation errors that occur.

@raise [Error::ResourceAlreadyExists]

if the resource with the primary key already exists on the Chef Server

@raise [Error::InvalidResource]

if any of the resource's validations fail

@param [Hash] attributes

the list of attributes to set on the new resource

@return [Resource::Base]

an instance of the created resource
# File lib/chef-api/resources/base.rb, line 293
def create(attributes = {}, prefix = {})
  resource = build(attributes, prefix)

  unless resource.new_resource?
    raise Error::ResourceAlreadyExists.new
  end

  resource.save!
  resource
end
delete(id, prefix = {}) click to toggle source

Delete the remote resource from the Chef Sserver.

@param [String, Fixnum] id

the id of the resource to delete

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [true]

# File lib/chef-api/resources/base.rb, line 174
def delete(id, prefix = {})
  path = resource_path(id, prefix)
  connection.delete(path)
  true
rescue Error::HTTPNotFound
  true
end
destroy(id, prefix = {}) click to toggle source

Destroy a record with the given id.

@param [String, Fixnum] id

the id of the resource to delete

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [Base, nil]

the destroyed resource, or nil if the resource does not exist on the
remote Chef Server
# File lib/chef-api/resources/base.rb, line 213
def destroy(id, prefix = {})
  resource = fetch(id, prefix)
  return nil if resource.nil?

  resource.destroy
  resource
end
destroy_all(prefix = {}) click to toggle source

Delete all remote resources of the given type from the Chef Server

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [Array<Base>]

an array containing the list of resources that were deleted
# File lib/chef-api/resources/base.rb, line 229
def destroy_all(prefix = {})
  map { |resource| resource.destroy }
end
each(prefix = {}, &block) click to toggle source

(Lazy) iterate over each item in the collection, yielding the fully- built resource object. This method, coupled with the Enumerable module, provides us with other methods like first and map.

@example get the first resource

Bacon.first #=> #<ChefAPI::Resource::Bacon>

@example get the first 3 resources

Bacon.first(3) #=> [#<ChefAPI::Resource::Bacon>, ...]

@example iterate over each resource

Bacon.each { |bacon| puts bacon.name }

@example get all the resource's names

Bacon.map(&:name) #=> ["ham", "sausage", "turkey"]
# File lib/chef-api/resources/base.rb, line 364
def each(prefix = {}, &block)
  collection(prefix).each do |resource, path|
    response = connection.get(path)
    result = from_json(response, prefix)

    block.call(result) if block
  end
end
exists?(id, prefix = {}) click to toggle source

Check if the given resource exists on the Chef Server.

@param [String, Fixnum] id

the id of the resource to fetch

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [Boolean]

# File lib/chef-api/resources/base.rb, line 314
def exists?(id, prefix = {})
  !fetch(id, prefix).nil?
end
expanded_collection_path(prefix = {}) click to toggle source

Expand the collection path, “interpolating” any parameters. This syntax is heavily borrowed from Rails and it will make more sense by looking at an example.

@example

/bacon, {} #=> "foo"
/bacon/:type, { type: 'crispy' } #=> "bacon/crispy"

@raise [Error::MissingURLParameter]

if a required parameter is not given

@param [Hash] prefix

the list of prefix options

@return [String]

the "interpolated" URL string
# File lib/chef-api/resources/base.rb, line 506
def expanded_collection_path(prefix = {})
  collection_path.gsub(/:\w+/) do |param|
    key = param.delete(':')
    value = prefix[key] || prefix[key.to_sym]

    if value.nil?
      raise Error::MissingURLParameter.new(param: key)
    end

    URI.escape(value)
  end.sub(/^\//, '') # Remove leading slash
end
fetch(id, prefix = {}) click to toggle source

Fetch a single resource in the remote collection.

@example fetch a single client

Client.fetch('chef-webui') #=> #<Client name: 'chef-webui', ...>

@param [String, Fixnum] id

the id of the resource to fetch

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [Resource::Base, nil]

an instance of the resource, or nil if that given resource does not
exist
# File lib/chef-api/resources/base.rb, line 248
def fetch(id, prefix = {})
  return nil if id.nil?

  path     = resource_path(id, prefix)
  response = connection.get(path)
  from_json(response, prefix)
rescue Error::HTTPNotFound
  nil
end
from_file(path) click to toggle source

Load the given resource from it's on-disk equivalent. This action only makes sense for some resources, and must be defined on a per-resource basis, since the logic varies between resources.

@param [String] path

the path to the file on disk
# File lib/chef-api/resources/base.rb, line 15
def from_file(path)
  raise Error::AbstractMethod.new(method: 'Resource::Base#from_file')
end
from_json(response, prefix = {}) click to toggle source

Construct the object from a JSON response. This method actually just delegates to the new method, but it removes some marshall data and whatnot from the response first.

@param [String] response

the JSON response from the Chef Server

@return [Resource::Base]

an instance of the resource represented by this JSON
# File lib/chef-api/resources/base.rb, line 406
def from_json(response, prefix = {})
  response.delete('json_class')
  response.delete('chef_type')

  new(response, prefix)
end
from_url(url, prefix = {}) click to toggle source

@todo doc

# File lib/chef-api/resources/base.rb, line 22
def from_url(url, prefix = {})
  from_json(connection.get(url), prefix)
end
has_many(method, options = {}) click to toggle source

Create a nested relationship collection. The associated collection is cached on the class, reducing API requests.

@example Create an association to environments

has_many :environments

@example Create an association with custom configuration

has_many :environments, class_name: 'Environment'
# File lib/chef-api/resources/base.rb, line 80
      def has_many(method, options = {})
        class_name    = options[:class_name] || "Resource::#{Util.camelize(method).sub(/s$/, '')}"
        rest_endpoint = options[:rest_endpoint] || method

        class_eval "          def #{method}
            associations[:#{method}] ||=
              Resource::CollectionProxy.new(self, #{class_name}, '#{rest_endpoint}')
          end
", __FILE__, __LINE__ + 1
      end
inspect() click to toggle source

The detailed string representation of this class, including the full schema definition.

@example for the Bacon class

Bacon.inspect #=> "Resource::Bacon(id, description, ...)"

@return [String]

# File lib/chef-api/resources/base.rb, line 434
def inspect
  "#{classname}(#{schema.attributes.keys.join(', ')})"
end
list(prefix = {}) click to toggle source

Get the “list” of items in this resource. This list contains the primary keys of all of the resources in this collection. This method is useful in CLI applications, because it only makes a single API request to gather this data.

@param [Hash] prefix

the listof prefix options (for nested resources)

@example Get the list of all clients

Client.list #=> ['validator', 'chef-webui']

@return [Array<String>]

# File lib/chef-api/resources/base.rb, line 196
def list(prefix = {})
  path = expanded_collection_path(prefix)
  connection.get(path).keys.sort
end
new(attributes = {}, prefix = {}) { |self| ... } click to toggle source

Initialize a new resource with the given attributes. These attributes are merged with the default values from the schema. Any attributes that aren't defined in the schema are silently ignored for security purposes.

@example create a resource using attributes

Bacon.new(foo: 'bar', zip: 'zap') #=> #<ChefAPI::Resource::Bacon>

@example using a block

Bacon.new do |bacon|
  bacon.foo = 'bar'
  bacon.zip = 'zap'
end

@param [Hash] attributes

the list of initial attributes to set on the model

@param [Hash] prefix

the list of prefix options (for nested resources)
# File lib/chef-api/resources/base.rb, line 556
def initialize(attributes = {}, prefix = {})
  @schema = self.class.schema.dup
  @schema.load_flavor(self.class.connection.flavor)

  @associations = {}
  @_prefix      = prefix

  # Define a getter and setter method for each attribute in the schema
  _attributes.each do |key, value|
    define_singleton_method(key) { _attributes[key] }
    define_singleton_method("#{key}=") { |value| update_attribute(key, value) }
  end

  attributes.each do |key, value|
    unless ignore_attribute?(key)
      update_attribute(key, value)
    end
  end

  yield self if block_given?
end
post(body, prefix = {}) click to toggle source

Make an authenticated HTTP POST request using the connection object. This method returns a new object representing the response from the server, which should be merged with an existing object's attributes to reflect the newest state of the resource.

@param [Hash] body

the request body to create the resource with (probably JSON)

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [String]

the JSON response from the server
# File lib/chef-api/resources/base.rb, line 139
def post(body, prefix = {})
  path = expanded_collection_path(prefix)
  connection.post(path, body)
end
protect(*ids) click to toggle source

Protect one or more resources from being altered by the user. This is useful if there's an admin client or magical cookbook that you don't want users to modify.

@example

protect 'chef-webui', 'my-magical-validator'

@example

protect ->(resource) { resource.name =~ /internal_(.+)/ }

@param [Array<String, Proc>] ids

the list of "things" to protect
# File lib/chef-api/resources/base.rb, line 64
def protect(*ids)
  ids.each { |id| protected_resources << id }
end
protected_resources() click to toggle source

@todo doc

# File lib/chef-api/resources/base.rb, line 95
def protected_resources
  @protected_resources ||= []
end
put(id, body, prefix = {}) click to toggle source

Perform a PUT request to the Chef Server against the given resource or resource identifier. The resource will be partially updated (this method doubles as PATCH) with the given parameters.

@param [String, Resource::Base] id

a resource object or a string representing the unique identifier of
the resource object to update

@param [Hash] body

the request body to create the resource with (probably JSON)

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [String]

the JSON response from the server
# File lib/chef-api/resources/base.rb, line 160
def put(id, body, prefix = {})
  path = resource_path(id, prefix)
  connection.put(path, body)
end
resource_path(id, prefix = {}) click to toggle source

The path to an individual resource.

@param [Hash] prefix

the list of prefix options

@return [String]

the path to the resource
# File lib/chef-api/resources/base.rb, line 484
def resource_path(id, prefix = {})
  [expanded_collection_path(prefix), id].join('/')
end
schema(&block) click to toggle source

Get or set the schema for the remote resource. You probably only want to call schema once with a block, because it will overwrite the existing schema (meaning entries are not merged). If a block is given, a new schema object is created, otherwise the current one is returned.

@example

schema do
  attribute :id, primary: true
  attribute :name, type: String, default: 'bacon'
  attribute :admin, type: Boolean, required: true
end

@return [Schema]

the schema object for this resource
# File lib/chef-api/resources/base.rb, line 42
def schema(&block)
  if block
    @schema = Schema.new(&block)
  else
    @schema
  end
end
size(prefix = {})
Alias for: count
to_s() click to toggle source

The string representation of this class.

@example for the Bacon class

Bacon.to_s #=> "Resource::Bacon"

@return [String]

# File lib/chef-api/resources/base.rb, line 421
def to_s
  classname
end
type() click to toggle source

The type of this resource.

@example

bacon

@return [String]

# File lib/chef-api/resources/base.rb, line 458
def type
  Util.underscore(name.split('::').last).gsub('_', ' ')
end
update(id, attributes = {}, prefix = {}) click to toggle source

Perform a PUT request to the Chef Server for the current resource, updating the given parameters. The parameters may be a full or partial resource update, as supported by the Chef Server.

@raise [Error::ResourceNotFound]

if the given resource does not exist on the Chef Server

@param [String, Fixnum] id

the id of the resource to update

@param [Hash] attributes

the list of attributes to set on the new resource

@param [Hash] prefix

the list of prefix options (for nested resources)

@return [Resource::Base]

the new resource
# File lib/chef-api/resources/base.rb, line 336
def update(id, attributes = {}, prefix = {})
  resource = fetch(id, prefix)

  unless resource
    raise Error::ResourceNotFound.new(type: type, id: id)
  end

  resource.update(attributes).save
  resource
end

Public Instance Methods

_attributes() click to toggle source

The list of attributes on this resource.

@return [Hash<Symbol, Object>]

# File lib/chef-api/resources/base.rb, line 609
def _attributes
  @_attributes ||= {}.merge(@schema.attributes)
end
_prefix() click to toggle source

@todo doc

# File lib/chef-api/resources/base.rb, line 600
def _prefix
  @_prefix
end
attribute?(key) click to toggle source

Determine if this resource has the given attribute.

@param [Symbol, String] key

the attribute key to find

@return [Boolean]

true if the attribute exists, false otherwise
# File lib/chef-api/resources/base.rb, line 622
def attribute?(key)
  _attributes.has_key?(key.to_sym)
end
destroy() click to toggle source

Remove the resource from the Chef Server.

@return [self]

the current instance of this object
# File lib/chef-api/resources/base.rb, line 718
def destroy
  self.class.delete(id, _prefix)
  self
end
diff() click to toggle source

Calculate a differential of the attributes on the local resource with it's remote Chef Server counterpart.

@example when the local resource is in sync with the remote resource

bacon = Bacon.first
bacon.diff #=> {}

@example when the local resource differs from the remote resource

bacon = Bacon.first
bacon.description = "My new description"
bacon.diff #=> { :description => { :local => "My new description", :remote => "Old description" } }

@note This is a VERY expensive operation - use it sparringly!

@return [Hash]

# File lib/chef-api/resources/base.rb, line 862
def diff
  diff = {}

  remote = self.class.fetch(id, _prefix) || self.class.new({}, _prefix)
  remote._attributes.each do |key, value|
    unless _attributes[key] == value
      diff[key] = { local: _attributes[key], remote: value }
    end
  end

  diff
end
dirty?() click to toggle source

Check if the local resource is in sync with the remote Chef Server. When a remote resource is updated, ChefAPI has no way of knowing it's cached resources are dirty unless additional requests are made against the remote Chef Server and diffs are compared.

@example when the resource is out of sync with the remote Chef Server

bacon = Bacon.first
bacon.description = "I'm different, yeah, I'm different!"
bacon.dirty? #=> true

@example when the resource is in sync with the remote Chef Server

bacon = Bacon.first
bacon.dirty? #=> false

@return [Boolean]

true if the local resource has differing attributes from the same
resource on the remote Chef Server, false otherwise
# File lib/chef-api/resources/base.rb, line 841
def dirty?
  new_resource? || !diff.empty?
end
errors() click to toggle source

The collection of errors on the resource.

@return [ErrorCollection]

# File lib/chef-api/resources/base.rb, line 907
def errors
  @errors ||= ErrorCollection.new
end
id() click to toggle source

The unique id for this resource.

@return [Object]

# File lib/chef-api/resources/base.rb, line 593
def id
  _attributes[primary_key]
end
ignore_attribute?(key) click to toggle source

Determine if a given attribute should be ignored. Ignored attributes are defined at the schema level and are frozen.

@param [Symbol] key

the attribute to check ignorance

@return [Boolean]

# File lib/chef-api/resources/base.rb, line 898
def ignore_attribute?(key)
  @schema.ignored_attributes.has_key?(key.to_sym)
end
inspect() click to toggle source

Custom inspect method for easier readability.

@return [String]

# File lib/chef-api/resources/base.rb, line 948
def inspect
  attrs = (_prefix).merge(_attributes).map do |key, value|
    if value.is_a?(String)
      "#{key}: #{Util.truncate(value, length: 50).inspect}"
    else
      "#{key}: #{value.inspect}"
    end
  end

  "#<#{self.class.classname} #{attrs.join(', ')}>"
end
new_resource?() click to toggle source

Check if this resource exists on the remote Chef Server. This is useful when determining if a resource should be saved or updated, since a resource must exist before it can be saved.

@example when the resource does not exist on the remote Chef Server

bacon = Bacon.new
bacon.new_resource? #=> true

@example when the resource exists on the remote Chef Server

bacon = Bacon.first
bacon.new_resource? #=> false

@return [Boolean]

true if the resource exists on the remote Chef Server, false otherwise
# File lib/chef-api/resources/base.rb, line 818
def new_resource?
  !self.class.exists?(id, _prefix)
end
primary_key() click to toggle source

The primary key for the resource.

@return [Symbol]

the primary key for this resource
# File lib/chef-api/resources/base.rb, line 584
def primary_key
  @schema.primary_key
end
protected?() click to toggle source

Determine if this current resource is protected. Resources may be protected by name or by a Proc. A protected resource is one that should not be modified (i.e. created/updated/deleted) by the user. An example of a protected resource is the pivotal key or the chef-webui client.

@return [Boolean]

# File lib/chef-api/resources/base.rb, line 634
def protected?
  @protected ||= self.class.protected_resources.any? do |thing|
                   if thing.is_a?(Proc)
                     thing.call(self)
                   else
                     id == thing
                   end
                 end
end
reload!() click to toggle source

Reload (or reset) this object using the values currently stored on the remote server. This method will also clear any cached collection proxies so they will be reloaded the next time they are requested. If the remote record does not exist, no attributes are modified.

@note This will remove any custom values you have set on the resource!

@return [self]

the instance of the reloaded record
# File lib/chef-api/resources/base.rb, line 655
def reload!
  associations.clear

  remote = self.class.fetch(id, _prefix)
  return self if remote.nil?

  remote._attributes.each do |key, value|
    update_attribute(key, value)
  end

  self
end
resource_path() click to toggle source

The URL for this resource on the Chef Server.

@example Get the resource path for a resource

bacon = Bacon.first
bacon.resource_path #=> /bacons/crispy

@return [String]

the partial URL path segment
# File lib/chef-api/resources/base.rb, line 885
def resource_path
  self.class.resource_path(id, _prefix)
end
save() click to toggle source

Commit the resource and any changes to the remote Chef Server. Any errors are gracefully handled and added to the resource's error collection for handling.

@return [Boolean]

true if the save was successfuly, false otherwise
# File lib/chef-api/resources/base.rb, line 706
def save
  save!
rescue
  false
end
save!() click to toggle source

Commit the resource and any changes to the remote Chef Server. Any errors will raise an exception in the main thread and the resource will not be committed back to the Chef Server.

Any response errors (such as server-side responses) that ChefAPI failed to account for in validations will also raise an exception.

@return [Boolean]

true if the resource was saved
# File lib/chef-api/resources/base.rb, line 679
def save!
  validate!

  response = if new_resource?
               self.class.post(to_json, _prefix)
             else
               self.class.put(id, to_json, _prefix)
             end

  # Update our local copy with any partial information that was returned
  # from the server, ignoring an "bad" attributes that aren't defined in
  # our schema.
  response.each do |key, value|
    update_attribute(key, value) if attribute?(key)
  end

  true
end
to_hash() click to toggle source

The hash representation of this resource. All attributes are serialized and any values that respond to to_hash are also serialized.

@return [Hash]

# File lib/chef-api/resources/base.rb, line 917
def to_hash
  {}.tap do |hash|
    _attributes.each do |key, value|
      hash[key] = value.respond_to?(:to_hash) ? value.to_hash : value
    end
  end
end
to_json(*) click to toggle source

The JSON serialization of this resource.

@return [String]

# File lib/chef-api/resources/base.rb, line 930
def to_json(*)
  JSON.fast_generate(to_hash)
end
to_s() click to toggle source

Custom ::to_s method for easier readability.

@return [String]

# File lib/chef-api/resources/base.rb, line 939
def to_s
  "#<#{self.class.classname} #{primary_key}: #{id.inspect}>"
end
update(attributes = {}) click to toggle source

Update a subset of attributes on the current resource. This is a handy way to update multiple attributes at once.

@param [Hash] attributes

the list of attributes to update

@return [self]

# File lib/chef-api/resources/base.rb, line 732
def update(attributes = {})
  attributes.each do |key, value|
    update_attribute(key, value)
  end

  self
end
update_attribute(key, value) click to toggle source

Update a single attribute in the attributes hash.

@raise

# File lib/chef-api/resources/base.rb, line 745
def update_attribute(key, value)
  unless attribute?(key.to_sym)
    raise Error::UnknownAttribute.new(attribute: key)
  end

  _attributes[key.to_sym] = value
end
valid?() click to toggle source

Determine if the current resource is valid. This relies on the validations defined in the schema at initialization.

@return [Boolean]

true if the resource is valid, false otherwise
# File lib/chef-api/resources/base.rb, line 792
def valid?
  errors.clear

  validators.each do |validator|
    validator.validate(self)
  end

  errors.empty?
end
validate!() click to toggle source

Run all of this resource's validations, raising an exception if any validations fail.

@raise [Error::InvalidResource]

if any of the validations fail

@return [Boolean]

true if the validation was successful - this method will never return
anything other than true because an exception is raised if validations
fail
# File lib/chef-api/resources/base.rb, line 776
def validate!
  unless valid?
    sentence = errors.full_messages.join(', ')
    raise Error::InvalidResource.new(errors: sentence)
  end

  true
end
validators() click to toggle source

The list of validators for this resource. This is primarily set and managed by the underlying schema clean room.

@return [Array<~Validator::Base>]

the list of validators for this resource
# File lib/chef-api/resources/base.rb, line 760
def validators
  @validators ||= @schema.validators
end