Ridley

Build Status Dependency Status Code Climate

A reliable Chef API client with a clean syntax

Installation

$ gem install ridley

Known Issues

  • Sandboxes has not been implemented
  • Full support for Cookbooks is not included
  • Acceptance test suite needs to be refactored

Usage

Require Ridley into your application

require 'ridley'

Creating a new connection

conn = Ridley::Connection.new(
  server_url: "https://api.opscode.com",
  client_name: "reset",
  client_key: "/Users/reset/.chef/reset.pem",
  organization: "ridley"
)

Creating a new connection requires you to specify at minimum:

  • server_url
  • client_name
  • client_key

An optional organization option can be specified if you are working with Hosted or Private Chef (OHC/OPC). For a full list of available options see the yard documentation.

NOTE: You do not want to specify an organization if you are connecting to an Open Source Chef server.

Connections can also be instantiated by a helper function: Ridley.connection

Ridley.connection(
  server_url: "https://api.opscode.com",
  client_name: "reset",
  client_key: "/Users/reset/.chef/reset.pem",
  organization: "ridley"
)

Using a connection object you can interact with collections of resources on a Chef server. Resources are:

  • Nodes
  • Roles
  • Environments
  • Clients
  • Cookbooks
  • Data Bags

Here is a simple example of instantiating a new connection and listing all of the roles on a Chef server.

conn = Ridley.connection(...)
conn.role.all => []

For more information scroll down to the Manipulating Chef Resources section of this README.

Synchronous execution

An alternative syntax is provided if you want to perform multiple requests, in order, on a connection.

conn = Ridley.connection(...)

conn.sync do
  role.all
  role.find("reset")
  role.create(name: "ridley-test")
  role.delete("reset")
end

The sync function on the connection object takes a block with no arguments and allows you to access the DSL within the block. You can address any one of the resources within the sync block:

conn.sync do
  environment.all
  node.all
  ...
end

A helper function exists to allow you to express yourself in a one-liner: Ridley.sync

Ridley.sync(server_url: "https://api.opscode.com", ...) do
  role.all => []
end

Asynchronous execution

COMING SOON

Manipulating Chef Resources

All resource can be listed, created, retrieved, updated, or destroyed. Some resources have additional functionality described in their documentation.

Listing all resources

You use a connection to interact with the resources on the remote Chef server it is pointing to. For example, if you wanted to get a list of all of the roles on your Chef server:

conn = Ridley.connection(...)
conn.role.all           => []

Calling role.all on the connection object will return an array of Ridley::Role objects. All of the resources can be listed, not just Roles:

conn = Ridley.connection(...)
conn.node.all           => [<#Ridley::Node>]
conn.role.all           => [<#Ridley::Role>]
conn.environment.all    => [<#Ridley::Environment>]
conn.client.all         => [<#Ridley::Client>]
conn.cookbook.all       => [<#Ridley::Cookbook>]
conn.data_bag.all       => [<#Ridley::DataBag>]

Creating a resource

A new resource can be created in a few ways

Create by instantiate and save

conn = Ridley.connection(...)
obj = conn.role.new

obj.name = "reset"
obj.save => <#Ridley::Role: @name="reset">

Create by the create function with attribute hash

conn = Ridley.connection(...)
conn.role.create(name: "reset") => <#Ridley::Role: @name="reset">

Create by the create function with a resource object

conn = Ridley.connection(...)
obj = conn.role.new

obj.name = "reset"
conn.role.create(obj) => <#Ridley::Role: @name="reset">

Each of these methods is identical, it is up to you on how you'd like to create new resources.

Retrieving a resource

There are two functions for retrieving a resource. find and find!. If you are familiar with ActiveRecord; these are the functions used to pull records out of the database.

Both find and find! will return a resource but if the resource is not found on the Chef server find! will raise an exception while find will return nil.

If you were following allong in the previous section we created a role named reset. We'll assume that role has been created in this next example.

conn = Ridley.connection(...)

conn.role.find("reset") => <#Ridley::Role: @name="reset">
conn.role.find!("reset") => <#Ridley::Role: @name="reset">

Now if we attempt to find a role that does not exist on the Chef server

conn = Ridley.connection(...)

conn.role.find("not_there") => nil
conn.role.find!("not_there") =>
Ridley::Errors::HTTPNotFound: errors: 'Cannot load role reset'
  from /Users/reset/code/ridley/lib/ridley/middleware/chef_response.rb:11:in `on_complete'
  from /Users/reset/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/faraday-0.8.1/lib/faraday/response.rb:9:in `block in call'
  from /Users/reset/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/faraday-0.8.1/lib/faraday/response.rb:63:in `on_complete'
  from /Users/reset/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/faraday-0.8.1/lib/faraday/response.rb:8:in `call'
  from /Users/reset/code/ridley/lib/ridley/middleware/chef_auth.rb:31:in `call'
  from /Users/reset/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/faraday-0.8.1/lib/faraday/connection.rb:226:in `run_request'
  from /Users/reset/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/faraday-0.8.1/lib/faraday/connection.rb:87:in `get'
  from /Users/reset/code/ridley/lib/ridley/resource.rb:115:in `find!'
  from /Users/reset/code/ridley/lib/ridley/context.rb:22:in `method_missing'
  from (irb):6
  from /Users/reset/.rbenv/versions/1.9.3-p194/bin/irb:12:in `<main>'

Updating a resource

Like creating a resource, updating a resource can also be expressed a few different ways

Update by the update function with an id and attribute hash

conn = Ridley.connection(...)
conn.role.update("reset", description: "testing updates!") => <#Ridley::Role: @name="reset", @description="testing updates!">

Update by the update function with a resource object

conn = Ridley.connection(...)
obj = conn.role.find("reset")
obj.description = "resource object!"

conn.role.update(obj) => <#Ridley::Role: @name="reset", @description="resource object!">

Update by saving a resource object

conn = Ridley.connection(...)
obj = conn.role.find("reset")

obj.description = "saving an object!"
obj.save => <#Ridley::Role: @name="reset", @description="saving an object!">

Deleting a resource

Like creating or updating a resource, there are a few ways deleting a resource can be expressed

Delete by the delete function with an id

conn = Ridley.connection(...)
conn.role.delete("reset") => <#Ridley::Role: @name="reset">

Delete by the delete function with a resource object

conn = Ridley.connection(...)
obj = conn.role.find("reset")

conn.role.delete(obj) => <#Ridley::Role: @name="reset">

Delete by the destroy function on a resource object

conn = Ridley.connection(...)
obj = conn.role.find("reset")

obj.destroy => true

Regenerating a client's private key

Regenerate function on a context with an id

conn = Ridley.connection(...)
conn.client.regenerate_key("jwinsor") => <#Ridley::Client: @name="jwinsor", @private_key="HIDDEN">

Regenerate function on an instantiated resource object

conn = Ridley.connection(...)
obj = conn.client.find("jwinsor")

obj.regenerate_key => <#Ridley::Client: @name="jwinsor", @private_key="HIDDEN">

Manipulating Data Bags and Data Bag Items

A data bag is managed exactly the same as any other Chef resource

conn = Ridley.connection(...)
conn.data_bag.create("ridley-test")

You can create, delete, update, or retrieve a data bag exactly how you would expect if you read through the Manipulating Chef Resources portion of this document.

Unlike a role, node, client, or environment, a data bag is a container for other resources. These other resources are Data Bag Items. Data Bag Items behave slightly different than other resources. Data Bag Items can have an abritrary attribute hash filled with any key values that you would like. The one exception is that every Data Bag Item requires an 'id' key and value. This identifier is the name of the Data Bag Item.

Creating a Data Bag Item

conn = Ridley.connection(...)
data_bag = conn.data_bag.create("ridley-test")

data_bag.item.create(id: "appconfig", host: "reset.local", user: "jwinsor") => 
  <#Ridley::DataBagItem: @id="appconfig", @host="reset.local", @user="jwinsor">

Saving a Data Bag Item

conn = Ridley.connection(...)
data_bag = conn.data_bag.create("ridley-test")

dbi = data_bag.item.new
dbi[:id] = "appconfig"
dbi[:host] = "reset.local"
dbi.save => true

Searching

conn = Ridley.connection(...)
conn.search(:node)
conn.search(:node, "name:ridley-test.local")

Search will return an array of Ridley resource objects if one of the default indices is specified. Chef's default indices are

  • node
  • role
  • client
  • environment

Manipulating Attributes

Using Ridley you can quickly manipulate node or environment attributes. Attributes are identified by a dotted path notation.

default[:my_app][:billing][:enabled] => "my_app.billing.enabled"

Given the previous example you could set the default node attribute with the set_default_attribute function on a Node object

Node Attributes

Setting the default[:my_app][:billing][:enabled] node level default attribute on the node "jwinsor-1"

conn = Ridley.connection
conn.sync do
  obj = node.find("jwinsor-1")
  obj.set_default_attribute("my_app.billing.enabled", false)
  obj.save
end

Other attribute precedence levels can be set with their own respective set attribute functions

conn = Ridley.connection
conn.sync do
  obj = node.find("jwinsor-1")
  obj.set_override_attribute("my_app.proxy.enabled", false)
  obj.set_normal_attribute("my_app.webapp.enabled", false)
  obj.save
end

Environment Attributes

Setting a default environment attribute is just like setting a node level default attribute

conn = Ridley.connection
conn.sync do
  obj = environment.find("production")
  obj.set_default_attribute("my_app.proxy.enabled", false)
  obj.save
end

And the same goes for setting an environment level override attribute

conn = Ridley.connection
conn.sync do
  obj = environment.find("production")
  obj.set_override_attribute("my_app.webapp.enabled", false)
  obj.save
end

Role Attributes

conn = Ridley.connection
conn.sync do
  obj = role.find("why_god_why")
  obj.set_default_attribute("my_app.proxy.enabled", false)
  obj.save
end

conn.sync do
  obj = role.find("why_god_why")
  obj.set_override_attribute("my_app.webapp.enabled", false)
  obj.save
end

Bootstrapping nodes

conn = Ridley.connection(
  server_url: "https://api.opscode.com",
  organization: "vialstudios",
  validator_client: "vialstudios-validator",
  validator_path: "/Users/reset/.chef/vialstudios-validator.pem",
  ssh: {
    user: "vagrant",
    password: "vagrant"
  }
)

conn.node.bootstrap("33.33.33.10", "33.33.33.11")

Authors and Contributors