Specify SSH configuration file at run time in Ansible 1.9


Question

I'm managing two server environments that are configured differently. I access the two environments by specifying different SSH configurations on the command line because I need to specify a different User, ProxyCommand, and a list of other options for SSH.

e.g.

ssh oldserver.example.org -F config_legacy

ssh newserver.example.org -F config

To configure and maintain state on my servers, I've been using Ansible (version 1.9.0.1), which reads an SSH configuration file that is specified by a line in its ansible.cfg:

...
ssh_args =  -F some_configuration_file
...

The ansible.cfg is loaded a number of ways:

def load_config_file():
    ''' Load Config File order(first found is used): ENV, CWD, HOME, /etc/ansible '''

    p = configparser.ConfigParser()

    path0 = os.getenv("ANSIBLE_CONFIG", None)
    if path0 is not None:
        path0 = os.path.expanduser(path0)
    path1 = os.getcwd() + "/ansible.cfg"
    path2 = os.path.expanduser("~/.ansible.cfg")
    path3 = "/etc/ansible/ansible.cfg"

    for path in [path0, path1, path2, path3]:
        if path is not None and os.path.exists(path):
            try:
                p.read(path)
            except configparser.Error as e:
                print("Error reading config file: \n{0}".format(e))
                sys.exit(1)
            return p
    return None

I could use this behavior to set an environmental variable before each command to load an entirely different ansible.cfg, but that seems messy as I only need to fiddle the ssh_args. Unfortunately, Ansible doesn't expose the command switch to specify an SSH config.

I'd like to not maintain any modifications to Ansible I'd like to not wrap all calls to the ansible or ansible-playbook commands. To preserve the behavior of Ansible's commands, I believe my options are:

  • a) have the target of ssh_args = -F <<config_file>> be a script that's opened
  • b) have the target of p.read(path) be a script that gets expanded to generate a valid ansible.cfg
  • c) just maintain different ansible.cfg files and take advantage of the fact that Ansible picks this file in the order of environmental variable, cwd.
1
2
6/29/2015 4:26:33 AM

Accepted Answer

Option C is the only way that I can see accomplishing this. You could have your default/most-used ansible.cfg be the one that is read in the cwd ansible.cfg, then optionally setting/unsetting an environmental variable that points to the version that specifies the ssh_args = -F config_legacy line that you need (ANSIBLE_SSH_ARGS).

The reason for needing to do an ansible.cfg instead of just passing an envvar with SSH options is because Ansible does not honor the User setting in an ssh configuration file -- it's already decided who it wants to run as on kick off of a command.

Dynamic inventory (ec2.py) files are incredibly poor places to hack in a change for maintenance reasons, which is why it's typical to see --user=REMOTE_USER flags, which coupled with setting an ANSIBLE_SSH_ARGS="-F some_ssh_config" environmental variable, make for a ugly commands to give to a casual user of an Ansible repo.

e.g.

ANSIBLE_SSH_ARGS="-F other_ssh_config" ansible-playbook playbooks/why_i_am_doing_this.yml -u ubuntu

v.

ansible-playbook playbooks/why_i_am_doing_this.yml -F other_ansible.cfg

Option A doesn't work because the file is opened all at once for loading into Python, per the p.read() above, not that it matters because if files could arbitrarily decide to open as scripts, we'd be living in a very scary world.

This is how the ansible.cfg loading looks from a system perspective:

$ sudo dtruss -a ansible ......

74947/0x11eadf:    312284       3      2 stat64("/Users/tfisher/code/ansible/ansible.cfg\0", 0x7FFF55D936C0, 0x7FD70207EA00)             = 0 0
74947/0x11eadf:    312308      19     17 open_nocancel("/Users/tfisher/code/ansible/ansible.cfg\0", 0x0, 0x1B6)          = 5 0
74947/0x11eadf:    312316       3      1 read_nocancel(0x5, "# ansible.cfg \n#\n# Config-file load order is:\n#   envvar ANSIBLE_CONFIG\n#   `pwd`/ansible.cfg\n#   ~/.ansible.cfg\n#   /etc/ansible/ansible.cfg\n\n# Some unmodified settings are left as comments to encourage research/suggest modific", 0x1000)               = 3477 0
74947/0x11eadf:    312308      19     17 open_nocancel("/Users/tfisher/code/ansible/ansible.cfg\0", 0x0, 0x1B6)          = 5 0

Option B doesn't work for the same reasons why A doesn't work -- even if you create a mock Python file object with proper read/readline/readlines sigatures, the file is still being opened for reading only, not execution.

And if this is the correct repo for OpenSSH, the config file is specified like so:

#define _PATH_HOST_CONFIG_FILE      SSHDIR "/ssh_config"

processed like so:

    /* Read systemwide configuration file after user config. */
    (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw,
        host, host_arg, &options,
        post_canon ? SSHCONF_POSTCANON : 0);

and read here with an fopen, which leaves no room for "file as a script" schenanigans.

5
6/30/2015 8:21:22 PM

Another option is set the environment variable ANSIBLE_SSH_ARGS to the arguments you want Ansible to pass to the ssh command.


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon