Ansible

ShellHub ships an Ansible collection, shellhub.core, whose dynamic inventory plugin lists the devices in a namespace and connects to each one through the gateway. Ansible treats them like any other SSH host.

Installing the collection

$ git clone https://github.com/shellhub-io/ansible-collection.git
$ ansible-galaxy collection install ./ansible-collection

The inventory

The source file’s name must end in shellhub.yml, which is how Ansible knows to hand it to the plugin:

# inventory.shellhub.yml
plugin: shellhub.core.devices
api_url: https://cloud.shellhub.io
ssh_user: root
ssh_key: ~/.ssh/id_ed25519
keyed_groups:
  - key: shellhub_tags
    prefix: tag

The plugin authenticates with a namespace API key, which also tells the server which namespace to list. Pass it in the environment rather than the file:

$ export SHELLHUB_API_KEY=...
$ ansible-inventory -i inventory.shellhub.yml --graph
$ ansible -i inventory.shellhub.yml tag_production -m ping

Every option can come from the file or the environment:

Option Environment Default
api_url SHELLHUB_API_URL https://cloud.shellhub.io
api_key SHELLHUB_API_KEY (required)
ssh_host SHELLHUB_SSH_HOST the host of api_url
ssh_port SHELLHUB_SSH_PORT 22
ssh_user SHELLHUB_SSH_USER root
ssh_key SHELLHUB_SSH_KEY (none)

What it puts in the inventory

Only accepted devices, paged through the API. For each one:

Variable
ansible_host The gateway. The same for every device
ansible_user The device’s SSHID, <ssh_user>@<namespace>.<device>
shellhub_tags The device’s tags, which is what keyed_groups groups on
shellhub_online Whether it is connected
shellhub_namespace The namespace the key resolved to

ansible_host being identical for every host is the whole point: one inventory reaches machines on networks that have no route to each other, and the device is identified entirely by ansible_user.

Because tags arrive as host facts, a device tagged production lands in the tag_production group with the keyed_groups above — and a device tagged after the fact joins on the next run, with no inventory to edit. See Tags.

Authentication

Ansible presents an SSH key like any client, and that key must be an SSH identity.

Add the key ahead of time. If it is not already an identity, the first connection parks waiting for somebody to approve it in the console — which for a playbook run means it hangs. Nobody is watching for that prompt at 3am.

For scheduled runs, connect as an API key with its own SSH access rather than as a person. The run is then attributed to ansible-runner in the session list, and it does not break when somebody leaves.

Authorization

Every host in the play is authorized separately, as its own connection. A play across fifty devices is fifty authorizations.

So the policy needs to cover the whole set — usually a policy granting the API key a tag, with the unix login the playbook uses:

Name Effect Subject Devices Logins
Ansible to fleet allow API key ansible-runner tag production root

A device the policy misses fails in the play as unreachable, which is the correct behavior and looks like a network problem. If a subset of hosts fails, check the policy before the network.

Performance

Every connection goes through the ShellHub server, so a play against a large fleet has a different shape to one on a LAN. Pipelining helps by reducing round trips:

[ssh_connection]
pipelining = True

ControlMaster also works — see SSH Client Configuration — but note that a persisted connection is authorized once, when it opens.

Where to go next

esc