suites: | |||||
- name: <%= ENV['SUITE'] %> | |||||
provisioner: | |||||
pillars-from-files: | |||||
salt.sls: tests/pillar/<%= ENV['SUITE'] %>.sls |
noservices: true | noservices: true | ||||
vendor_repo: | vendor_repo: | ||||
- type: apt | - type: apt | ||||
url: http://apt-mk.mirantis.com/trusty | |||||
url: http://apt-mk.mirantis.com/xenial | |||||
key_url: http://apt-mk.mirantis.com/public.gpg | key_url: http://apt-mk.mirantis.com/public.gpg | ||||
components: salt | components: salt | ||||
distribution: testing | distribution: testing | ||||
enabled: true | enabled: true | ||||
master: | master: | ||||
host: localhost | host: localhost | ||||
pkgs: | |||||
- python-m2crypto | |||||
- python-psutil | |||||
- python-yaml | |||||
linux: | linux: | ||||
system: | system: | ||||
enabled: true | enabled: true | ||||
image: <%=ENV['PLATFORM'] || 'trevorj/salty-whales:xenial'%> | image: <%=ENV['PLATFORM'] || 'trevorj/salty-whales:xenial'%> | ||||
platform: ubuntu | platform: ubuntu | ||||
# - name: ubuntu-xenial | |||||
# driver_config: | |||||
# image: trevorj/salty-whales:xenial | |||||
# platform: ubuntu | |||||
# - name: debian-jessie | |||||
# driver_config: | |||||
# image: debian:jessie | |||||
# - name: debian-stretch | |||||
# driver_config: | |||||
# image: debian:stretch | |||||
suites: | suites: | ||||
- name: minion-default | - name: minion-default | ||||
provisioner: | provisioner: | ||||
pillars: | pillars: | ||||
minion_handler.sls: tests/pillar/minion_custom_handler.sls | minion_handler.sls: tests/pillar/minion_custom_handler.sls | ||||
minion_local_pillar.sls: tests/pillar/minion_local_pillar.sls | minion_local_pillar.sls: tests/pillar/minion_local_pillar.sls | ||||
minion_local_reclass.sls: tests/pillar/minion_local_reclass.sls | minion_local_reclass.sls: tests/pillar/minion_local_reclass.sls | ||||
minion_backend_urllib.sls: tests/pillar/minion_backend_urllib.sls | |||||
- name: master-default | - name: master-default | ||||
provisioner: | provisioner: | ||||
- master_ssh_root | - master_ssh_root | ||||
- minion_pki_cert | - minion_pki_cert | ||||
- master_formulas | - master_formulas | ||||
common.sls: | |||||
salt: | |||||
master: | |||||
#Use a useless package to avoid upgrading salt-master | |||||
pkgs: | |||||
- python-yaml | |||||
pillars-from-files: | pillars-from-files: | ||||
minion_pki.sls: tests/pillar/minion_pki_ca.sls | minion_pki.sls: tests/pillar/minion_pki_ca.sls | ||||
minion_pki_cert.sls: tests/pillar/minion_pki_cert.sls | minion_pki_cert.sls: tests/pillar/minion_pki_cert.sls | ||||
salt.sls: tests/pillar/minion_multi_master_failover.sls | salt.sls: tests/pillar/minion_multi_master_failover.sls | ||||
# - name: minion-local | |||||
# provisioner: | |||||
# pillars: | |||||
# top.sls: | |||||
# base: | |||||
# "*": | |||||
# - common | |||||
# - minion_local_pillar | |||||
# - minion_local_reclass | |||||
# pillars-from-files: | |||||
# minion_local_pillar.sls: tests/pillar/minion_local_pillar.sls | |||||
# minion_local_reclass.sls: tests/pillar/minion_local_reclass.sls | |||||
# vim: ft=yaml sw=2 ts=2 sts=2 tw=125 | # vim: ft=yaml sw=2 ts=2 sts=2 tw=125 |
- bundle install | - bundle install | ||||
env: | env: | ||||
- PLATFORM=trevorj/salty-whales:trusty | |||||
- PLATFORM=trevorj/salty-whales:xenial | |||||
- PLATFORM=trevorj/salty-whales:trusty SUITE=minion_default | |||||
- PLATFORM=trevorj/salty-whales:xenial SUITE=minion_default | |||||
- PLATFORM=trevorj/salty-whales:trusty SUITE=master_default | |||||
- PLATFORM=trevorj/salty-whales:xenial SUITE=master_default | |||||
- PLATFORM=trevorj/salty-whales:trusty SUITE=minion_default | |||||
- PLATFORM=trevorj/salty-whales:xenial SUITE=minion_default | |||||
- PLATFORM=trevorj/salty-whales:trusty SUITE=control_default | |||||
- PLATFORM=trevorj/salty-whales:xenial SUITE=control_default | |||||
- PLATFORM=trevorj/salty-whales:trusty SUITE=minion_multi_master_failover | |||||
- PLATFORM=trevorj/salty-whales:xenial SUITE=minion_multi_master_failover | |||||
before_script: | before_script: | ||||
- set -o pipefail | - set -o pipefail | ||||
- make test | tail | - make test | tail | ||||
script: | script: | ||||
- test ! -e .kitchen.yml || bundle exec kitchen converge || true | |||||
- test ! -e .kitchen.yml || bundle exec kitchen verify -t tests/integration | |||||
- KITCHEN_LOCAL_YAML=.kitchen.travis.yml bundle exec kitchen test -t tests/integration | |||||
notifications: | notifications: | ||||
webhooks: | webhooks: |
host: 127.0.0.1 | host: 127.0.0.1 | ||||
port: 9999 | port: 9999 | ||||
Salt engine definition for saltgraph metadata collector | |||||
.. code-block:: yaml | |||||
salt: | |||||
master: | |||||
engine: | |||||
graph_metadata: | |||||
engine: saltgraph | |||||
host: 127.0.0.1 | |||||
port: 5432 | |||||
user: salt | |||||
password: salt | |||||
database: salt | |||||
Salt engine definition for sending events from docker events | |||||
.. code-block:: yaml | |||||
salt: | |||||
master: | |||||
engine: | |||||
docker_events: | |||||
docker_url: unix://var/run/docker.sock | |||||
Salt master peer setup for remote certificate signing | Salt master peer setup for remote certificate signing | ||||
.. code-block:: yaml | .. code-block:: yaml | ||||
master: | master: | ||||
state_output: changes | state_output: changes | ||||
Salt Reactor system configuration | |||||
Salt synchronise node pillar and modules after start | |||||
.. code-block:: yaml | .. code-block:: yaml | ||||
master: | master: | ||||
reactor: | reactor: | ||||
salt/minion/*/start: | salt/minion/*/start: | ||||
- salt://reactor/minion-started.sls | |||||
- salt://salt/reactor/node_start.sls | |||||
Trigger basic node install | |||||
.. code-block:: yaml | |||||
salt: | |||||
master: | |||||
reactor: | |||||
salt/minion/install: | |||||
- salt://salt/reactor/node_install.sls | |||||
Sample event to trigger the node installation | |||||
.. code-block:: bash | |||||
salt-call event.send 'salt/minion/install' | |||||
Run any defined orchestration pipeline | |||||
.. code-block:: yaml | |||||
salt: | |||||
master: | |||||
reactor: | |||||
salt/orchestrate/start: | |||||
- salt://salt/reactor/orchestrate_start.sls | |||||
Event to trigger the orchestration pipeline | |||||
.. code-block:: bash | |||||
salt-call event.send 'salt/orchestrate/start' "{'orchestrate': 'salt/orchestrate/infra_install.sls'}" | |||||
Synchronise modules and pillars on minion start. | |||||
.. code-block:: yaml | |||||
salt: | |||||
master: | |||||
reactor: | |||||
'salt/minion/*/start': | |||||
- salt://salt/reactor/minion_start.sls | |||||
Add and/or remove the minion key | |||||
.. code-block:: yaml | |||||
salt: | |||||
master: | |||||
reactor: | |||||
salt/key/create: | |||||
- salt://salt/reactor/key_create.sls | |||||
salt/key/remove: | |||||
- salt://salt/reactor/key_remove.sls | |||||
Event to trigger the key creation | |||||
.. code-block:: bash | |||||
salt-call event.send 'salt/key/create' \ | |||||
> "{'node_id': 'id-of-minion', 'node_host': '172.16.10.100', 'orch_post_create': 'kubernetes.orchestrate.compute_install', 'post_create_pillar': {'node_name': 'id-of-minion'}}" | |||||
.. note:: | |||||
You can add pass additional `orch_pre_create`, `orch_post_create`, | |||||
`orch_pre_remove` or `orch_post_remove` parameters to the event to call | |||||
extra orchestrate files. This can be useful for example for | |||||
registering/unregistering nodes from the monitoring alarms or dashboards. | |||||
The key creation event needs to be run from other machine than the one | |||||
being registered. | |||||
Event to trigger the key removal | |||||
.. code-block:: bash | |||||
salt-call event.send 'salt/key/remove' | |||||
Salt syndic | Salt syndic | ||||
----------- | ----------- | ||||
salt: | salt: | ||||
minion: | minion: | ||||
proxy: | |||||
proxy_minion: | |||||
master: localhost | master: localhost | ||||
device: | device: | ||||
vsrx01.mydomain.local: | vsrx01.mydomain.local: | ||||
host: 127.0.0.1 | host: 127.0.0.1 | ||||
port: 3128 | port: 3128 | ||||
Salt minion to specify non-default HTTP backend. The default tornado backend | |||||
does not respect HTTP proxy settings set as environment variables. This is | |||||
useful for cases where you need to set no_proxy lists. | |||||
.. code-block:: yaml | |||||
salt: | |||||
minion: | |||||
backend: urllib2 | |||||
Salt minion with PKI certificate authority (CA) | Salt minion with PKI certificate authority (CA) | ||||
.. literalinclude:: tests/pillar/minion_pki_ca.sls | .. literalinclude:: tests/pillar/minion_pki_ca.sls | ||||
.. literalinclude:: tests/pillar/minion_pki_cert.sls | .. literalinclude:: tests/pillar/minion_pki_cert.sls | ||||
:language: yaml | :language: yaml | ||||
Salt minion trust CA certificates issued by salt CA on a specific host (ie: salt-master node) | |||||
.. code-block:: yaml | |||||
salt: | |||||
minion: | |||||
trusted_ca_minions: | |||||
- cfg01 | |||||
Salt control (cloud/kvm/docker) | Salt control (cloud/kvm/docker) | ||||
------------------------------- | ------------------------------- |
# -*- coding: utf-8 -*- | |||||
''' | |||||
Saltgraph engine for catching returns of state runs, parsing them | |||||
and passing them to flat database of latest Salt resource runs. | |||||
''' | |||||
# Import python libs | |||||
from __future__ import absolute_import | |||||
import datetime | |||||
import json | |||||
import logging | |||||
# Import salt libs | |||||
import salt.utils.event | |||||
# Import third party libs | |||||
try: | |||||
import psycopg2 | |||||
import psycopg2.extras | |||||
HAS_POSTGRES = True | |||||
except ImportError: | |||||
HAS_POSTGRES = False | |||||
__virtualname__ = 'saltgraph' | |||||
log = logging.getLogger(__name__) | |||||
def __virtual__(): | |||||
if not HAS_POSTGRES: | |||||
return False, 'Could not import saltgraph engine. python-psycopg2 is not installed.' | |||||
return __virtualname__ | |||||
def _get_conn(options={}): | |||||
''' | |||||
Return a postgres connection. | |||||
''' | |||||
host = options.get('host', '127.0.0.1') | |||||
user = options.get('user', 'salt') | |||||
passwd = options.get('passwd', 'salt') | |||||
datab = options.get('db', 'salt') | |||||
port = options.get('port', 5432) | |||||
return psycopg2.connect( | |||||
host=host, | |||||
user=user, | |||||
password=passwd, | |||||
database=datab, | |||||
port=port) | |||||
def _close_conn(conn): | |||||
''' | |||||
Close the Postgres connection | |||||
''' | |||||
conn.commit() | |||||
conn.close() | |||||
def _get_lowstate_data(options={}): | |||||
''' | |||||
TODO: document this method | |||||
''' | |||||
conn = _get_conn(options) | |||||
cur = conn.cursor() | |||||
try: | |||||
# you can only do this on Salt Masters minion | |||||
lowstate_req = __salt__['saltutil.cmd']('*', 'state.show_lowstate', **{'timeout': 15, 'concurrent': True, 'queue': True}) | |||||
except: | |||||
lowstate_req = {} | |||||
for minion, lowstate_ret in lowstate_req.items(): | |||||
if lowstate_ret.get('retcode') != 0: | |||||
continue | |||||
for resource in lowstate_ret.get('ret', []): | |||||
low_sql = '''INSERT INTO salt_resources | |||||
(id, resource_id, host, service, module, fun, status) | |||||
VALUES (%s, %s, %s, %s, %s, %s, %s) | |||||
ON CONFLICT (id) DO UPDATE | |||||
SET resource_id = excluded.resource_id, | |||||
host = excluded.host, | |||||
service = excluded.service, | |||||
module = excluded.module, | |||||
fun = excluded.fun, | |||||
alter_time = current_timestamp''' | |||||
rid = "%s|%s" % (minion, resource.get('__id__')) | |||||
cur.execute( | |||||
low_sql, ( | |||||
rid, | |||||
resource.get('__id__'), | |||||
minion, | |||||
resource.get('__sls__'), | |||||
resource.get('state') if 'state' in resource else resource.get('module'), | |||||
resource.get('fun'), | |||||
'unknown' | |||||
) | |||||
) | |||||
conn.commit() | |||||
if lowstate_req: | |||||
meta_sql = '''INSERT INTO salt_resources_meta | |||||
(id, options) | |||||
VALUES (%s, %s) | |||||
ON CONFLICT (id) DO UPDATE | |||||
SET options = excluded.options, | |||||
alter_time = current_timestamp''' | |||||
cur.execute( | |||||
meta_sql, ( | |||||
'lowstate_data', | |||||
'{}' | |||||
) | |||||
) | |||||
_close_conn(conn) | |||||
def _up_to_date(options={}): | |||||
''' | |||||
TODO: document this method | |||||
''' | |||||
conn = _get_conn(options) | |||||
cur = conn.cursor() | |||||
#cur_dict = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) | |||||
ret = False | |||||
# if lowstate data are older than 1 day, refresh them | |||||
cur.execute('SELECT alter_time FROM salt_resources_meta WHERE id = %s', ('lowstate_data',)) | |||||
alter_time = cur.fetchone() | |||||
if alter_time: | |||||
now = datetime.datetime.utcnow() | |||||
day = datetime.timedelta(days=1) | |||||
time_diff = now - alter_time[0].replace(tzinfo=None) | |||||
if time_diff < day: | |||||
ret = True | |||||
else: | |||||
skip = False | |||||
_close_conn(conn) | |||||
return ret | |||||
def _update_resources(event, options): | |||||
''' | |||||
TODO: document this method | |||||
''' | |||||
conn = _get_conn(options) | |||||
cur = conn.cursor() | |||||
cur.execute('SELECT id FROM salt_resources') | |||||
resources_db = [res[0] for res in cur.fetchall()] | |||||
resources = event.get('return', {}).values() | |||||
for resource in resources: | |||||
rid = '%s|%s' % (event.get('id'), resource.get('__id__')) | |||||
if rid in resources_db: | |||||
status = 'unknown' | |||||
if resource.get('result', None) is not None: | |||||
status = 'success' if resource.get('result') else 'failed' | |||||
resource_sql = '''UPDATE salt_resources SET (status, last_ret, alter_time) = (%s, %s, current_timestamp) | |||||
WHERE id = %s''' | |||||
cur.execute( | |||||
resource_sql, ( | |||||
status, | |||||
repr(resource), | |||||
rid | |||||
) | |||||
) | |||||
conn.commit() | |||||
_close_conn(conn) | |||||
def start(host='salt', user='salt', password='salt', database='salt', port=5432, **kwargs): | |||||
''' | |||||
Listen to events and parse Salt state returns | |||||
''' | |||||
if __opts__['__role'] == 'master': | |||||
event_bus = salt.utils.event.get_master_event( | |||||
__opts__, | |||||
__opts__['sock_dir'], | |||||
listen=True) | |||||
else: | |||||
event_bus = salt.utils.event.get_event( | |||||
'minion', | |||||
transport=__opts__['transport'], | |||||
opts=__opts__, | |||||
sock_dir=__opts__['sock_dir'], | |||||
listen=True) | |||||
log.debug('Saltgraph engine started') | |||||
while True: | |||||
event = event_bus.get_event() | |||||
supported_funcs = ['state.sls', 'state.apply', 'state.highstate'] | |||||
if event and event.get('fun', None) in supported_funcs: | |||||
test = 'test=true' in [arg.lower() for arg in event.get('fun_args', [])] | |||||
if not test: | |||||
options = { | |||||
'host': host, | |||||
'user': user, | |||||
'passwd': password, | |||||
'db': database, | |||||
'port': port | |||||
} | |||||
is_reclass = [arg for arg in event.get('fun_args', []) if arg.startswith('reclass')] | |||||
if is_reclass or not _up_to_date(options): | |||||
_get_lowstate_data(options) | |||||
_update_resources(event, options) | |||||
from __future__ import absolute_import | |||||
# Import python libs | |||||
import logging | |||||
import os | |||||
try: | |||||
import paramiko | |||||
HAS_PARAMIKO = True | |||||
except: | |||||
HAS_PARAMIKO = False | |||||
# Import Salt libs | |||||
import salt.config | |||||
import salt.wheel | |||||
LOG = logging.getLogger(__name__) | |||||
def __virtual__(): | |||||
''' | |||||
Only load if paramiko library exist. | |||||
''' | |||||
if not HAS_PARAMIKO: | |||||
return ( | |||||
False, | |||||
'Can not load module saltkey: paramiko library not found') | |||||
return True | |||||
def key_create(id_, host, force=False): | |||||
''' | |||||
Generates minion keypair, accepts it on master and injects it to minion via SSH. | |||||
:param id_: expected minion ID of target node | |||||
:param host: IP address or resolvable hostname/FQDN of target node | |||||
CLI Examples: | |||||
.. code-block:: bash | |||||
salt-call saltkey.key_create <MINION_ID> <MINION_IP_ADDRESS> force=False | |||||
''' | |||||
ret = { | |||||
'retcode': 0, | |||||
'msg': 'Salt Key for minion %s is already accepted' % id_, | |||||
} | |||||
opts = salt.config.master_config('/etc/salt/master') | |||||
wheel = salt.wheel.WheelClient(opts) | |||||
keys = wheel.cmd('key.gen_accept', arg=[id_], kwarg={'force': force}) | |||||
pub_key = keys.get('pub', None) | |||||
priv_key = keys.get('priv', None) | |||||
if pub_key and priv_key: | |||||
ssh = paramiko.SSHClient() | |||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | |||||
# Establish SSH connection to minion | |||||
try: | |||||
ssh.connect(host) | |||||
except paramiko.ssh_exception.AuthenticationException: | |||||
msg = ('Could not establish SSH connection to minion "%s" on address %s, please ensure ' | |||||
'that current user\'s SSH key is present in minions authorized_keys.') % (id_, host) | |||||
LOG.error(msg) | |||||
ret['retcode'] = 1 | |||||
ret['msg'] = msg | |||||
wheel.cmd_async({'fun': 'key.delete', 'match': id_}) | |||||
return ret | |||||
except Exception as e: | |||||
msg = ('Unknown error occured while establishing SSH connection ' | |||||
'to minion "%s" on address %s: %s') % (id_, host, repr(e)) | |||||
LOG.error(msg) | |||||
ret['retcode'] = 1 | |||||
ret['msg'] = msg | |||||
wheel.cmd_async({'fun': 'key.delete', 'match': id_}) | |||||
return ret | |||||
# Setup the keys on minion side the ugly way, nice one didn't work | |||||
key_path = '/etc/salt/pki/minion' | |||||
command = ('echo "%(pub_key)s" > %(pub_path)s && chmod 644 %(pub_path)s && ' | |||||
'echo "%(priv_key)s" > %(priv_path)s && chmod 400 %(priv_path)s && ' | |||||
'salt-call --local service.restart salt-minion') % { | |||||
'pub_path': os.path.join(key_path, 'minion.pub'), | |||||
'pub_key': pub_key, | |||||
'priv_path': os.path.join(key_path, 'minion.pem'), | |||||
'priv_key': priv_key | |||||
} | |||||
ssh_chan = ssh.get_transport().open_session() | |||||
ssh_chan.exec_command(command) | |||||
# Wait for command return | |||||
while True: | |||||
if ssh_chan.exit_status_ready(): | |||||
exit_status = ssh_chan.recv_exit_status() | |||||
stderr = ssh_chan.recv_stderr(1000) | |||||
stdout = ssh_chan.recv(1000) | |||||
break | |||||
ssh.close() | |||||
# Evaluate SSH command exit status | |||||
if exit_status != 0: | |||||
msg = 'Keypair injection to Salt minion failed on target with following error: %s' % stderr | |||||
LOG.error(msg) | |||||
ret['retcode'] = exit_status | |||||
ret['msg'] = msg | |||||
return ret | |||||
ret['msg'] = 'Salt Key successfully created' | |||||
return ret | |||||
from __future__ import absolute_import | |||||
# Let's not allow PyLint complain about string substitution | |||||
# pylint: disable=W1321,E1321 | |||||
# Import python libs | |||||
import logging | |||||
# Import Salt libs | |||||
import salt.returners | |||||
# Import third party libs | |||||
try: | |||||
import psycopg2 | |||||
import psycopg2.extras | |||||
HAS_POSTGRES = True | |||||
except ImportError: | |||||
HAS_POSTGRES = False | |||||
__virtualname__ = 'saltresource' | |||||
LOG = logging.getLogger(__name__) | |||||
def __virtual__(): | |||||
if not HAS_POSTGRES: | |||||
return False, 'Could not import saltresource module; psycopg2 is not installed.' | |||||
return __virtualname__ | |||||
def _get_options(ret=None): | |||||
''' | |||||
Get the postgres options from salt. | |||||
''' | |||||
defaults = {'host': '127.0.0.1', | |||||
'user': 'salt', | |||||
'passwd': 'salt', | |||||
'db': 'salt', | |||||
'port': '5432'} | |||||
_options = {} | |||||
for key, default in defaults.items(): | |||||
_options[key] = __salt__['config.get']('%s.%s' % (__virtualname__, key), default) | |||||
return _options | |||||
def _get_conn(ret=None): | |||||
''' | |||||
Return a postgres connection. | |||||
''' | |||||
_options = _get_options(ret) | |||||
host = _options.get('host') | |||||
user = _options.get('user') | |||||
passwd = _options.get('passwd') | |||||
datab = _options.get('db') | |||||
port = _options.get('port') | |||||
return psycopg2.connect( | |||||
host=host, | |||||
user=user, | |||||
password=passwd, | |||||
database=datab, | |||||
port=port) | |||||
def _close_conn(conn): | |||||
''' | |||||
Close the Postgres connection | |||||
''' | |||||
conn.commit() | |||||
conn.close() | |||||
def graph_data(*args, **kwargs): | |||||
''' | |||||
Returns graph data for visualization app | |||||
CLI Examples: | |||||
.. code-block:: bash | |||||
salt '*' saltresource.graph_data | |||||
''' | |||||
conn = _get_conn() | |||||
cur_dict = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) | |||||
cur_dict.execute('SELECT host, service, status FROM salt_resources') | |||||
resources_db = [dict(res) for res in cur_dict] | |||||
db_dict = {} | |||||
for resource in resources_db: | |||||
host = resource.get('host') | |||||
service = '.'.join(resource.get('service').split('.')[:2]) | |||||
status = resource.get('status') | |||||
if db_dict.get(host, None): | |||||
if db_dict[host].get(service, None): | |||||
service_data = db_dict[host][service] | |||||
service_data.append(status) | |||||
else: | |||||
db_dict[host][service] = [status] | |||||
else: | |||||
db_dict[host] = {service: []} | |||||
graph = [] | |||||
for host, services in db_dict.items(): | |||||
for service, statuses in services.items(): | |||||
status = 'unknown' | |||||
if 'failed' in statuses: | |||||
status = 'failed' | |||||
elif 'success' in statuses and not ('failed' in statuses or 'unknown' in statuses): | |||||
status = 'success' | |||||
datum = {'host': host, 'service': service, 'status': status} | |||||
graph.append(datum) | |||||
_close_conn(conn) | |||||
return {'graph': graph} | |||||
def host_data(host, **kwargs): | |||||
''' | |||||
Returns data describing single host | |||||
CLI Examples: | |||||
.. code-block:: bash | |||||
salt-call saltresource.host_data '<minion_id>' | |||||
''' | |||||
conn = _get_conn() | |||||
cur_dict = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) | |||||
sql = 'SELECT host, service, resource_id, last_ret, status FROM salt_resources WHERE host=%s' | |||||
cur_dict.execute(sql, (host,)) | |||||
resources_db = [dict(res) for res in cur_dict] | |||||
db_dict = {} | |||||
for resource in resources_db: | |||||
host = resource.get('host') | |||||
service = '.'.join(resource.get('service').split('.')[:2]) | |||||
status = resource.get('status') | |||||
if db_dict.get(host, None): | |||||
if db_dict[host].get(service, None): | |||||
service_data = db_dict[host][service] | |||||
service_data.append(status) | |||||
else: | |||||
db_dict[host][service] = [status] | |||||
else: | |||||
db_dict[host] = {service: []} | |||||
graph = [] | |||||
for host, services in db_dict.items(): | |||||
for service, statuses in services.items(): | |||||
status = 'unknown' | |||||
if 'failed' in statuses: | |||||
status = 'failed' | |||||
elif 'success' in statuses and not ('failed' in statuses or 'unknown' in statuses): | |||||
status = 'success' | |||||
resources = [{'service': r.get('service', ''), 'resource_id': r.get('resource_id', ''), 'last_ret': r.get('last_ret', None), 'status': r.get('status', '')} | |||||
for r | |||||
in resources_db | |||||
if r.get('service', '').startswith(service)] | |||||
datum = {'host': host, 'service': service, 'status': status, 'resources': resources} | |||||
graph.append(datum) | |||||
_close_conn(conn) | |||||
return {'graph': graph} | |||||
def sync_db(*args, **kwargs): | |||||
conn = _get_conn() | |||||
cur = conn.cursor() | |||||
resources_sql = ''' | |||||
CREATE TABLE IF NOT EXISTS salt_resources ( | |||||
id varchar(255) NOT NULL UNIQUE, | |||||
resource_id varchar(255) NOT NULL, | |||||
host varchar(255) NOT NULL, | |||||
service varchar(255) NOT NULL, | |||||
module varchar(50) NOT NULL, | |||||
fun varchar(50) NOT NULL, | |||||
status varchar(50) NOT NULL, | |||||
options json NULL, | |||||
last_ret text NULL, | |||||
alter_time TIMESTAMP WITH TIME ZONE DEFAULT now() | |||||
); | |||||
''' | |||||
cur.execute(resources_sql) | |||||
conn.commit() | |||||
resources_meta_sql = ''' | |||||
CREATE TABLE IF NOT EXISTS salt_resources_meta ( | |||||
id varchar(255) NOT NULL UNIQUE, | |||||
options json NULL, | |||||
alter_time TIMESTAMP WITH TIME ZONE DEFAULT now() | |||||
); | |||||
''' | |||||
cur.execute(resources_meta_sql) | |||||
_close_conn(conn) | |||||
return True | |||||
def flush_db(*args, **kwargs): | |||||
conn = _get_conn() | |||||
cur = conn.cursor() | |||||
result = True | |||||
resources_sql = 'DELETE FROM salt_resources' | |||||
try: | |||||
cur.execute(resources_sql) | |||||
conn.commit() | |||||
except Exception as e: | |||||
LOG.warning(repr(e)) | |||||
result = False | |||||
resources_meta_sql = 'DELETE FROM salt_resources_meta' | |||||
try: | |||||
cur.execute(resources_meta_sql) | |||||
_close_conn(conn) | |||||
except Exception as e: | |||||
LOG.warning(repr(e)) | |||||
result = False | |||||
return result | |||||
def destroy_db(*args, **kwargs): | |||||
conn = _get_conn() | |||||
cur = conn.cursor() | |||||
resources_sql = 'DROP TABLE IF EXISTS salt_resources;' | |||||
cur.execute(resources_sql) | |||||
conn.commit() | |||||
resources_meta_sql = 'DROP TABLE IF EXISTS salt_resources_meta;' | |||||
cur.execute(resources_meta_sql) | |||||
_close_conn(conn) | |||||
return True | |||||
# -*- coding: utf-8 -*- | |||||
''' | |||||
Return data to a postgresql graph server | |||||
.. note:: | |||||
Creates database of all Salt resources which are to be run on | |||||
all minions and then updates their last known state during state | |||||
file runs. It can't function as master nor minion external cache. | |||||
:maintainer: None | |||||
:maturity: New | |||||
:depends: psycopg2 | |||||
:platform: all | |||||
To enable this returner the minion will need the psycopg2 installed and | |||||
the following values configured in the minion or master config: | |||||
.. code-block:: yaml | |||||
returner.postgres_graph_db.host: 'salt' | |||||
returner.postgres_graph_db.user: 'salt' | |||||
returner.postgres_graph_db.passwd: 'salt' | |||||
returner.postgres_graph_db.db: 'salt' | |||||
returner.postgres_graph_db.port: 5432 | |||||
Alternative configuration values can be used by prefacing the configuration. | |||||
Any values not found in the alternative configuration will be pulled from | |||||
the default location: | |||||
.. code-block:: yaml | |||||
alternative.returner.postgres_graph_db.host: 'salt' | |||||
alternative.returner.postgres_graph_db.user: 'salt' | |||||
alternative.returner.postgres_graph_db.passwd: 'salt' | |||||
alternative.returner.postgres_graph_db.db: 'salt' | |||||
alternative.returner.postgres_graph_db.port: 5432 | |||||
Running the following commands as the postgres user should create the database | |||||
correctly: | |||||
.. code-block:: sql | |||||
psql << EOF | |||||
CREATE ROLE salt WITH LOGIN; | |||||
ALTER ROLE salt WITH PASSWORD 'salt'; | |||||
CREATE DATABASE salt WITH OWNER salt; | |||||
EOF | |||||
psql -h localhost -U salt << EOF | |||||
-- | |||||
-- Table structure for table 'salt_resources' | |||||
-- | |||||
DROP TABLE IF EXISTS salt_resources; | |||||
CREATE TABLE salt_resources ( | |||||
id varchar(255) NOT NULL UNIQUE, | |||||
resource_id varchar(255) NOT NULL, | |||||
host varchar(255) NOT NULL, | |||||
service varchar(255) NOT NULL, | |||||
module varchar(50) NOT NULL, | |||||
fun varchar(50) NOT NULL, | |||||
status varchar(50) NOT NULL, | |||||
options json NULL, | |||||
last_ret text NULL, | |||||
alter_time TIMESTAMP WITH TIME ZONE DEFAULT now() | |||||
); | |||||
-- | |||||
-- Table structure for table 'salt_resources_meta' | |||||
-- | |||||
DROP TABLE IF EXISTS salt_resources_meta; | |||||
CREATE TABLE salt_resources_meta ( | |||||
id varchar(255) NOT NULL UNIQUE, | |||||
options json NULL, | |||||
alter_time TIMESTAMP WITH TIME ZONE DEFAULT now() | |||||
); | |||||
EOF | |||||
Required python modules: psycopg2 | |||||
To use the postgres_graph_db returner, append '--return postgres_graph_db' to the salt command. | |||||
.. code-block:: bash | |||||
salt '*' test.ping --return postgres_graph_db | |||||
To use the alternative configuration, append '--return_config alternative' to the salt command. | |||||
.. versionadded:: 2015.5.0 | |||||
.. code-block:: bash | |||||
salt '*' test.ping --return postgres_graph_db --return_config alternative | |||||
To override individual configuration items, append --return_kwargs '{"key:": "value"}' to the salt command. | |||||
.. versionadded:: 2016.3.0 | |||||
.. code-block:: bash | |||||
salt '*' test.ping --return postgres_graph_db --return_kwargs '{"db": "another-salt"}' | |||||
''' | |||||
from __future__ import absolute_import | |||||
# Let's not allow PyLint complain about string substitution | |||||
# pylint: disable=W1321,E1321 | |||||
# Import python libs | |||||
import datetime | |||||
import json | |||||
import logging | |||||
# Import Salt libs | |||||
import salt.utils.jid | |||||
import salt.returners | |||||
# Import third party libs | |||||
try: | |||||
import psycopg2 | |||||
import psycopg2.extras | |||||
HAS_POSTGRES = True | |||||
except ImportError: | |||||
HAS_POSTGRES = False | |||||
__virtualname__ = 'postgres_graph_db' | |||||
LOG = logging.getLogger(__name__) | |||||
def __virtual__(): | |||||
if not HAS_POSTGRES: | |||||
return False, 'Could not import postgres returner; psycopg2 is not installed.' | |||||
return __virtualname__ | |||||
def _get_options(ret=None): | |||||
''' | |||||
Get the postgres options from salt. | |||||
''' | |||||
attrs = {'host': 'host', | |||||
'user': 'user', | |||||
'passwd': 'passwd', | |||||
'db': 'db', | |||||
'port': 'port'} | |||||
_options = salt.returners.get_returner_options('returner.{0}'.format(__virtualname__), | |||||
ret, | |||||
attrs, | |||||
__salt__=__salt__, | |||||
__opts__=__opts__) | |||||
return _options | |||||
def _get_conn(ret=None): | |||||
''' | |||||
Return a postgres connection. | |||||
''' | |||||
_options = _get_options(ret) | |||||
host = _options.get('host') | |||||
user = _options.get('user') | |||||
passwd = _options.get('passwd') | |||||
datab = _options.get('db') | |||||
port = _options.get('port') | |||||
return psycopg2.connect( | |||||
host=host, | |||||
user=user, | |||||
password=passwd, | |||||
database=datab, | |||||
port=port) | |||||
def _close_conn(conn): | |||||
''' | |||||
Close the Postgres connection | |||||
''' | |||||
conn.commit() | |||||
conn.close() | |||||
def _get_lowstate_data(): | |||||
''' | |||||
TODO: document this method | |||||
''' | |||||
conn = _get_conn() | |||||
cur = conn.cursor() | |||||
try: | |||||
# you can only do this on Salt Masters minion | |||||
lowstate_req = __salt__['saltutil.cmd']('*', 'state.show_lowstate', **{'timeout': 15, 'concurrent': True, 'queue': True}) | |||||
except: | |||||
lowstate_req = {} | |||||
for minion, lowstate_ret in lowstate_req.items(): | |||||
if lowstate_ret.get('retcode') != 0: | |||||
continue | |||||
for resource in lowstate_ret.get('ret', []): | |||||
low_sql = '''INSERT INTO salt_resources | |||||
(id, resource_id, host, service, module, fun, status) | |||||
VALUES (%s, %s, %s, %s, %s, %s, %s) | |||||
ON CONFLICT (id) DO UPDATE | |||||
SET resource_id = excluded.resource_id, | |||||
host = excluded.host, | |||||
service = excluded.service, | |||||
module = excluded.module, | |||||
fun = excluded.fun, | |||||
alter_time = current_timestamp''' | |||||
rid = "%s|%s" % (minion, resource.get('__id__')) | |||||
cur.execute( | |||||
low_sql, ( | |||||
rid, | |||||
resource.get('__id__'), | |||||
minion, | |||||
resource.get('__sls__'), | |||||
resource.get('state') if 'state' in resource else resource.get('module'), | |||||
resource.get('fun'), | |||||
'unknown' | |||||
) | |||||
) | |||||
conn.commit() | |||||
if lowstate_req: | |||||
meta_sql = '''INSERT INTO salt_resources_meta | |||||
(id, options) | |||||
VALUES (%s, %s) | |||||
ON CONFLICT (id) DO UPDATE | |||||
SET options = excluded.options, | |||||
alter_time = current_timestamp''' | |||||
cur.execute( | |||||
meta_sql, ( | |||||
'lowstate_data', | |||||
'{}' | |||||
) | |||||
) | |||||
_close_conn(conn) | |||||
def _up_to_date(): | |||||
''' | |||||
TODO: document this method | |||||
''' | |||||
conn = _get_conn() | |||||
cur = conn.cursor() | |||||
#cur_dict = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) | |||||
ret = False | |||||
# if lowstate data are older than 1 day, refresh them | |||||
cur.execute('SELECT alter_time FROM salt_resources_meta WHERE id = %s', ('lowstate_data',)) | |||||
alter_time = cur.fetchone() | |||||
if alter_time: | |||||
now = datetime.datetime.utcnow() | |||||
day = datetime.timedelta(days=1) | |||||
time_diff = now - alter_time[0].replace(tzinfo=None) | |||||
if time_diff < day: | |||||
ret = True | |||||
else: | |||||
skip = False | |||||
_close_conn(conn) | |||||
return ret | |||||
def _update_resources(ret): | |||||
''' | |||||
TODO: document this method | |||||
''' | |||||
conn = _get_conn(ret) | |||||
cur = conn.cursor() | |||||
cur.execute('SELECT id FROM salt_resources') | |||||
resources_db = [res[0] for res in cur.fetchall()] | |||||
resources = ret.get('return', {}).values() | |||||
for resource in resources: | |||||
rid = '%s|%s' % (ret.get('id'), resource.get('__id__')) | |||||
if rid in resources_db: | |||||
status = 'unknown' | |||||
if resource.get('result', None) is not None: | |||||
status = 'success' if resource.get('result') else 'failed' | |||||
resource_sql = '''UPDATE salt_resources SET (status, last_ret, alter_time) = (%s, %s, current_timestamp) | |||||
WHERE id = %s''' | |||||
cur.execute( | |||||
resource_sql, ( | |||||
status, | |||||
repr(resource), | |||||
rid | |||||
) | |||||
) | |||||
conn.commit() | |||||
_close_conn(conn) | |||||
def returner(ret): | |||||
''' | |||||
Return data to a postgres server | |||||
''' | |||||
#LOG.warning('RET: %s' % repr(ret)) | |||||
supported_funcs = ['state.sls', 'state.apply', 'state.highstate'] | |||||
test = 'test=true' in [arg.lower() for arg in ret.get('fun_args', [])] | |||||
if ret.get('fun') in supported_funcs and not test: | |||||
is_reclass = [arg for arg in ret.get('fun_args', []) if arg.startswith('reclass')] | |||||
if is_reclass or not _up_to_date(): | |||||
_get_lowstate_data() | |||||
_update_resources(ret) | |||||
engine: pkg | engine: pkg | ||||
command_timeout: 5 | command_timeout: 5 | ||||
worker_threads: 3 | worker_threads: 3 | ||||
max_event_size: 100000000 |
parameters: | |||||
salt: | |||||
master: | |||||
reactor: | |||||
'salt/minion/*/start': | |||||
- salt://salt/reactor/minion_start.sls |
engine: pkg | engine: pkg | ||||
command_timeout: 5 | command_timeout: 5 | ||||
worker_threads: 3 | worker_threads: 3 | ||||
max_event_size: 100000000 | |||||
base_environment: ${_param:salt_master_base_environment} | base_environment: ${_param:salt_master_base_environment} |
salt: | salt: | ||||
minion: | minion: | ||||
enabled: true | enabled: true | ||||
max_event_size: 100000000 | |||||
source: | source: | ||||
engine: pkg | engine: pkg | ||||
masters: | masters: |
salt: | salt: | ||||
minion: | minion: | ||||
enabled: true | enabled: true | ||||
max_event_size: 100000000 | |||||
source: | source: | ||||
engine: pkg | engine: pkg | ||||
local: true | local: true |
salt: | salt: | ||||
minion: | minion: | ||||
enabled: true | enabled: true | ||||
max_event_size: 100000000 | |||||
source: | source: | ||||
engine: pkg | engine: pkg | ||||
master: | master: |
- watch_in: | - watch_in: | ||||
- service: salt_api_service | - service: salt_api_service | ||||
{%- if api.get('ssl', {}).authority is defined %} | |||||
{%- set cert_file = "/etc/ssl/certs/" + api.ssl.get('name', grains.id) + ".crt" %} | |||||
{%- set ca_file = "/etc/ssl/certs/ca-" + api.ssl.authority + ".crt" %} | |||||
salt_api_init_tls: | |||||
cmd.run: | |||||
- name: "cat {{ cert_file }} {{ ca_file }} > /etc/ssl/certs/{{ api.ssl.get('name', grains.id) }}-chain.crt" | |||||
- creates: /etc/ssl/certs/{{ api.ssl.get('name', grains.id) }}-chain.crt | |||||
- watch_in: | |||||
- service: salt_api_service | |||||
{%- endif %} | |||||
salt_api_service: | salt_api_service: | ||||
service.running: | service.running: | ||||
- name: salt-api | - name: salt-api | ||||
- watch: | - watch: | ||||
- file: /etc/salt/master.d/_api.conf | - file: /etc/salt/master.d/_api.conf | ||||
{%- endif %} | |||||
{%- endif %} |
ssl_crt: /etc/letsencrypt/live/{{ api.ssl.name }}/cert.pem | ssl_crt: /etc/letsencrypt/live/{{ api.ssl.name }}/cert.pem | ||||
ssl_key: /etc/letsencrypt/live/{{ api.ssl.name }}/privkey.pem | ssl_key: /etc/letsencrypt/live/{{ api.ssl.name }}/privkey.pem | ||||
{%- elif api.ssl.engine == 'salt' %} | {%- elif api.ssl.engine == 'salt' %} | ||||
ssl_crt: /etc/ssl/certs/{{ system.name }}.{{ system.domain }}.crt | |||||
ssl_key: /etc/ssl/private/{{ system.name }}.{{ system.domain }}.key | |||||
ssl_crt: /etc/ssl/certs/{{ api.ssl.get('name', grains.id) }}.crt | |||||
ssl_key: /etc/ssl/private/{{ api.ssl.get('name', grains.id) }}.key | |||||
{%- if api.ssl.authority is defined %} | |||||
ssl_chain: /etc/ssl/certs/{{ api.ssl.get('name', grains.id) }}-chain.crt | |||||
{%- endif %} | |||||
{%- else %} | {%- else %} | ||||
ssl_crt: {{ api.ssl.get('cert_file')|default("/etc/ssl/certs/"+grains.get('fqdn')+".crt") }} | ssl_crt: {{ api.ssl.get('cert_file')|default("/etc/ssl/certs/"+grains.get('fqdn')+".crt") }} | ||||
ssl_key: {{ api.ssl.get('key_file')|default("/etc/ssl/private/"+grains.get('fqdn')+".key") }} | ssl_key: {{ api.ssl.get('key_file')|default("/etc/ssl/private/"+grains.get('fqdn')+".key") }} | ||||
{#- | {#- | ||||
vim: syntax=jinja | vim: syntax=jinja | ||||
-#} | -#} | ||||
{% from "salt/map.jinja" import minion with context %} | |||||
beacons: {{ minion.beacon }} |
{% from "salt/map.jinja" import master with context %} | |||||
engines: | |||||
{%- for engine_name, engine in master.engine.items() %} | |||||
{%- set name = engine.get('engine', engine_name) %} | |||||
{%- if engine.engine is defined %}{%- do engine.pop('engine') %}{% endif %} | |||||
- {{ name }}: {{ engine|yaml }} | |||||
{%- endfor %} |
worker_threads: {{ master.worker_threads }} | worker_threads: {{ master.worker_threads }} | ||||
timeout: {{ master.command_timeout }} | timeout: {{ master.command_timeout }} | ||||
{%- if master.get('max_open_files') %} | |||||
max_open_files: {{ master.max_open_files }} | |||||
{%- endif %} | |||||
state_output: {{ master.get('state_output', 'changes') }} | state_output: {{ master.get('state_output', 'changes') }} | ||||
{%- if master.system is defined %} | {%- if master.system is defined %} | ||||
auto_accept: True | auto_accept: True | ||||
{%- endif %} | {%- endif %} | ||||
{%- if master.get('max_event_size') %} | |||||
max_event_size: {{ master.max_event_size }} | |||||
{%- endif %} | |||||
{%- if master.pillar.engine == 'salt' %} | {%- if master.pillar.engine == 'salt' %} | ||||
pillar_roots: | pillar_roots: | ||||
{%- if master.get('order_masters', False) %} | {%- if master.get('order_masters', False) %} | ||||
order_masters: True | order_masters: True | ||||
{%- endif %} | {%- endif %} | ||||
{#- | |||||
vim: syntax=jinja | |||||
-#} |
id: {{ system.name }}.{{ system.domain }} | id: {{ system.name }}.{{ system.domain }} | ||||
{%- for opt in ['max_event_size', 'acceptance_wait_time_max', | |||||
'acceptance_wait_time', 'random_reauth_delay', 'recon_default', 'recon_max', | |||||
'recon_randomize', 'auth_timeout'] %} | |||||
{% if minion.get(opt) %} | |||||
{{ opt }}: {{ minion.get(opt) }} | |||||
{%- endif %} | |||||
{%- endfor %} | |||||
{%- set excluded_keys = ('master', 'system', 'public_keys', 'private_keys', 'known_hosts', '__reclass__', '_secret', '_param') %} | {%- set excluded_keys = ('master', 'system', 'public_keys', 'private_keys', 'known_hosts', '__reclass__', '_secret', '_param') %} | ||||
grains: | grains: | ||||
proxy_port: {{ minion.proxy.port }} | proxy_port: {{ minion.proxy.port }} | ||||
{%- endif %} | {%- endif %} | ||||
{%- if minion.backend is defined %} | |||||
backend: {{ minion.backend }} | |||||
{%- endif %} | |||||
{%- if minion.sentry is defined %} | {%- if minion.sentry is defined %} | ||||
sentry_handler: | sentry_handler: | ||||
{% for server in minion.sentry.servers %} | {% for server in minion.sentry.servers %} | ||||
{%- endif %} | {%- endif %} | ||||
{%- endfor %} | {%- endfor %} | ||||
{#- | |||||
vim: syntax=jinja | |||||
-#} |
{{ provider_name }}: | |||||
provider: {{ provider.engine }} | |||||
{%- if provider.insecure is defined %} | |||||
insecure: true | |||||
{%- endif %} | |||||
{#- location: {{ provider.region }} #} | |||||
personal_access_token: {{ provider.api_key }} | |||||
{%- endif %} |
{{ provider_name }}: | |||||
provider: {{ provider.engine }} | |||||
{%- if provider.insecure is defined %} | |||||
insecure: true | |||||
{%- endif %} |
{{ provider_name }}: | |||||
provider: {{ provider.engine }} | |||||
{%- if provider.insecure is defined %} | |||||
insecure: true | |||||
{%- endif %} | |||||
identity_url: '{{ provider.identity_url }}' | |||||
{%- if provider.compute_name is defined %} | |||||
compute_name: {{ provider.get('compute_name', 'nova') }} | |||||
{%- endif %} | |||||
protocol: ipv4 | |||||
compute_region: {{ provider.region }} | |||||
tenant: {{ provider.tenant }} | |||||
user: {{ provider.user }} | |||||
{%- if provider.api_key is defined %} | |||||
apikey: {{ provider.api_key }} | |||||
{%- elif provider.password is defined %} | |||||
password: {{ provider.password }} | |||||
{%- endif %} | |||||
ssh_key_name: salt-cloud | |||||
ssh_key_file: /root/.ssh/id_rsa | |||||
ssh_interface: {{ provider.get('interface', 'private') }}_ips | |||||
networks: | |||||
- fixed: | |||||
{%- for net in provider.fixed_networks %} | |||||
- {{ net }} | |||||
{%- endfor %} | |||||
- floating: | |||||
{%- for net in provider.floating_networks %} | |||||
- {{ net }} | |||||
{%- endfor %} | |||||
{%- if provider.ignore_cidr is defined %} | |||||
ignore_cidr: {{ provider.ignore_cidr }} | |||||
{%- endif %} |
# This configuration file is used to manage the behavior of all Salt Proxy | # This configuration file is used to manage the behavior of all Salt Proxy | ||||
# Minions on this host. | # Minions on this host. | ||||
master: {{ proxy.master|default('localhost') }} | |||||
master: {{ proxy_minion.master|default('localhost') }} | |||||
multiprocessing: False | multiprocessing: False | ||||
mine_enabled: True | mine_enabled: True |
files: /srv/salt/env | files: /srv/salt/env | ||||
pillar: | pillar: | ||||
engine: salt | engine: salt | ||||
max_event_size: 100000000 | |||||
minion_data_cache: 'localfs' | |||||
{%- endload %} | {%- endload %} | ||||
{%- load_yaml as master_specific %} | {%- load_yaml as master_specific %} | ||||
{%- if pillar.salt.get('minion', {}).get('source', {}).version is defined %} | {%- if pillar.salt.get('minion', {}).get('source', {}).version is defined %} | ||||
version: {{ pillar.salt.minion.source.version }} | version: {{ pillar.salt.minion.source.version }} | ||||
{%- endif %} | {%- endif %} | ||||
max_event_size: 100000000 | |||||
{%- endload %} | {%- endload %} | ||||
{%- load_yaml as minion_specific %} | {%- load_yaml as minion_specific %} | ||||
- PyYAML | - PyYAML | ||||
- M2Crypto | - M2Crypto | ||||
- psutil | - psutil | ||||
cert_pkgs: | |||||
- ca-certificates | |||||
Gentoo: | Gentoo: | ||||
pkgs: | pkgs: | ||||
- app-admin/salt | - app-admin/salt | ||||
- PyYAML | - PyYAML | ||||
- M2Crypto | - M2Crypto | ||||
- psutil | - psutil | ||||
cert_pkgs: | |||||
- ca-certificates | |||||
{%- endload %} | {%- endload %} | ||||
{%- if pillar.salt.minion is defined %} | {%- if pillar.salt.minion is defined %} | ||||
{%- set raw_minion = salt['grains.filter_by'](minion_specific, merge=salt['pillar.get']('salt:minion')) %} | {%- set raw_minion = salt['grains.filter_by'](minion_specific, merge=salt['pillar.get']('salt:minion')) %} | ||||
{%- set minion = salt['grains.filter_by'](minion_common, merge=raw_minion) %} | {%- set minion = salt['grains.filter_by'](minion_common, merge=raw_minion) %} | ||||
{%- if pillar.salt.minion.proxy is defined %} | |||||
{% set proxy = salt['grains.filter_by']({ | |||||
{%- if pillar.salt.minion.proxy_minion is defined %} | |||||
{% set proxy_minion = salt['grains.filter_by']({ | |||||
'Debian': { | 'Debian': { | ||||
'napalm_pkgs': ['python-pip', 'libxml2-dev', 'libxslt1-dev', 'zlib1g-dev'], | 'napalm_pkgs': ['python-pip', 'libxml2-dev', 'libxslt1-dev', 'zlib1g-dev'], | ||||
'napalm_pip_pkgs': ['napalm', 'oauth'] | 'napalm_pip_pkgs': ['napalm', 'oauth'] | ||||
'RedHat': { | 'RedHat': { | ||||
'napalm_pkgs': ['libxml2-dev', 'libxslt1-dev', 'zlib1g-dev'] | 'napalm_pkgs': ['libxml2-dev', 'libxslt1-dev', 'zlib1g-dev'] | ||||
}, | }, | ||||
}, merge=pillar.salt.minion.get('proxy', {})) %} | |||||
}, merge=pillar.salt.minion.get('proxy_minion', {})) %} | |||||
{%- endif %} | {%- endif %} | ||||
{%- endif %} | {%- endif %} |
- /srv/salt/env/{{ master.system.environment }}/_modules | - /srv/salt/env/{{ master.system.environment }}/_modules | ||||
- /srv/salt/env/{{ master.system.environment }}/_states | - /srv/salt/env/{{ master.system.environment }}/_states | ||||
- /srv/salt/env/{{ master.system.environment }}/_grains | - /srv/salt/env/{{ master.system.environment }}/_grains | ||||
- /srv/salt/env/{{ master.system.environment }}/_engines | |||||
- /srv/salt/env/{{ master.system.environment }} | - /srv/salt/env/{{ master.system.environment }} | ||||
- makedirs: True | - makedirs: True | ||||
- target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/{{ formula_name }} | - target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/{{ formula_name }} | ||||
- require: | - require: | ||||
- file: salt_env_{{ environment_name }}_dirs | - file: salt_env_{{ environment_name }}_dirs | ||||
- force: True | |||||
- makedirs: True | |||||
{%- for grain_name, grain in formula.get('grain', {}).iteritems() %} | {%- for grain_name, grain in formula.get('grain', {}).iteritems() %} | ||||
file.symlink: | file.symlink: | ||||
- name: /usr/share/salt-formulas/env/_grains/{{ grain_name }} | - name: /usr/share/salt-formulas/env/_grains/{{ grain_name }} | ||||
- target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/_grains/{{ grain_name }} | - target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/_grains/{{ grain_name }} | ||||
- force: True | |||||
- makedirs: True | |||||
{%- endfor %} | {%- endfor %} | ||||
file.symlink: | file.symlink: | ||||
- name: /usr/share/salt-formulas/env/_modules/{{ module_name }} | - name: /usr/share/salt-formulas/env/_modules/{{ module_name }} | ||||
- target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/_modules/{{ module_name }} | - target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/_modules/{{ module_name }} | ||||
- force: True | |||||
- makedirs: True | |||||
{%- endfor %} | {%- endfor %} | ||||
file.symlink: | file.symlink: | ||||
- name: /usr/share/salt-formulas/env/_states/{{ state_name }} | - name: /usr/share/salt-formulas/env/_states/{{ state_name }} | ||||
- target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/_states/{{ state_name }} | - target: /usr/share/salt-formulas/env/_formulas/{{ formula_name }}/_states/{{ state_name }} | ||||
- force: True | |||||
- makedirs: True | |||||
{%- endfor %} | {%- endfor %} | ||||
- target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/{{ formula_name }} | - target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/{{ formula_name }} | ||||
- require: | - require: | ||||
- file: salt_env_{{ environment_name }}_dirs | - file: salt_env_{{ environment_name }}_dirs | ||||
- force: True | |||||
- makedirs: True | |||||
{%- for grain_name, grain in formula.get('grain', {}).iteritems() %} | {%- for grain_name, grain in formula.get('grain', {}).iteritems() %} | ||||
file.symlink: | file.symlink: | ||||
- name: /srv/salt/env/{{ environment_name }}/_grains/{{ grain_name }} | - name: /srv/salt/env/{{ environment_name }}/_grains/{{ grain_name }} | ||||
- target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/_grains/{{ grain_name }} | - target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/_grains/{{ grain_name }} | ||||
- force: True | |||||
- makedirs: True | |||||
{%- endfor %} | {%- endfor %} | ||||
file.symlink: | file.symlink: | ||||
- name: /srv/salt/env/{{ environment_name }}/_grains/{{ module_name }} | - name: /srv/salt/env/{{ environment_name }}/_grains/{{ module_name }} | ||||
- target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/_grains/{{ module_name }} | - target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/_grains/{{ module_name }} | ||||
- force: True | |||||
- makedirs: True | |||||
{%- endfor %} | {%- endfor %} | ||||
file.symlink: | file.symlink: | ||||
- name: /srv/salt/env/{{ environment_name }}/_grains/{{ state_name }} | - name: /srv/salt/env/{{ environment_name }}/_grains/{{ state_name }} | ||||
- target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/_grains/{{ state_name }} | - target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/_grains/{{ state_name }} | ||||
- force: True | |||||
- makedirs: True | |||||
{%- endfor %} | |||||
{%- for engine_name, engine in formula.get('engine', {}).iteritems() %} | |||||
salt_master_{{ environment_name }}_{{ engine_name }}_state: | |||||
file.symlink: | |||||
- name: /srv/salt/env/{{ environment_name }}/_engines/{{ engine_name }} | |||||
- target: /srv/salt/env/{{ environment_name }}/_formulas/{{ formula_name }}/_engines/{{ engine_name }} | |||||
- force: True | |||||
- makedirs: True | |||||
{%- endfor %} | {%- endfor %} | ||||
{%- if master.source.get('engine', 'pkg') == 'pkg' %} | {%- if master.source.get('engine', 'pkg') == 'pkg' %} | ||||
salt_master_packages: | salt_master_packages: | ||||
pkg.latest: | |||||
pkg.installed: | |||||
- names: {{ master.pkgs }} | - names: {{ master.pkgs }} | ||||
{%- if master.source.version is defined %} | {%- if master.source.version is defined %} | ||||
- version: {{ master.source.version }} | - version: {{ master.source.version }} | ||||
{%- endif %} | {%- endif %} | ||||
{%- if master.engine is defined %} | |||||
/etc/salt/master.d/_engine.conf: | |||||
file.managed: | |||||
- source: salt://salt/files/_engine.conf | |||||
- user: root | |||||
- template: jinja | |||||
- require: | |||||
- {{ master.install_state }} | |||||
- watch_in: | |||||
- service: salt_master_service | |||||
{%- endif %} | |||||
{%- if master.peer is defined %} | {%- if master.peer is defined %} | ||||
/etc/salt/master.d/_peer.conf: | /etc/salt/master.d/_peer.conf: |
{%- from "salt/map.jinja" import master with context %} | |||||
{%- if master.enabled %} | |||||
salt_master_test_packages: | |||||
pkg.latest: | |||||
- names: {{ master.test_pkgs }} | |||||
/etc/salt/roster: | |||||
file.managed: | |||||
- source: salt://salt/files/roster | |||||
- user: root | |||||
- template: jinja | |||||
- require: | |||||
- {{ master.install_state }} | |||||
- watch_in: | |||||
- service: salt_master_service | |||||
{%- endif %} |
graph: | |||||
{%- if pillar.get('salt', {}).minion is defined %} | |||||
{%- from "salt/map.jinja" import minion with context %} | |||||
- host: {{ grains.id }} | |||||
service: salt.minion | |||||
type: software-config | |||||
relations: | |||||
{%- if minion.master is defined %} | |||||
- service: salt.master | |||||
{%- if minion.master.host in ['127.0.0.1', 'localhost'] %} | |||||
host: {{ grains.id }} | |||||
{%- else %} | |||||
host_from_target: {{ minion.master.host }} | |||||
{%- endif %} | |||||
direction: source | |||||
type: tcp-0mq | |||||
{%- endif %} | |||||
{%- endif %} | |||||
{%- if pillar.get('salt', {}).master is defined %} | |||||
{%- from "salt/map.jinja" import master with context %} | |||||
- host: {{ grains.id }} | |||||
service: salt.master | |||||
type: software-config | |||||
relations: | |||||
{%- if master.pillar.engine == 'reclass' %} | |||||
- host: {{ grains.id }} | |||||
service: reclass.storage | |||||
direction: source | |||||
type: local-file | |||||
{%- endif %} | |||||
{%- endif %} |
{%- set service_grains = {'salt': {'graph': []}} %} | |||||
{%- for service_name, service in pillar.items() %} | |||||
{%- set grains_fragment_file = service_name+'/meta/meta.yml' %} | |||||
{%- macro load_grains_file() %}{% include grains_fragment_file ignore missing %}{% endmacro %} | |||||
{%- set grains_yaml = load_grains_file()|load_yaml %} | |||||
{%- if grains_yaml is mapping %} | |||||
{%- for node in grains_yaml.graph %} | |||||
{%- do service_grains.salt.graph.append(node) %} | |||||
{%- endfor %} | |||||
{%- endif %} | |||||
{%- endfor %} | |||||
grain: | |||||
salt: | |||||
{{ service_grains|yaml(False)|indent(4) }} | |||||
orchestrate: | orchestrate: | ||||
master: | master: | ||||
priority: 60 | priority: 60 | ||||
priority: 400 | priority: 400 | ||||
require: | require: | ||||
- salt: salt.master | - salt: salt.master | ||||
minion: | minion: | ||||
{%- if pillar.get('salt', {}).get('minion', {}).get('ca') %} | {%- if pillar.get('salt', {}).get('minion', {}).get('ca') %} | ||||
pki: | pki: | ||||
{%- from "salt/map.jinja" import minion with context %} | {%- from "salt/map.jinja" import minion with context %} | ||||
x509_signing_policies: | x509_signing_policies: | ||||
{%- for ca_name,ca in minion.ca.items() %} | {%- for ca_name,ca in minion.ca.items() %} | ||||
{%- set ca_file = ca.get('ca_file', '/etc/pki/ca/' ~ ca_name ~ '/ca.crt') %} | |||||
{%- set ca_key_file = ca.get('ca_key_file', '/etc/pki/ca/' ~ ca_name ~ '/ca.key') %} | |||||
{%- set ca_certs_dir = salt['file.dirname'](ca_file) ~ '/certs/' %} | |||||
{%- for signing_policy_name, signing_policy in ca.signing_policy.iteritems() %} | {%- for signing_policy_name, signing_policy in ca.signing_policy.iteritems() %} | ||||
{{ ca_name }}_{{ signing_policy_name }}: | {{ ca_name }}_{{ signing_policy_name }}: | ||||
- minions: '{{ signing_policy.minions }}' | - minions: '{{ signing_policy.minions }}' | ||||
- signing_private_key: /etc/pki/ca/{{ ca_name }}/ca.key | |||||
- signing_cert: /etc/pki/ca/{{ ca_name }}/ca.crt | |||||
- signing_private_key: {{ ca_key_file }} | |||||
- signing_cert: {{ ca_file }} | |||||
{%- if ca.country is defined %} | {%- if ca.country is defined %} | ||||
- C: {{ ca.country }} | - C: {{ ca.country }} | ||||
{%- endif %} | {%- endif %} | ||||
- subjectKeyIdentifier: hash | - subjectKeyIdentifier: hash | ||||
- authorityKeyIdentifier: keyid,issuer:always | - authorityKeyIdentifier: keyid,issuer:always | ||||
- days_valid: {{ ca.days_valid.certificate }} | - days_valid: {{ ca.days_valid.certificate }} | ||||
- copypath: /etc/pki/ca/{{ ca_name }}/certs/ | |||||
- copypath: {{ ca_certs_dir }} | |||||
{%- endfor %} | {%- endfor %} | ||||
{%- endfor %} | {%- endfor %} | ||||
{%- endif %} | {%- endif %} |
check: | check: | ||||
local_salt_master_proc: | local_salt_master_proc: | ||||
command: "PATH=$PATH:/usr/lib64/nagios/plugins:/usr/lib/nagios/plugins check_procs -C salt-master -u root -c 1:50" | |||||
command: "PATH=$PATH:/usr/lib64/nagios/plugins:/usr/lib/nagios/plugins check_procs -a salt-master -u root -c 1:50" | |||||
interval: 60 | interval: 60 | ||||
occurrences: 1 | occurrences: 1 | ||||
subscribers: | subscribers: | ||||
- local-salt-master | - local-salt-master | ||||
local_salt_minion_proc: | local_salt_minion_proc: | ||||
command: "PATH=$PATH:/usr/lib64/nagios/plugins:/usr/lib/nagios/plugins check_procs -C salt-minion -u root -c 1:10" | |||||
command: "PATH=$PATH:/usr/lib64/nagios/plugins:/usr/lib/nagios/plugins check_procs -a salt-minion -u root -c 1:10" | |||||
interval: 60 | interval: 60 | ||||
occurrences: 1 | occurrences: 1 | ||||
subscribers: | subscribers: |
{%- for ca_name,ca in minion.ca.iteritems() %} | {%- for ca_name,ca in minion.ca.iteritems() %} | ||||
/etc/pki/ca/{{ ca_name }}/certs: | |||||
{%- set ca_file = ca.get('ca_file', '/etc/pki/ca/' ~ ca_name ~ '/ca.crt') %} | |||||
{%- set ca_key_file = ca.get('ca_key_file', '/etc/pki/ca/' ~ ca_name ~ '/ca.key') %} | |||||
{%- set ca_key_usage = ca.get('key_usage',"critical,cRLSign,keyCertSign") %} | |||||
{%- set ca_dir = salt['file.dirname'](ca_file) %} | |||||
{%- set ca_key_dir = salt['file.dirname'](ca_key_file) %} | |||||
{%- set ca_certs_dir = ca_dir ~ '/certs' %} | |||||
salt_minion_cert_{{ ca_name }}_dirs: | |||||
file.directory: | file.directory: | ||||
- makedirs: true | |||||
- names: | |||||
- {{ ca_dir }} | |||||
- {{ ca_key_dir }} | |||||
- {{ ca_certs_dir }} | |||||
- makedirs: true | |||||
/etc/pki/ca/{{ ca_name }}/ca.key: | |||||
{{ ca_key_file }}: | |||||
x509.private_key_managed: | x509.private_key_managed: | ||||
- bits: 4096 | - bits: 4096 | ||||
- backup: True | - backup: True | ||||
- require: | - require: | ||||
- file: /etc/pki/ca/{{ ca_name }}/certs | |||||
- file: {{ ca_certs_dir }} | |||||
/etc/pki/ca/{{ ca_name }}/ca.crt: | |||||
# TODO: Squash this with the previous state after switch to Salt version >= 2016.11.2 | |||||
{{ ca_name }}_key_permissions: | |||||
file.managed: | |||||
- name: {{ ca_key_file }} | |||||
- mode: {{ ca.get("mode", 0600) }} | |||||
{%- if salt['user.info'](ca.get("user", "root")) %} | |||||
- user: {{ ca.get("user", "root") }} | |||||
{%- endif %} | |||||
{%- if salt['group.info'](ca.get("group", "root")) %} | |||||
- group: {{ ca.get("group", "root") }} | |||||
{%- endif %} | |||||
- replace: false | |||||
- require: | |||||
- x509: {{ ca_key_file }} | |||||
{{ ca_file }}: | |||||
x509.certificate_managed: | x509.certificate_managed: | ||||
- signing_private_key: /etc/pki/ca/{{ ca_name }}/ca.key | |||||
- signing_private_key: {{ ca_key_file }} | |||||
- CN: "{{ ca.common_name }}" | - CN: "{{ ca.common_name }}" | ||||
{%- if ca.country is defined %} | {%- if ca.country is defined %} | ||||
- C: {{ ca.country }} | - C: {{ ca.country }} | ||||
- OU: {{ ca.organization_unit }} | - OU: {{ ca.organization_unit }} | ||||
{%- endif %} | {%- endif %} | ||||
- basicConstraints: "critical,CA:TRUE" | - basicConstraints: "critical,CA:TRUE" | ||||
- keyUsage: "critical,cRLSign,keyCertSign" | |||||
- keyUsage: {{ ca_key_usage }} | |||||
- subjectKeyIdentifier: hash | - subjectKeyIdentifier: hash | ||||
- authorityKeyIdentifier: keyid,issuer:always | - authorityKeyIdentifier: keyid,issuer:always | ||||
- days_valid: {{ ca.days_valid.authority }} | - days_valid: {{ ca.days_valid.authority }} | ||||
- days_remaining: 0 | - days_remaining: 0 | ||||
- backup: True | - backup: True | ||||
- require: | - require: | ||||
- x509: /etc/pki/ca/{{ ca_name }}/ca.key | |||||
- x509: {{ ca_key_file }} | |||||
# TODO: Squash this with the previous state after switch to Salt version >= 2016.11.2 | |||||
{{ ca_name }}_cert_permissions: | |||||
file.managed: | |||||
- name: {{ ca_file }} | |||||
- mode: 0644 | |||||
{%- if salt['user.info'](ca.get("user", "root")) %} | |||||
- user: {{ ca.get("user", "root") }} | |||||
{%- endif %} | |||||
{%- if salt['group.info'](ca.get("group", "root")) %} | |||||
- group: {{ ca.get("group", "root") }} | |||||
{%- endif %} | |||||
- require: | |||||
- x509: {{ ca_file }} | |||||
salt_system_ca_mine_send_ca_{{ ca_name }}: | salt_system_ca_mine_send_ca_{{ ca_name }}: | ||||
module.run: | module.run: | ||||
- name: mine.send | - name: mine.send | ||||
- func: x509.get_pem_entries | - func: x509.get_pem_entries | ||||
- kwargs: | - kwargs: | ||||
glob_path: /etc/pki/ca/{{ ca_name }}/ca.crt | |||||
glob_path: {{ ca_file }} | |||||
- require: | - require: | ||||
- x509: /etc/pki/ca/{{ ca_name }}/ca.crt | |||||
- x509: {{ ca_file }} | |||||
{%- endfor %} | {%- endfor %} | ||||
{%- set key_file = cert.get('key_file', '/etc/ssl/private/' + cert.common_name + '.key') %} | {%- set key_file = cert.get('key_file', '/etc/ssl/private/' + cert.common_name + '.key') %} | ||||
{%- set cert_file = cert.get('cert_file', '/etc/ssl/certs/' + cert.common_name + '.crt') %} | {%- set cert_file = cert.get('cert_file', '/etc/ssl/certs/' + cert.common_name + '.crt') %} | ||||
{%- set ca_file = cert.get('ca_file', '/etc/ssl/certs/ca-' + cert.authority + '.crt') %} | {%- set ca_file = cert.get('ca_file', '/etc/ssl/certs/ca-' + cert.authority + '.crt') %} | ||||
{%- set key_dir = key_file|replace(key_file.split('/')[-1], "") %} | |||||
{%- set cert_dir = cert_file|replace(cert_file.split('/')[-1], "") %} | |||||
{%- set ca_dir = ca_file|replace(ca_file.split('/')[-1], "") %} | |||||
{%- set key_dir = salt['file.dirname'](key_file) %} | |||||
{%- set cert_dir = salt['file.dirname'](cert_file) %} | |||||
{%- set ca_dir = salt['file.dirname'](ca_file) %} | |||||
{# Only ensure directories exists, don't touch permissions, etc. #} | {# Only ensure directories exists, don't touch permissions, etc. #} | ||||
salt_minion_cert_{{ cert_name }}_dirs: | salt_minion_cert_{{ cert_name }}_dirs: | ||||
{{ key_file }}: | {{ key_file }}: | ||||
x509.private_key_managed: | x509.private_key_managed: | ||||
- bits: {{ cert.get('bits', 4096) }} | - bits: {{ cert.get('bits', 4096) }} | ||||
require: | |||||
- file: salt_minion_cert_{{ cert_name }}_dirs | |||||
- require: | |||||
- file: salt_minion_cert_{{ cert_name }}_dirs | |||||
{%- if cert.all_file is defined %} | |||||
- watch_in: | |||||
- cmd: salt_minion_cert_{{ cert_name }}_all | |||||
{%- endif %} | |||||
# TODO: Squash this with the previous state after switch to Salt version >= 2016.11.2 | |||||
{{ key_file }}_key_permissions: | {{ key_file }}_key_permissions: | ||||
file.managed: | file.managed: | ||||
- name: {{ key_file }} | - name: {{ key_file }} | ||||
- group: {{ cert.get("group", "root") }} | - group: {{ cert.get("group", "root") }} | ||||
{%- endif %} | {%- endif %} | ||||
- replace: false | - replace: false | ||||
- watch: | |||||
- require: | |||||
- x509: {{ key_file }} | - x509: {{ key_file }} | ||||
{{ cert_file }}: | {{ cert_file }}: | ||||
- backup: True | - backup: True | ||||
- watch: | - watch: | ||||
- x509: {{ key_file }} | - x509: {{ key_file }} | ||||
{%- if cert.all_file is defined %} | |||||
- watch_in: | |||||
- cmd: salt_minion_cert_{{ cert_name }}_all | |||||
{%- endif %} | |||||
# TODO: Squash this with the previous state after switch to Salt version >= 2016.11.2 | |||||
{{ cert_file }}_cert_permissions: | {{ cert_file }}_cert_permissions: | ||||
file.managed: | file.managed: | ||||
- name: {{ cert_file }} | - name: {{ cert_file }} | ||||
- group: {{ cert.get("group", "root") }} | - group: {{ cert.get("group", "root") }} | ||||
{%- endif %} | {%- endif %} | ||||
- replace: false | - replace: false | ||||
- watch: | |||||
- require: | |||||
- x509: {{ cert_file }} | - x509: {{ cert_file }} | ||||
{%- if cert.host is defined and ca_file not in created_ca_files %} | {%- if cert.host is defined and ca_file not in created_ca_files %} | ||||
- text: {{ ca_cert|replace('\n', '') }} | - text: {{ ca_cert|replace('\n', '') }} | ||||
- watch: | - watch: | ||||
- x509: {{ cert_file }} | - x509: {{ cert_file }} | ||||
{%- if cert.all_file is defined %} | |||||
- watch_in: | |||||
- cmd: salt_minion_cert_{{ cert_name }}_all | |||||
{%- endif %} | |||||
# TODO: Squash this with the previous state after switch to Salt version >= 2016.11.2 | |||||
{{ ca_file }}_cert_permissions: | {{ ca_file }}_cert_permissions: | ||||
file.managed: | file.managed: | ||||
- name: {{ ca_file }} | - name: {{ ca_file }} | ||||
- mode: 0644 | - mode: 0644 | ||||
- watch: | |||||
{%- if salt['user.info'](cert.get("user", "root")) %} | |||||
- user: {{ cert.get("user", "root") }} | |||||
{%- endif %} | |||||
{%- if salt['group.info'](cert.get("group", "root")) %} | |||||
- group: {{ cert.get("group", "root") }} | |||||
{%- endif %} | |||||
- require: | |||||
- x509: {{ ca_file }} | - x509: {{ ca_file }} | ||||
{{ ca_file }}_local_trusted_symlink: | |||||
file.symlink: | |||||
- name: "{{ cacerts_dir }}/ca-{{ cert.authority }}.crt" | |||||
- target: {{ ca_file }} | |||||
- watch_in: | |||||
- cmd: salt_update_certificates | |||||
{%- endif %} | {%- endif %} | ||||
{%- endfor %} | {%- endfor %} | ||||
{%- do created_ca_files.append(ca_file) %} | {%- do created_ca_files.append(ca_file) %} | ||||
{%- endif %} | {%- endif %} | ||||
{%- if cert.all_file is defined %} | {%- if cert.all_file is defined %} | ||||
salt_minion_cert_{{ cert_name }}_all: | salt_minion_cert_{{ cert_name }}_all: | ||||
cmd.wait: | cmd.wait: | ||||
- name: cat {{ key_file }} {{ cert_file }} {{ ca_file }} > {{ cert.all_file }} | - name: cat {{ key_file }} {{ cert_file }} {{ ca_file }} > {{ cert.all_file }} | ||||
- watch: | |||||
- x509: {{ key_file }} | |||||
- x509: {{ cert_file }} | |||||
- x509: {{ ca_file }} | |||||
{{ cert.all_file }}_cert_permissions: | {{ cert.all_file }}_cert_permissions: | ||||
file.managed: | file.managed: | ||||
- group: {{ cert.get("group", "root") }} | - group: {{ cert.get("group", "root") }} | ||||
{%- endif %} | {%- endif %} | ||||
- replace: false | - replace: false | ||||
- watch: | |||||
- require: | |||||
- cmd: salt_minion_cert_{{ cert_name }}_all | - cmd: salt_minion_cert_{{ cert_name }}_all | ||||
{%- endif %} | {%- endif %} | ||||
salt_ca_certificates_packages: | salt_ca_certificates_packages: | ||||
pkg.installed: | pkg.installed: | ||||
{%- if grains.os_family == 'Debian' %} | |||||
- name: ca-certificates | |||||
{%- elif grains.os_family == 'RedHat' %} | |||||
- name: ca-certificates | |||||
{%- else %} | |||||
- name: [] | |||||
{%- endif %} | |||||
- names: {{ minion.cert_pkgs }} | |||||
salt_update_certificates: | salt_update_certificates: | ||||
cmd.wait: | cmd.wait: | ||||
- require: | - require: | ||||
- pkg: salt_ca_certificates_packages | - pkg: salt_ca_certificates_packages | ||||
{%- if minion.get('cert', {}).get('trust_salt_ca', 'True') %} | |||||
{%- if minion.get('trust_salt_ca', True) %} | |||||
{%- for trusted_ca_minion in minion.get('trusted_ca_minions', []) %} | {%- for trusted_ca_minion in minion.get('trusted_ca_minions', []) %} | ||||
{%- for ca_host, certs in salt['mine.get'](trusted_ca_minion+'*', 'x509.get_pem_entries').iteritems() %} | {%- for ca_host, certs in salt['mine.get'](trusted_ca_minion+'*', 'x509.get_pem_entries').iteritems() %} | ||||
{%- for ca_path, ca_cert in certs.iteritems() %} | {%- for ca_path, ca_cert in certs.iteritems() %} | ||||
{%- if not 'ca.crt' in ca_path %}{% continue %}{% endif %} | |||||
{%- if ca_path.endswith('ca.crt') %} | |||||
{%- set cacert_file="ca-"+ca_path.split("/")[4]+".crt" %} | |||||
{# authority name can be obtained only from a cacert path in case of mine.get #} | |||||
{%- set ca_authority = ca_path.split("/")[-2] %} | |||||
{%- set cacert_file="%s/ca-%s.crt" % (cacerts_dir,ca_authority) %} | |||||
salt_cert_{{ cacerts_dir }}/{{ cacert_file }}: | |||||
salt_trust_ca_{{ cacert_file }}: | |||||
x509.pem_managed: | |||||
- name: {{ cacert_file }} | |||||
- text: {{ ca_cert|replace('\n', '') }} | |||||
- watch_in: | |||||
- file: salt_trust_ca_{{ cacert_file }}_permissions | |||||
- cmd: salt_update_certificates | |||||
salt_trust_ca_{{ cacert_file }}_permissions: | |||||
file.managed: | file.managed: | ||||
- name: {{ cacerts_dir }}/{{ cacert_file }} | |||||
- contents: | | |||||
{{ ca_cert|replace(' ', '')|indent(8) }} | |||||
- makedirs: True | |||||
- show_changes: True | |||||
- follow_symlinks: True | |||||
- watch_in: | |||||
- cmd: salt_update_certificates | |||||
- name: {{ cacert_file }} | |||||
- mode: 0444 | |||||
{%- endif %} | |||||
{%- endfor %} | {%- endfor %} | ||||
{%- endfor %} | {%- endfor %} | ||||
{%- endfor %} | {%- endfor %} | ||||
{%- endif %} | {%- endif %} | ||||
{%- endif %} | {%- endif %} | ||||
- salt.minion.ca | - salt.minion.ca | ||||
{%- endif %} | {%- endif %} | ||||
- salt.minion.cert | - salt.minion.cert | ||||
{%- if pillar.salt.minion.proxy is defined %} | |||||
{%- if pillar.salt.minion.proxy_minion is defined %} | |||||
- salt.minion.proxy | - salt.minion.proxy | ||||
{%- endif %} | {%- endif %} |
{%- from "salt/map.jinja" import proxy with context %} | |||||
{%- from "salt/map.jinja" import proxy_minion with context %} | |||||
{%- set napalm = false %} | {%- set napalm = false %} | ||||
{%- for proxy_name, proxy_device in proxy.get('device', {}).iteritems() %} | |||||
{%- for proxy_name, proxy_device in proxy_minion.get('device', {}).iteritems() %} | |||||
{%- if proxy_device.engine == 'napalm' %} | {%- if proxy_device.engine == 'napalm' %} | ||||
- template: jinja | - template: jinja | ||||
- defaults: | - defaults: | ||||
napalm: {{ napalm }} | napalm: {{ napalm }} | ||||
proxy: {{ proxy|yaml }} | |||||
proxy_minion: {{ proxy_minion|yaml }} | |||||
{%- if napalm %} | {%- if napalm %} | ||||
network_proxy_packages: | network_proxy_packages: | ||||
pkg.installed: | pkg.installed: | ||||
- names: {{ proxy.napalm_pkgs }} | |||||
- names: {{ proxy_minion.napalm_pkgs }} | |||||
napalm: | napalm: | ||||
pip.installed: | pip.installed: | ||||
- name: {{ proxy.napalm_pip_pkgs}} | |||||
- name: {{ proxy_minion.napalm_pip_pkgs}} | |||||
- require: | - require: | ||||
- pkg: python-pip | - pkg: python-pip | ||||
{%- endif %} | {%- endif %} | ||||
{%- for proxy_name, proxy_device in proxy.get('device', {}).iteritems() %} | |||||
{%- for proxy_name, proxy_device in proxy_minion.get('device', {}).iteritems() %} | |||||
salt_proxy_{{ proxy_name }}_service: | salt_proxy_{{ proxy_name }}_service: | ||||
service.running: | service.running: |
{%- if minion.source.get('engine', 'pkg') == 'pkg' %} | {%- if minion.source.get('engine', 'pkg') == 'pkg' %} | ||||
salt_minion_packages: | salt_minion_packages: | ||||
pkg.latest: | |||||
pkg.installed: | |||||
- names: {{ minion.pkgs }} | - names: {{ minion.pkgs }} | ||||
{%- if minion.source.version is defined %} | {%- if minion.source.version is defined %} | ||||
- version: {{ minion.source.version }} | - version: {{ minion.source.version }} | ||||
- template: jinja | - template: jinja | ||||
- require: | - require: | ||||
- {{ minion.install_state }} | - {{ minion.install_state }} | ||||
{%- if not grains.get('noservices', False) %} | |||||
- watch_in: | |||||
- service: salt_minion_service | |||||
{%- endif %} | |||||
{%- for service_name, service in pillar.items() %} | {%- for service_name, service in pillar.items() %} | ||||
{%- set support_fragment_file = service_name+'/meta/salt.yml' %} | {%- set support_fragment_file = service_name+'/meta/salt.yml' %} | ||||
- name: /etc/salt/minion.d/_{{ name }}.conf | - name: /etc/salt/minion.d/_{{ name }}.conf | ||||
- contents: | | - contents: | | ||||
{{ conf|yaml(False)|indent(8) }} | {{ conf|yaml(False)|indent(8) }} | ||||
{%- if not grains.get('noservices', False) %} | |||||
- watch_in: | |||||
- cmd: salt_minion_service_restart | |||||
{%- endif %} | |||||
- require: | - require: | ||||
- {{ minion.install_state }} | - {{ minion.install_state }} | ||||
salt_minion_config_{{ service_name }}_{{ name }}_validity_check: | salt_minion_config_{{ service_name }}_{{ name }}_validity_check: | ||||
cmd.wait: | |||||
cmd.run: | |||||
- name: python -c "import yaml; stream = file('/etc/salt/minion.d/_{{ name }}.conf', 'r'); yaml.load(stream); stream.close()" | - name: python -c "import yaml; stream = file('/etc/salt/minion.d/_{{ name }}.conf', 'r'); yaml.load(stream); stream.close()" | ||||
- watch: | |||||
- onchanges: | |||||
- file: salt_minion_config_{{ service_name }}_{{ name }} | - file: salt_minion_config_{{ service_name }}_{{ name }} | ||||
{%- if not grains.get('noservices', False) %} | |||||
- require_in: | |||||
- onchanges_in: | |||||
- cmd: salt_minion_service_restart | - cmd: salt_minion_service_restart | ||||
{%- endif %} | |||||
{%- endfor %} | {%- endfor %} | ||||
{%- endif %} | {%- endif %} | ||||
{%- endfor %} | {%- endfor %} | ||||
{%- if not grains.get('noservices', False) %} | |||||
salt_minion_service: | salt_minion_service: | ||||
service.running: | service.running: | ||||
- name: {{ minion.service }} | - name: {{ minion.service }} | ||||
- require: | - require: | ||||
- pkg: salt_minion_packages | - pkg: salt_minion_packages | ||||
- pkg: salt_minion_dependency_packages | - pkg: salt_minion_dependency_packages | ||||
{%- if grains.get('noservices') %} | |||||
- onlyif: /bin/false | |||||
{%- endif %} | |||||
{#- Restart salt-minion if needed but after all states are executed #} | {#- Restart salt-minion if needed but after all states are executed #} | ||||
salt_minion_service_restart: | salt_minion_service_restart: | ||||
cmd.wait: | |||||
cmd.run: | |||||
- name: 'while true; do salt-call saltutil.running|grep fun: && continue; salt-call --local service.restart {{ minion.service }}; break; done' | - name: 'while true; do salt-call saltutil.running|grep fun: && continue; salt-call --local service.restart {{ minion.service }}; break; done' | ||||
- shell: /bin/bash | - shell: /bin/bash | ||||
- bg: true | - bg: true | ||||
- order: last | |||||
- onchanges: | |||||
- file: /etc/salt/minion.d/minion.conf | |||||
{%- if grains.get('noservices') %} | |||||
- onlyif: /bin/false | |||||
{%- endif %} | |||||
- require: | - require: | ||||
- service: salt_minion_service | - service: salt_minion_service | ||||
{%- endif %} | |||||
salt_minion_sync_all: | salt_minion_sync_all: | ||||
module.run: | module.run: | ||||
- name: 'saltutil.sync_all' | - name: 'saltutil.sync_all' | ||||
{%- if not grains.get('noservices', False) %} | |||||
- watch: | |||||
- onchanges: | |||||
- service: salt_minion_service | - service: salt_minion_service | ||||
{%- endif %} | |||||
- require: | - require: | ||||
- pkg: salt_minion_packages | - pkg: salt_minion_packages | ||||
- pkg: salt_minion_dependency_packages | - pkg: salt_minion_dependency_packages |
linux_state_all_nodes: | |||||
salt.state: | |||||
- tgt: 'linux:system' | |||||
- tgt_type: pillar | |||||
- sls: linux | |||||
- queue: True | |||||
salt_state_all_nodes: | |||||
salt.state: | |||||
- tgt: 'salt:minion' | |||||
- tgt_type: pillar | |||||
- sls: salt.minion | |||||
- queue: True | |||||
- require: | |||||
- salt: linux_state_all_nodes | |||||
openssh_state_all_nodes: | |||||
salt.state: | |||||
- tgt: 'openssh:server' | |||||
- tgt_type: pillar | |||||
- sls: openssh | |||||
- queue: True | |||||
- require: | |||||
- salt: salt_state_all_nodes | |||||
ntp_state_all_nodes: | |||||
salt.state: | |||||
- tgt: 'ntp:client' | |||||
- tgt_type: pillar | |||||
- sls: ntp | |||||
- queue: True | |||||
- require: | |||||
- salt: salt_state_all_nodes |
{%- set node_id = salt['pillar.get']('node_id') %} | |||||
{%- set node_host = salt['pillar.get']('node_host') %} | |||||
linux_state_all_nodes: | |||||
salt.state: | |||||
- tgt: 'salt:master' | |||||
- tgt_type: pillar | |||||
- sls: salt.reactor_sls.key_create | |||||
- queue: True | |||||
- pillar: | |||||
node_id: {{ node_id }} | |||||
node_host: {{ node_host }} | |||||
{%- set node_id = salt['pillar.get']('node_id') %} | |||||
linux_state_all_nodes: | |||||
salt.state: | |||||
- tgt: 'salt:master' | |||||
- tgt_type: pillar | |||||
- sls: salt.reactor_sls.key_remove | |||||
- queue: True | |||||
- pillar: | |||||
node_id: {{ node_id }} | |||||
salt_state_config_node: | |||||
salt.state: | |||||
- tgt: 'salt:master' | |||||
- tgt_type: pillar | |||||
- sls: salt.master | |||||
- queue: True | |||||
reclass_state_config_nodes | |||||
salt.state: | |||||
- tgt: 'reclass:storage' | |||||
- tgt_type: pillar | |||||
- sls: reclass | |||||
- queue: True | |||||
- require: | |||||
- salt: salt_state_config_node | |||||
{%- set node_name = salt['pillar.get']('event_originator') %} | |||||
linux_state: | |||||
salt.state: | |||||
- tgt: '{{ node_name }}' | |||||
- sls: linux | |||||
- queue: True | |||||
salt_state: | |||||
salt.state: | |||||
- tgt: '{{ node_name }}' | |||||
- sls: salt.minion | |||||
- queue: True | |||||
- require: | |||||
- salt: linux_state | |||||
misc_states: | |||||
salt.state: | |||||
- tgt: '{{ node_name }}' | |||||
- sls: ntp,openssh | |||||
- queue: True | |||||
- require: | |||||
- salt: salt_state | |||||
orchestrate_infra_install: | |||||
runner.state.orchestrate: | |||||
- mods: salt.orchestrate.reactor.infra_install | |||||
- queue: True | |||||
{% if data.data.orch_pre_create is defined %} | |||||
orchestrate_node_key_pre_create: | |||||
runner.state.orchestrate: | |||||
- mods: {{ data.data.orch_pre_create }} | |||||
- queue: True | |||||
- pillar: {{ data.data.get('orch_pre_create_pillar', {}) }} | |||||
{% endif %} | |||||
node_key_create: | |||||
runner.state.orchestrate: | |||||
- mods: salt.orchestrate.reactor.key_create | |||||
- queue: True | |||||
- pillar: | |||||
node_id: {{ data.data['node_id'] }} | |||||
node_host: {{ data.data['node_host'] }} | |||||
{% if data.data.orch_post_create is defined %} | |||||
orchestrate_node_key_post_create: | |||||
runner.state.orchestrate: | |||||
- mods: {{ data.data.orch_post_create }} | |||||
- queue: True | |||||
- pillar: {{ data.data.get('orch_post_create_pillar', {}) }} | |||||
{% endif %} |
{% if data.data.orch_pre_remove is defined %} | |||||
orchestrate_node_key_pre_remove: | |||||
runner.state.orchestrate: | |||||
- mods: {{ data.data.orch_pre_remove }} | |||||
- queue: True | |||||
- pillar: {{ data.data.get('orch_pre_remove_pillar', {}) }} | |||||
{% endif %} | |||||
node_key_remove: | |||||
runner.state.orchestrate: | |||||
- mods: salt.orchestrate.reactor.key_remove.sls | |||||
- queue: True | |||||
- pillar: | |||||
node_id: {{ data.data['node_id'] }} | |||||
{% if data.data.orch_post_remove is defined %} | |||||
orchestrate_node_key_post_remove: | |||||
runner.state.orchestrate: | |||||
- mods: {{ data.data.orch_post_remove }} | |||||
- queue: True | |||||
- pillar: {{ data.data.get('orch_post_remove_pillar', {}) }} | |||||
{% endif %} |
minion_sync_all: | |||||
local.saltutil.sync_all: | |||||
- tgt: {{ data.id }} | |||||
- queue: True | |||||
minion_refresh_pillar: | |||||
local.saltutil.refresh_pillar: | |||||
- tgt: {{ data.id }} | |||||
- queue: True | |||||
orchestrate_node_install: | |||||
runner.state.orchestrate: | |||||
- mods: salt.reactor.orchestrate.node_install | |||||
- queue: True | |||||
- pillar: | |||||
event_originator: {{ data.id }} | |||||
orchestrate_orchestrate_run: | |||||
runner.state.orchestrate: | |||||
- mods: {{ data.data.orchestrate }} | |||||
- queue: {{ data.data.get('queue', True) }} | |||||
{%- set node_id = salt['pillar.get']('node_id') %} | |||||
{%- set node_host = salt['pillar.get']('node_host') %} | |||||
key_create_{{ node_id }}: | |||||
module.run: | |||||
saltkey.key_create: | |||||
- id_: {{ node_id }} | |||||
- host: {{ node_host }} | |||||
{%- set node_id = salt['pillar.get']('node_id') %} | |||||
key_create_{{ node_id }}: | |||||
salt.wheel: | |||||
- name: key.delete | |||||
- match: {{ node_id }} |
{%- from "salt/map.jinja" import syndic with context %} | |||||
{%- from "salt/map.jinja" import master, syndic with context %} | |||||
{%- if syndic.enabled %} | {%- if syndic.enabled %} | ||||
include: | include: | ||||
- name: {{ syndic.service }} | - name: {{ syndic.service }} | ||||
- enable: true | - enable: true | ||||
{%- endif %} | |||||
{%- if master.minion_data_cache == 'localfs' %} | |||||
{%- for master in syndic.get('masters', []) %} | |||||
salt_syndic_master_{{ master }}_fingerprint: | |||||
ssh_known_hosts.present: | |||||
- name: {{ master.host }} | |||||
- user: root | |||||
salt_syndic_master_{{ master }}_sync_cache: | |||||
rsync.synchronized: | |||||
- name: {{ master.host }}:/var/cache/salt/master/minions | |||||
- source: /var/cache/salt/master/minions/ | |||||
- prepare: True | |||||
- update: True | |||||
salt_syndic_master_{{ master }}_sync_keys: | |||||
rsync.synchronized: | |||||
- name: {{ master.host }}:/etc/salt/pki/master/minions | |||||
- source: /etc/salt/pki/master/minions/ | |||||
- prepare: True | |||||
- update: True | |||||
{%- else %} | |||||
salt_syndic_master_fingerprint: | |||||
ssh_known_hosts.present: | |||||
- name: {{ syndic.master.host }} | |||||
- user: root | |||||
salt_syndic_master_sync_cache: | |||||
rsync.synchronized: | |||||
- name: {{ syndic.master.host }}:/var/cache/salt/master/minions | |||||
- source: /var/cache/salt/master/minions/ | |||||
- prepare: True | |||||
- update: True | |||||
salt_syndic_master_sync_keys: | |||||
rsync.synchronized: | |||||
- name: {{ syndic.master.host }}:/etc/salt/pki/master/minions | |||||
- source: /etc/salt/pki/master/minions/ | |||||
- prepare: True | |||||
- update: True | |||||
{%- endfor %} | |||||
{%- endif %} | |||||
{%- endif %} | |||||
salt: | |||||
minion: | |||||
enabled: true | |||||
backend: urllib2 |
ca_intermediate: | ca_intermediate: | ||||
type: v3_intermediate_ca | type: v3_intermediate_ca | ||||
minions: '*' | minions: '*' | ||||
salt-ca-alt: | |||||
common_name: Alt CA Testing | |||||
country: Czech | |||||
state: Prague | |||||
locality: Cesky Krumlov | |||||
days_valid: | |||||
authority: 3650 | |||||
certificate: 90 | |||||
signing_policy: | |||||
cert_server: | |||||
type: v3_edge_cert_server | |||||
minions: '*' | |||||
cert_client: | |||||
type: v3_edge_cert_client | |||||
minions: '*' | |||||
ca_edge: | |||||
type: v3_edge_ca | |||||
minions: '*' | |||||
ca_intermediate: | |||||
type: v3_intermediate_ca | |||||
minions: '*' | |||||
ca_file: '/etc/test/ca.crt' | |||||
ca_key_file: '/etc/test/ca.key' | |||||
user: test | |||||
group: test |
# salt.ci.local | # salt.ci.local | ||||
#signing_policy: | #signing_policy: | ||||
# cert_server | # cert_server | ||||
test_cert: | |||||
alternative_names: | |||||
IP:127.0.0.1,DNS:salt.ci.local,DNS:test.ci.local | |||||
cert_file: | |||||
/srv/salt/pki/ci/test.ci.local.crt | |||||
common_name: | |||||
test.ci.local | |||||
key_file: | |||||
/srv/salt/pki/ci/test.ci.local.key | |||||
country: CZ | |||||
state: Prague | |||||
locality: Cesky Krumlov | |||||
signing_cert: | |||||
/etc/test/ca.crt | |||||
signing_private_key: | |||||
/etc/test/ca.key | |||||
# Kitchen-Salt CI trigger `salt-call --local`, below attributes | |||||
# can't be used as there is no required SaltMaster connectivity | |||||
authority: | |||||
salt-ca-alt |
salt: | salt: | ||||
minion: | minion: | ||||
enabled: true | enabled: true | ||||
proxy: | |||||
proxy_minion: | |||||
master: localhost | master: localhost | ||||
device: | device: | ||||
vsrx01.mydomain.local: | vsrx01.mydomain.local: |