Properties

Server objects in Capistrano essentially consist of a name and a hash: The name is the DNS name (or IP address) and the hash contains the ‘Properties’ of the server. These properties are of two sorts: ones required by Capistrano (Capistrano Properties) and ones available for use by the Application (Custom Properties). These share the same namespace (there is only one underlying hash!) so the names of custom properties are restricted.

Capistrano Properties

The Capistrano properties are those used to SSH into the server and those that support the basic role functionality. These are:

  • :user - the name of the SSH user for the server
  • :password - for the SSH user
  • :port - the port number of the SSH daemon on the server
  • :roles - an array of rolenames
  • :ssh_options - a hash of SSH parameters (see below)
  • :primary - a boolean that indicates whether the server should be considered primary or not.

The :user, :port and :password may be specified as follows:

  • As part of the hostname in the form ‘user@host:port’ without a password,
  • In the properties :user, :password and :port, and
  • In the property :ssh_options (with the same keys)

Precedence

The SSH related properties are set with the following precedence, beginning with the highest:

  • Property declarations on the server or role. The last property declaration overrides all the previous server or role declarations
  • Values specified in the hostname string
  • Values in the server or role :ssh_options property
  • The stage global variable :ssh_options
  • The SSHKit backend ssh_options
  • The settings in your local ~/.ssh/config file

Note however that defaults taken from these places will not be reflected back into the server properties, so host.user will be nil if a lower precedence default is being used.

Custom Properties

When using Capistrano as a general purpose deployment framework (above and beyond it’s traditional use for Rails deployments) it becomes important to be able to store additional parameters. You can think of Capistrano as an MVC framework for deployments, where the stage file (representing all the relationships between application components) is the Model, the tasks (enabling model changes to be actioned) are the Controllers, and the actual physical embodiments (typically configuration files on running servers) are the Views.

Property Access from within Tasks

The properties on Capistrano server are accessible programmatically from a Capistrano task. Capistrano properties are available through methods on the host object itself and Custom properties via methods on the properties attribute of the host.

These methods have the expected names: user, port and so on. An exception is the ssh_config which is available via the netssh_options method.

The following feature is new in Capistrano 3.3.6 and above.

Within the scope of an on() block, the host that is yielded is a copy of the underlying host, which allows you to temporarily override any of the properties by calling the setter method. An example is:

on roles(:all) do |host|
  host.user = 'root'
  host.password = 'supersecret'
  execute :yum, 'makecache'
end

This temporarily sets the SSH user to ‘root’ (with an appropriate password) without affecting the SSH user defined for the server in the configuration.

Property setting in Complex Configurations

As configurations involve more servers it helps to be able to define a set of properties at the role level, and have those be overridden by a later definition at the server level. This keeps your configuration as DRY as possible. A typical requirement is defining a set of Redis servers which all have the same port parameter and are all slaves except for one which is the master.

To allow this properties can be set at both the Server and Role level. The guiding principle is that the properties are merged and that the last definition wins. In practice we finesse this slightly depending on the type of the properties value:

  • scalar values will be overridden
  • hash values will have their keys merged with duplicate keys taking on the value of the last one.
  • array values will have subsequent entries appended to the array

Example of Server and Role Properties

The above Redis requirement can be met using the following declarations in the stage file:

role :redis, %w{ r1.example.com r2.example.com r3.example.com }, redis: { port: 6379, master: false },
server 'r1.example.com', redis: { port: 6380, master: true }

Conventions for Role Properties

This is complicated by the fact that a single machine may serve multiple roles, and in fact a single machine may need to do the same role twice! An example of this might be in a development situation where you want a single machine to be the database server, a primary Redis server and a slave Redis server.

To solve this problem we adopt a convention for the use of server properties:

  • Server properties for a given role should be stored with the keyname equal to the role. The contents of the property can be a scalar, array or hash.

  • Multiple occurrences of a role on the same server should have the contents be an array, in which the successive elements denote each instance.

The following example shows a configuration with multiple Redis and Sentinel roles on the same server:

server 'dev.local', roles: %w{db web redis sentinel worker}, primary: true,
    redis: [ { name: 'resque', port: 6379, db: 0, downtime: 10, master: true },
             { name: 'resque', port: 6380, db: 0, downtime: 10 } ],
    sentinel: [ { port: 26379 }, { port: 26380 }, { port: 26381 } ]

These properties can be accessed in the ordinary way, but to assist in obtaining them you can use the role_properties() function (see below).

Setting Properties

Properties can be set at both the role and server levels.

Role Properties

The declaration of a role takes an array of server names and a trailing hash of properties. By convention the first server in a role declaration is taken to be the primary, but the :primary property will not actually be set in such a case.

Server Properties

The declaration of a server takes the name of a server and a trailing hash of properties. One of those properties must be :role and have a value which is an array of role names.

Accessing Properties

The roles() Method

The roles() method takes one or more role names (or an array of roles) followed by an optional Property Filter) and returns an array of Capistrano::Configuration::Server objects that belong to those roles. These have the following useful attributes:

  • hostname - a String
  • properties.keys - the names of the available properties
  • properties - a hash-like object that stores the properties. It uses Ruby’s ‘method_missing’ to provide a method for each valid key.
  • roles - a Set of role names as symbols

The servers retrieved by this method are NOT filtered by any host or role filters.

The role_properties() Method

This takes a list of roles (followed by an optional Property Filter) and returns an array of hashes containing the properties with the keys :hostname and :role added:

task :props do
  rps = role_properties(:redis, :sentinel)
  rps.each do |props|
    puts props.inspect
  end
end

# Produces...

{:name=>"resque", :port=>6379, :db=>0, :downtime=>10, :master=>true, :role=>:redis, :hostname=>"dev.local"}
{:name=>"resque", :port=>6380, :db=>0, :downtime=>10, :role=>:redis, :hostname=>"dev.local"}
{:port=>26379, :role=>:sentinel, :hostname=>"dev.local"}
{:port=>26380, :role=>:sentinel, :hostname=>"dev.local"}
{:port=>26381, :role=>:sentinel, :hostname=>"dev.local"}

Alternatively you can supply a block and it will yield the hostname, role and properties:

task :props_block do
  role_properties(:sentinel) do |hostname, role, props|
    puts "Host: #{hostname}, Role: #{role}, #{props.inspect}"
  end
end

# Produces...

Host: dev.local, Role: sentinel, {:port=>26379}
Host: dev.local, Role: sentinel, {:port=>26380}
Host: dev.local, Role: sentinel, {:port=>26381}

Note that unlike on() this function doesn’t cause any remote execution to occur, it is purely for configuration purposes.

Fork me on GitHub