File: //proc/self/root/usr/lib/python2.7/site-packages/salt/roster/terraform.py
# -*- coding: utf-8 -*-
'''
Dynamic roster from terraform current state
===========================================
This roster module allows you dynamically generate the roster from the terraform
resources defined with the `Terraform Salt`_ provider.
It exposes all salt_host resources with the same attributes to the salt-ssh
roster, making it completely independent of the type of terraform resource, and
providing the integration using terraform constructs with interpolation.
Basic Example
-------------
Given a simple salt-ssh tree with a Saltfile:
.. code-block:: yaml
salt-ssh:
config_dir: etc/salt
max_procs: 30
wipe_ssh: True
and ``etc/salt/master``:
.. code-block:: yaml
root_dir: .
file_roots:
base:
- srv/salt
pillar_roots:
base:
- srv/pillar
roster: terraform
In the same folder as your ``Saltfile``, create terraform file with resources
like cloud instances, virtual machines, etc. For every single one of those that
you want to manage with Salt, create a ``salt_host`` resource:
.. code-block:: text
resource "salt_host" "dbminion" {
salt_id = "dbserver"
host = "${libvirt_domain.vm-db.network_interface.0.addresses.0}"
user = "root"
passwd = "linux"
}
You can use the count attribute to create multiple roster entries with a single
definition. Please refer to the `Terraform Salt`_ provider for more detailed
examples.
.. _Terraform Salt: https://github.com/dmacvicar/terraform-provider-salt
'''
# Import Python libs
from __future__ import absolute_import, unicode_literals
import logging
import os.path
# Import Salt libs
import salt.utils.files
import salt.utils.json
log = logging.getLogger(__name__)
TF_OUTPUT_PREFIX = 'salt.roster.'
TF_ROSTER_ATTRS = {'host': 's',
'user': 's',
'passwd': 's',
'port': 'i',
'sudo': 'b',
'sudo_user': 's',
'tty': 'b', 'priv': 's',
'timeout': 'i',
'minion_opts': 'm',
'thin_dir': 's',
'cmd_umask': 'i'}
MINION_ID = 'salt_id'
def _handle_salt_host_resource(resource):
'''
Handles salt_host resources.
See https://github.com/dmacvicar/terraform-provider-salt
Returns roster attributes for the resource or None
'''
ret = {}
attrs = resource.get('primary', {}).get('attributes', {})
ret[MINION_ID] = attrs.get(MINION_ID)
valid_attrs = set(attrs.keys()).intersection(TF_ROSTER_ATTRS.keys())
for attr in valid_attrs:
ret[attr] = _cast_output_to_type(attrs.get(attr), TF_ROSTER_ATTRS.get(attr))
return ret
def _add_ssh_key(ret):
'''
Setups the salt-ssh minion to be accessed with salt-ssh default key
'''
priv = None
if __opts__.get('ssh_use_home_key') and os.path.isfile(os.path.expanduser('~/.ssh/id_rsa')):
priv = os.path.expanduser('~/.ssh/id_rsa')
else:
priv = __opts__.get(
'ssh_priv',
os.path.abspath(os.path.join(
__opts__['pki_dir'],
'ssh',
'salt-ssh.rsa'
))
)
if priv and os.path.isfile(priv):
ret['priv'] = priv
def _cast_output_to_type(value, typ):
'''cast the value depending on the terraform type'''
if typ == 'b':
return bool(value)
if typ == 'i':
return int(value)
return value
def _parse_state_file(state_file_path='terraform.tfstate'):
'''
Parses the terraform state file passing different resource types to the right handler
'''
ret = {}
with salt.utils.files.fopen(state_file_path, 'r') as fh_:
tfstate = salt.utils.json.load(fh_)
modules = tfstate.get('modules')
if not modules:
log.error('Malformed tfstate file. No modules found')
return ret
for module in modules:
resources = module.get('resources', [])
for resource_name, resource in salt.ext.six.iteritems(resources):
roster_entry = None
if resource['type'] == 'salt_host':
roster_entry = _handle_salt_host_resource(resource)
if not roster_entry:
continue
minion_id = roster_entry.get(MINION_ID, resource.get('id'))
if not minion_id:
continue
if MINION_ID in roster_entry:
del roster_entry[MINION_ID]
_add_ssh_key(roster_entry)
ret[minion_id] = roster_entry
return ret
def targets(tgt, tgt_type='glob', **kwargs): # pylint: disable=W0613
'''
Returns the roster from the terraform state file, checks opts for location, but defaults to terraform.tfstate
'''
roster_file = os.path.abspath('terraform.tfstate')
if __opts__.get('roster_file'):
roster_file = os.path.abspath(__opts__['roster_file'])
if not os.path.isfile(roster_file):
log.error("Can't find terraform state file '%s'", roster_file)
return {}
log.debug('terraform roster: using %s state file', roster_file)
if not roster_file.endswith('.tfstate'):
log.error("Terraform roster can only be used with terraform state files")
return {}
raw = _parse_state_file(roster_file)
log.debug('%s hosts in terraform state file', len(raw))
return __utils__['roster_matcher.targets'](raw, tgt, tgt_type, 'ipv4')