capistrano/capistrano

View on GitHub
docs/documentation/advanced-features/properties/index.markdown

Summary

Maintainability
Test Coverage
---
title: Properties
layout: default
---

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:

```ruby
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:

```ruby
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:

```ruby
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](/documentation/advanced-features/property-filtering)) 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](/documentation/advanced-features/property-filtering)) and returns an array of
hashes containing the properties with the keys `:hostname` and `:role` added:

```ruby
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:

```ruby
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.