Bash completion for Consul nodes on Ubuntu

After making the jump to Ubuntu as my preferred distribution, I've admittedly become addicted to bash completions (aka autocomplete, tab completion, typeahead). Bash completions provide immediate hints for common commands, and even the options associated with them.

For example, a quick \t\t (double-tap of the tab key) after the service command will provide a quick snapshot of services available on the machine, beginning with your query.

someone@example:~$ sudo service a
acpid  anacron  apache2  atd

As with most things, seemingly small details, like typeahead completions, can make for a very useful productivity improvement.


Consul

I have been utilizing the Consul agent from HashiCorp for service discovery inside an AWS VPC. Consul is a well-thought-out, distributed, service discovery agent that accomplishes a few things quite elegantly:

  • Determines location of services via CLI and DNS
  • Keeps availability status up-to-date with health checks
  • Stores facts that can be shared across instances
  • Provides performant localhost access to everything
  • Easy to setup, highly configurable, and resilient

Though individually these features seem pretty straight-forward, the combined elements of service discovery can be incredibly complex in dynamic environments, and challenging to keep synchronized. You can read more about Consul on their website.


Bash Completion

Bash completion on Ubuntu provides a configuration directory for adding your own complete handlers. In this example, you will need to have the consul agent binary accessible in your path.

someone@example:~$ consul members
Node       Address          Status  Type    Build  Protocol  DC
dev-web    10.10.0.17:8301  alive   server  0.6.4  2         dc-east
dev-mysql  10.10.0.44:8301  alive   client  0.6.4  2         dc-east
qa-web     10.10.0.63:8301  failed  client  0.6.4  2         dc-east

The consul members command will provide a list of nodes alongside a status of: alive, failed, or left. You may choose to filter out different statuses from your completion. I only filter out left because it indicates a proper shutdown and removal for my use case. The failed status nodes are something I would still want access to.

We will create our completion script here:

# /etc/bash_completion.d/ssh_consul

With the following contents:

#!/bin/bash
_consul_members()
{
  local cur
  local res
  cur=${COMP_WORDS[COMP_CWORD]}
  res=$(consul members | grep -v ' left ' | tail -n +2 | cut -f1 -d ' ' | sort -u)
  COMPREPLY=($(compgen -W "${res}" -- ${cur}))
}
complete -F _consul_members ssh

By default, Consul uses the node.consul domain to access nodes. You have some options for resolving the domain:

  • Make the domain accessible using the consul local DNS
  • Use your own hostnames as the full name of the node
  • Or, suffix the COMPREPLY variable with an existing domain (hack)

I have setup dnsmasq to forward the .consul domain requests to the consul DNS server. And, added domain search resolution here:

# /etc/resolvconf/resolv.conf.d/base
search node.consul

This will attempt to resolve hostnames matching the name of the consul node, for example: ssh dev-web would attempt to resolve the hostname {dev-web}.node.consul through the local DNS server.


Putting It All Together

After saving the completion config, we'll need to reinitialize our bash session for it to take effect. Now our ssh command will provide immediate and accurate insights into what instances are available.

someone@example:~$ ssh dev-
dev-foo-20  dev-foo-21  dev-mysql  dev-web

Gentoo has provided some excellent documentation for bash completions here. You can write some pretty advanced completion scripts, if one were so inclined.

I highly recommend managing your Consul implementation with Ansible, or a similar configuration management tool. If you have questions or suggestions, please feel free to get in touch via the appropriate social outlet.