awx/awx/main/utils/ansible.py
2021-03-23 15:17:47 -04:00

116 lines
3.9 KiB
Python

# Copyright (c) 2017 Ansible by Red Hat
# All Rights Reserved.
# Python
import codecs
import re
import os
import logging
from configparser import ConfigParser
# Django
from django.utils.encoding import smart_str
logger = logging.getLogger('awx.main.utils.ansible')
__all__ = ['skip_directory', 'could_be_playbook', 'could_be_inventory']
valid_playbook_re = re.compile(r'^\s*?-?\s*?(?:hosts|include|import_playbook):\s*?.*?$')
valid_inventory_re = re.compile(r'^[a-zA-Z0-9_.=\[\]]')
def skip_directory(relative_directory_path):
path_elements = relative_directory_path.split(os.sep)
# Exclude files in a roles subdirectory.
if 'roles' in path_elements:
return True
# Filter files in a tasks subdirectory.
if 'tasks' in path_elements:
return True
for element in path_elements:
# Do not include dot files or dirs
if element.startswith('.'):
return True
# Exclude anything inside of group or host vars directories
if 'group_vars' in path_elements or 'host_vars' in path_elements:
return True
return False
def could_be_playbook(project_path, dir_path, filename):
if os.path.splitext(filename)[-1] not in ['.yml', '.yaml']:
return None
playbook_path = os.path.join(dir_path, filename)
# Filter files that do not have either hosts or top-level
# includes. Use regex to allow files with invalid YAML to
# show up.
matched = False
try:
for n, line in enumerate(codecs.open(playbook_path, 'r', encoding='utf-8', errors='ignore')):
if valid_playbook_re.match(line):
matched = True
break
# Any YAML file can also be encrypted with vault;
# allow these to be used as the main playbook.
elif n == 0 and line.startswith('$ANSIBLE_VAULT;'):
matched = True
break
except IOError:
return None
if not matched:
return None
return os.path.relpath(playbook_path, smart_str(project_path))
def could_be_inventory(project_path, dir_path, filename):
# Decisions based exclusively on filename
inventory_path = os.path.join(dir_path, filename)
inventory_rel_path = os.path.relpath(inventory_path, smart_str(project_path))
suspected_ext = os.path.splitext(filename)[-1]
if filename in ['inventory', 'hosts']:
# Users commonly name their inventory files these names
return inventory_rel_path
elif suspected_ext == '.ini' or os.access(inventory_path, os.X_OK):
# Files with any of these extensions are always included
return inventory_rel_path
elif '.' in suspected_ext:
# If not using those extensions, inventory must have _no_ extension
return None
# Filter files that do not use a character set consistent with
# Ansible inventory mainly
matched = False
try:
# only read through first 10 lines for performance
with open(inventory_path, encoding='utf-8', errors='ignore') as inv_file:
for i, line in enumerate(inv_file):
if i > 10:
break
elif valid_inventory_re.match(line):
matched = True
break
except IOError:
return None
if not matched:
return None
return inventory_rel_path
def read_ansible_config(project_path, variables_of_interest):
fnames = ['/etc/ansible/ansible.cfg']
if project_path:
fnames.append(os.path.join(project_path, 'ansible.cfg'))
values = {}
try:
parser = ConfigParser()
parser.read(fnames)
if 'defaults' in parser:
for var in variables_of_interest:
if var in parser['defaults']:
values[var] = parser['defaults'][var]
except Exception:
logger.exception('Failed to read ansible configuration(s) {}'.format(fnames))
return values