chef -- pass an attribute hash to a resource - ruby

chef has many resources\providers\definitions, for each of which there are attributes that can be set. for instance, see this and this.
by examine few definitions, it is cleat the the attributes given for a specific resource\provider\definition are packed into a hash pointed by the param variable.
i was wondering whether there is the ability to use a resource\provider\definition without unpacking a hash. here is a pseudo-code or my intentions:
attr = { :name => "/tmp/folder", :owner => "root", :group => "root", :mode => 0755, :action => :create }
directory attr
instead of writing it as follows:
directory "/tmp/folder" do
owner "root"
group "root"
mode 0755
action :create
end
is there a native way of achieving something alike?
thanks you, roth.

You can try the following
attrs = { .. }
directory "/tmp/folder" do
attrs.each do |method_name, value|
send(method_name, value)
end
end
More about Ruby's send: http://apidock.com/ruby/Object/send

by examine few definitions, it is cleat the the attributes given for a specific resource\provider\definition are packed into a hash pointed by the param variable.
This is only true for definitions.
In the case of resources the common attributes (retries/actions/etc) are a mixture of attributes and methods in the Chef::Resource class (super class of all resources). For the resource specific attributes they are typically defined as methods on the resource in question. In the case of LWRPs Chef will generate a class behind the scenes and add each of the attributes as methods to that class.
i was wondering whether there is the ability to use a resource\provider\definition without unpacking a hash.
The Chef::Resource class has a json_create method, so assuming you converted your hash to JSON it may be possible. More generally I'm curious as to the reason for wanting to do this as I believe that it will make your recipes harder to understand.

Related

Sharing a class instance between two classes

I have two different classes that both represent objects that need to be persisted to my database and now I want to share the database client object between the two classes. I want to avoid instantiating the client object more than once.
Currently I do this by using a global variable
$client = Mysql2::Client.new(:database => "myDb", :user => "user", :password => "password", :host => "localhost")
class Person
def save
$client.query("INSERT INTO persons")
end
end
class Car
def save
$client.query("INSERT INTO cars")
end
end
This works, but I am wondering if there are more correct ways to do this and why they are more correct?
You can inherit from a parent class. This allows you to share common functionality across objects and follows DRY (do not repeat yourself) programming principles. It will also allow you to protect your DB connection with locks, resuces, queues, pools, and whatever else you may want to do without having to worry about it in your children classes
class Record
#table_name = nil
##client = Mysql2::Client.new(:database => "myDb", :user => "user", :password => "password", :host => "localhost")
def save
##client.query("INSERT INTO #{#table_name}") if #table_name
end
end
class Person < Record
#table_name = "persons"
end
class Car < Record
#table_name = "cars"
end
While we are on the subject, you should look at using ActiveRecord for handling your database models and connections. It already does pretty much anything you'll need and will be more compatible with other gems already out there. It can be used without rails.
As an alternative on using inheritance, why not consider a simple Singleton pattern? This could make your models cleaner, by separating the responsibility outside your classes. And eliminating the need for inheritance.
The example below illustrates this. Only one, single instance of the DataManager class can exist. So, you'll only instantiate it once - but can use it everywhere:
require 'singleton'
class DataManager
include Singleton
attr_accessor :last_run_query
def initialize()
if #client.nil?
p "Initialize the Mysql client here - note that this'll only be called once..."
end
end
def query(args)
# do your magic here
#last_run_query = args
end
end
Next, calling it using the .instance accessor is a breeze - and will always point to one single instance, like so:
# Fetch, or create a new singleton instance
first = DataManager.instance
first.query('drop table mother')
p first.last_run_query
# Again, fetch or create a new instance
# this'll actually just fetch the first instance from above
second = DataManager.instance
p second.last_run_query
# last line prints: "drop table mother"
For the record, the Singleton pattern can have some downsides and using it frequently results in a never-ending debate on whether you should use it or not. But in my opinion it's a decent alternative to your specific question.

Split Grape API (non-Rails) into different files

I am writing an API in Grape, but it stands alone, with no Rails or Sinatra or anything. I'd like to split the app.rb file into separate files. I have looked at How to split things up in a grape api app?, but that is with Rails.
I'm not sure how to make this work with modules or classes — I did try subclassing the different files into my big GrapeApp, but that was ugly and I'm not even sure it worked properly. What's the best way to do this?
I currently have versions split by folders (v1, v2, etc) but that is all.
You don't need to subclass from your main app, you can just mount separate Grape::API sub-classes inside the main one. And of course you can define those classes in separate files, and use require to load in all the routes, entities and helpers that you app might need. I have found it useful to create one mini-app per "domain object", and load those in app.rb, which looks like this:
# I put the big list of requires in another file . .
require 'base_requires'
class MyApp < Grape::API
prefix 'api'
version 'v2'
format :json
# Helpers are modules which can have their own files of course
helpers APIAuthorisation
# Each of these routes deals with a particular sort of API object
group( :foo ) { mount APIRoutes::Foo }
group( :bar ) { mount APIRoutes::Bar }
end
I arrange files in folders, fairly arbitrarily:
# Each file here defines a subclass of Grape::API
/routes/foo.rb
# Each file here defines a subclass of Grape::Entity
/entities/foo.rb
# Files here marshal together functions from gems, the model and elsewhere for easy use
/helpers/authorise.rb
I would probably emulate Rails and have a /models/ folder or similar to hold ActiveRecord or DataMapper definitions, but as it happens that is provided for me in a different pattern in my current project.
Most of my routes look very basic, they just call a relevant helper method, then present an entity based on it. E.g. /routes/foo.rb might look like this:
module APIRoutes
class Foo < Grape::API
helpers APIFooHelpers
get :all do
present get_all_users_foos, :with => APIEntity::Foo
end
group "id/:id" do
before do
#foo = Model::Foo.first( :id => params[:id] )
error_if_cannot_access! #foo
end
get do
present #foo, :with => APIEntity::Foo, :type => :full
end
put do
update_foo( #foo, params )
present #foo, :with => APIEntity::Foo, :type => :full
end
delete do
delete_foo #foo
true
end
end # group "id/:id"
end # class Foo
end # module APIRoutes

How to implement a dynamic attribute default in chef LWRP definition

I would like to be able to define a lightweight resource with let's say 3 parameters, two of them being basic/elementary parameters and the third being a combination of these two. I would also like to provide a possibility of customization of the third parameter. For example:
How to modify following code to achieve above behaviour for the full_name attribute:
resource definition:
actions :install
attribute :name, :kind_of => String, :name_attribute => true
attribute :version, :kind_of => String
attribute :full_name, :kind_of => String
provider definition:
action :install do
Chef::Log.info "#{new_resource.full_name}"
end
I would like to see different outputs for different resource directives, e.g.:
resource "abc" do
version "1.0.1"
end
will result in abc-1.0.1, but:
resource "def" do
version "0.1.3"
full_name "completely_irrelevant"
end
will result in completely_irrelevant.
Is there a possibility to define this behaviour in the resource definition (probably through the default parameter) or I am able to do it in provider definition only? If the second is true, then can I store the calculated value in the new_resource object's full_name attribute (the class seems to miss the full_name= method definition) or I have to store it in a local variable?
Update
Thanks to Draco's hint, I realized that I can create an accessor method in the resource file and calculate the full_name value on the fly when requested. I would prefer a cleaner solution but it's much better than calculating it in action implementation.
Chef version
Chef: 10.16.4
Setting #full_name in constructor, similar to providing default action in chef < 0.10.10, as written in wiki, does not work, because #version is not set at that point yet.
def initialize( name, run_context=nil )
super
#full_name ||= "%s-%s" % [name, version]
end
So we have to overwrite full_name method in resource by adding
def full_name( arg=nil )
if arg.nil? and #full_name.nil?
"%s-%s" % [name, version]
else
set_or_return( :full_name, arg, :kind_of => String )
end
end
into resource definition. That works. Tested.
attribute :full_name, :kind_of => String, default => lazy {|r| "#{r.name}-#{r.version}" }
After fighting this for some time, I found this to work cleanly.
attribute :eman, String, default: lazy {|r| r.name.reverse }
The part that was missing for me was the |r| parameter to the lazy block.
https://docs.chef.io/resource_common.html#lazy-evaluation

field vs method ruby on rails

I have this class:
class User
include Mongoid::Document
field :revenues, :type => Integer, :default => nil
attr_accessible :revenues
#now method
def revenues
return 1
end
end
Why in console I get 1 instead nil?
1.9.3-p125 :002 > u.revenues
=> 1
Which has priority, the method or the field? How can I created a method with the same features that a field?
The field macro is defined in Mongoid::Document. It is neither a syntatic feature from Ruby nor from Rails.
What's happening with your code is the following:
The field function creates for you some methods, one of them is called revenues.
When you create another method called revenues, you are in effect overwriting the previously defined method, therefore making it useless.
Short answer: I don't understand a zip about Mongoid, but chances are that your field still exists even after you defined oce again a method named revenues. The only drawback is that you cannot access it by calling myUser.revenues anymore.
Try to make a test: access your field with the notation some_user[:revenues] and see what happen :)
Best regards

data_mapper, attr_accessor, & serialization only serializing properties not attr_accessor attributes

I'm using data_mapper/sinatra and trying to create some attributes with attr_accessor. The following example code:
require 'json'
class Person
include DataMapper::Resource
property :id, Serial
property :first_name, String
attr_accessor :last_name
end
ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json
produces this output:
"{\"id\":null,\"first_name\":\"Mike\"}"
Obviously I would like for it to give me both the first and last name attributes. Any ideas on how to get this to work in the way one would expect so that my json has all of the attributes?
Also, feel free to also explain why my expectation (that I'd get all of the attributes) is incorrect. I'm guessing some internal list of attributes isn't getting the attr_accessor instance variables added to it or something. But even so, why?
Datamapper has it’s own serialization library, dm-serializer, that provides a to_json method for any Datamapper resource. If you require Datamapper with require 'data_mapper' in your code, you are using the data_mapper meta-gem that requires dm-serializer as part of it’s set up.
The to_json method provided by dm-serializer only serializes the Datamapper properties of your object (i.e. those you’ve specified with property) and not the “normal” properties (that you’ve defined with attr_accessor). This is why you get id and first_name but not last_name.
In order to avoid using dm-serializer you need to explicitly require those libraries you need, rather than rely on data_mapper. You will need at least dm-core and maybe others.
The “normal” json library doesn’t include any attributes in the default to_json call on an object, it just uses the objects to_s method. So in this case, if you replace require 'data_mapper' with require 'dm-core', you will get something like "\"#<Person:0x000001013a0320>\"".
To create json representations of your own objects you need to create your own to_json method. A simple example would be to just hard code the attributes you want in the json:
def to_json
{:id => id, :first_name => first_name, :last_name => last_name}.to_json
end
You could create a method that looks at the attributes and properties of the object and create the appropriate json from that instead of hardcoding them this way.
Note that if you create your own to_json method you could still call require 'data_mapper', your to_json will replace the one provided by dm-serializer. In fact dm-serializer also adds an as_json method that you could use to create the combined to_json method, e.g.:
def to_json
as_json.merge({:last_name => last_name}).to_json
end
Thanks to Matt I did some digging and found the :method param for dm-serializer's to_json method. Their to_json method was pretty decent and was basically just a wrapper for an as_json helper method so I overwrote it by just adding a few lines:
if options[:include_attributes]
options[:methods] = [] if options[:methods].nil?
options[:methods].concat(model.attributes).uniq!
end
The completed method override looks like:
module DataMapper
module Serializer
def to_json(*args)
options = args.first
options = {} unless options.kind_of?(Hash)
if options[:include_attributes]
options[:methods] = [] if options[:methods].nil?
options[:methods].concat(model.attributes).uniq!
end
result = as_json(options)
# default to making JSON
if options.fetch(:to_json, true)
MultiJson.dump(result)
else
result
end
end
end
end
This works along with an attributes method I added to a base module I use with my models. The relevant section is below:
module Base
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def attr_accessor(*vars)
#attributes ||= []
#attributes.concat vars
super(*vars)
end
def attributes
#attributes || []
end
end
def attributes
self.class.attributes
end
end
now my original example:
require 'json'
class Person
include DataMapper::Resource
include Base
property :id, Serial
property :first_name, String
attr_accessor :last_name
end
ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json :include_attributes => true
Works as expected, with the new option parameter.
What I could have done to selectively get the attributes I wanted without having to do the extra work was to just pass the attribute names into the :methods param.
p ps.to_json :methods => [:last_name]
Or, since I already had my Base class:
p ps.to_json :methods => Person.attributes
Now I just need to figure out how I want to support collections.

Resources