# File lib/chef-api/resources/base.rb, line 584 def primary_key @schema.primary_key end
The list of associations.
@return [Hash]
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 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
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
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
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
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
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
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 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 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
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
(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
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
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 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
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
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
@todo doc
# File lib/chef-api/resources/base.rb, line 22 def from_url(url, prefix = {}) from_json(connection.get(url), prefix) end
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
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
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
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
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 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
@todo doc
# File lib/chef-api/resources/base.rb, line 95 def protected_resources @protected_resources ||= [] end
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
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
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
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
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
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
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
@todo doc
# File lib/chef-api/resources/base.rb, line 600 def _prefix @_prefix end
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
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
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
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
The collection of errors on the resource.
@return [ErrorCollection]
# File lib/chef-api/resources/base.rb, line 907 def errors @errors ||= ErrorCollection.new end
The unique id for this resource.
@return [Object]
# File lib/chef-api/resources/base.rb, line 593 def id _attributes[primary_key] end
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
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
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
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
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 (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
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
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
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
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
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
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 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 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
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
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
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