mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-26 04:11:25 -07:00
With 2.0, we decided to create a special list of param names which were taken out of the role data structure and stored as params instead (connection, port, and remote_user). This causes problems with inheritance of these params, so we are now deprecating that while also keeping those keys in the ds so they are brought in as attributes on the Role correctly. Fixes #17395
223 lines
8.8 KiB
Python
223 lines
8.8 KiB
Python
# (c) 2014 Michael DeHaan, <michael@ansible.com>
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# Make coding more python3-ish
|
|
from __future__ import (absolute_import, division, print_function)
|
|
__metaclass__ = type
|
|
|
|
from ansible.compat.six import iteritems, string_types
|
|
|
|
import os
|
|
|
|
from ansible import constants as C
|
|
from ansible.errors import AnsibleError
|
|
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
|
|
from ansible.playbook.attribute import Attribute, FieldAttribute
|
|
from ansible.playbook.base import Base
|
|
from ansible.playbook.become import Become
|
|
from ansible.playbook.conditional import Conditional
|
|
from ansible.playbook.taggable import Taggable
|
|
from ansible.template import Templar
|
|
from ansible.utils.path import unfrackpath
|
|
|
|
try:
|
|
from __main__ import display
|
|
except ImportError:
|
|
from ansible.utils.display import Display
|
|
display = Display()
|
|
|
|
|
|
__all__ = ['RoleDefinition']
|
|
|
|
|
|
class RoleDefinition(Base, Become, Conditional, Taggable):
|
|
|
|
_role = FieldAttribute(isa='string')
|
|
|
|
def __init__(self, play=None, role_basedir=None, variable_manager=None, loader=None):
|
|
self._play = play
|
|
self._variable_manager = variable_manager
|
|
self._loader = loader
|
|
|
|
self._role_path = None
|
|
self._role_basedir = role_basedir
|
|
self._role_params = dict()
|
|
super(RoleDefinition, self).__init__()
|
|
|
|
#def __repr__(self):
|
|
# return 'ROLEDEF: ' + self._attributes.get('role', '<no name set>')
|
|
|
|
@staticmethod
|
|
def load(data, variable_manager=None, loader=None):
|
|
raise AnsibleError("not implemented")
|
|
|
|
def preprocess_data(self, ds):
|
|
# role names that are simply numbers can be parsed by PyYAML
|
|
# as integers even when quoted, so turn it into a string type
|
|
if isinstance(ds, int):
|
|
ds = "%s" % ds
|
|
|
|
assert isinstance(ds, dict) or isinstance(ds, string_types) or isinstance(ds, AnsibleBaseYAMLObject)
|
|
|
|
if isinstance(ds, dict):
|
|
ds = super(RoleDefinition, self).preprocess_data(ds)
|
|
|
|
# save the original ds for use later
|
|
self._ds = ds
|
|
|
|
# we create a new data structure here, using the same
|
|
# object used internally by the YAML parsing code so we
|
|
# can preserve file:line:column information if it exists
|
|
new_ds = AnsibleMapping()
|
|
if isinstance(ds, AnsibleBaseYAMLObject):
|
|
new_ds.ansible_pos = ds.ansible_pos
|
|
|
|
# first we pull the role name out of the data structure,
|
|
# and then use that to determine the role path (which may
|
|
# result in a new role name, if it was a file path)
|
|
role_name = self._load_role_name(ds)
|
|
(role_name, role_path) = self._load_role_path(role_name)
|
|
|
|
# next, we split the role params out from the valid role
|
|
# attributes and update the new datastructure with that
|
|
# result and the role name
|
|
if isinstance(ds, dict):
|
|
(new_role_def, role_params) = self._split_role_params(ds)
|
|
new_ds.update(new_role_def)
|
|
self._role_params = role_params
|
|
|
|
# set the role name in the new ds
|
|
new_ds['role'] = role_name
|
|
|
|
# we store the role path internally
|
|
self._role_path = role_path
|
|
|
|
# and return the cleaned-up data structure
|
|
return new_ds
|
|
|
|
def _load_role_name(self, ds):
|
|
'''
|
|
Returns the role name (either the role: or name: field) from
|
|
the role definition, or (when the role definition is a simple
|
|
string), just that string
|
|
'''
|
|
|
|
if isinstance(ds, string_types):
|
|
return ds
|
|
|
|
role_name = ds.get('role', ds.get('name'))
|
|
if not role_name or not isinstance(role_name, string_types):
|
|
raise AnsibleError('role definitions must contain a role name', obj=ds)
|
|
|
|
# if we have the required datastructures, and if the role_name
|
|
# contains a variable, try and template it now
|
|
if self._variable_manager:
|
|
all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)
|
|
templar = Templar(loader=self._loader, variables=all_vars)
|
|
if templar._contains_vars(role_name):
|
|
role_name = templar.template(role_name)
|
|
|
|
return role_name
|
|
|
|
def _load_role_path(self, role_name):
|
|
'''
|
|
the 'role', as specified in the ds (or as a bare string), can either
|
|
be a simple name or a full path. If it is a full path, we use the
|
|
basename as the role name, otherwise we take the name as-given and
|
|
append it to the default role path
|
|
'''
|
|
|
|
# we always start the search for roles in the base directory of the playbook
|
|
role_search_paths = [
|
|
os.path.join(self._loader.get_basedir(), u'roles'),
|
|
]
|
|
|
|
# also search in the configured roles path
|
|
if C.DEFAULT_ROLES_PATH:
|
|
role_search_paths.extend(C.DEFAULT_ROLES_PATH)
|
|
|
|
# next, append the roles basedir, if it was set, so we can
|
|
# search relative to that directory for dependent roles
|
|
if self._role_basedir:
|
|
role_search_paths.append(self._role_basedir)
|
|
|
|
# finally as a last resort we look in the current basedir as set
|
|
# in the loader (which should be the playbook dir itself) but without
|
|
# the roles/ dir appended
|
|
role_search_paths.append(self._loader.get_basedir())
|
|
|
|
# create a templar class to template the dependency names, in
|
|
# case they contain variables
|
|
if self._variable_manager is not None:
|
|
all_vars = self._variable_manager.get_vars(loader=self._loader, play=self._play)
|
|
else:
|
|
all_vars = dict()
|
|
|
|
templar = Templar(loader=self._loader, variables=all_vars)
|
|
role_name = templar.template(role_name)
|
|
|
|
# now iterate through the possible paths and return the first one we find
|
|
for path in role_search_paths:
|
|
path = templar.template(path)
|
|
role_path = unfrackpath(os.path.join(path, role_name))
|
|
if self._loader.path_exists(role_path):
|
|
return (role_name, role_path)
|
|
|
|
# if not found elsewhere try to extract path from name
|
|
role_path = unfrackpath(role_name)
|
|
if self._loader.path_exists(role_path):
|
|
role_name = os.path.basename(role_name)
|
|
return (role_name, role_path)
|
|
|
|
raise AnsibleError("the role '%s' was not found in %s" % (role_name, ":".join(role_search_paths)), obj=self._ds)
|
|
|
|
def _split_role_params(self, ds):
|
|
'''
|
|
Splits any random role params off from the role spec and store
|
|
them in a dictionary of params for parsing later
|
|
'''
|
|
|
|
role_def = dict()
|
|
role_params = dict()
|
|
base_attribute_names = frozenset(self._valid_attrs.keys())
|
|
for (key, value) in iteritems(ds):
|
|
# use the list of FieldAttribute values to determine what is and is not
|
|
# an extra parameter for this role (or sub-class of this role)
|
|
# FIXME: hard-coded list of exception key names here corresponds to the
|
|
# connection fields in the Base class. There may need to be some
|
|
# other mechanism where we exclude certain kinds of field attributes,
|
|
# or make this list more automatic in some way so we don't have to
|
|
# remember to update it manually.
|
|
if key not in base_attribute_names or key in ('connection', 'port', 'remote_user'):
|
|
if key in ('connection', 'port', 'remote_user'):
|
|
display.deprecated("Using '%s' as a role param has been deprecated. " % key + \
|
|
"In the future, these values should be entered in the `vars:` " + \
|
|
"section for roles, but for now we'll store it as both a param and an attribute.")
|
|
role_def[key] = value
|
|
# this key does not match a field attribute, so it must be a role param
|
|
role_params[key] = value
|
|
else:
|
|
# this is a field attribute, so copy it over directly
|
|
role_def[key] = value
|
|
|
|
return (role_def, role_params)
|
|
|
|
def get_role_params(self):
|
|
return self._role_params.copy()
|
|
|
|
def get_role_path(self):
|
|
return self._role_path
|