mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-23 13:20:23 -07:00
Making the switch to v2
This commit is contained in:
parent
8cf4452d48
commit
ce3ef7f4c1
486 changed files with 7948 additions and 9070 deletions
396
lib/ansible/playbook/role/__init__.py
Normal file
396
lib/ansible/playbook/role/__init__.py
Normal file
|
@ -0,0 +1,396 @@
|
|||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.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 six import iteritems, string_types
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from hashlib import sha1
|
||||
from types import NoneType
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.parsing import DataLoader
|
||||
from ansible.playbook.attribute import FieldAttribute
|
||||
from ansible.playbook.base import Base
|
||||
from ansible.playbook.become import Become
|
||||
from ansible.playbook.conditional import Conditional
|
||||
from ansible.playbook.helpers import load_list_of_blocks
|
||||
from ansible.playbook.role.include import RoleInclude
|
||||
from ansible.playbook.role.metadata import RoleMetadata
|
||||
from ansible.playbook.taggable import Taggable
|
||||
from ansible.plugins import get_all_plugin_loaders
|
||||
from ansible.utils.vars import combine_vars
|
||||
|
||||
|
||||
__all__ = ['Role', 'ROLE_CACHE', 'hash_params']
|
||||
|
||||
# FIXME: this should be a utility function, but can't be a member of
|
||||
# the role due to the fact that it would require the use of self
|
||||
# in a static method. This is also used in the base class for
|
||||
# strategies (ansible/plugins/strategies/__init__.py)
|
||||
def hash_params(params):
|
||||
if not isinstance(params, dict):
|
||||
return params
|
||||
else:
|
||||
s = set()
|
||||
for k,v in params.iteritems():
|
||||
if isinstance(v, dict):
|
||||
s.update((k, hash_params(v)))
|
||||
elif isinstance(v, list):
|
||||
things = []
|
||||
for item in v:
|
||||
things.append(hash_params(item))
|
||||
s.update((k, tuple(things)))
|
||||
else:
|
||||
s.update((k, v))
|
||||
return frozenset(s)
|
||||
|
||||
# The role cache is used to prevent re-loading roles, which
|
||||
# may already exist. Keys into this cache are the SHA1 hash
|
||||
# of the role definition (for dictionary definitions, this
|
||||
# will be based on the repr() of the dictionary object)
|
||||
ROLE_CACHE = dict()
|
||||
|
||||
|
||||
class Role(Base, Become, Conditional, Taggable):
|
||||
|
||||
def __init__(self):
|
||||
self._role_name = None
|
||||
self._role_path = None
|
||||
self._role_params = dict()
|
||||
self._loader = None
|
||||
|
||||
self._metadata = None
|
||||
self._play = None
|
||||
self._parents = []
|
||||
self._dependencies = []
|
||||
self._task_blocks = []
|
||||
self._handler_blocks = []
|
||||
self._default_vars = dict()
|
||||
self._role_vars = dict()
|
||||
self._had_task_run = False
|
||||
self._completed = False
|
||||
|
||||
super(Role, self).__init__()
|
||||
|
||||
def __repr__(self):
|
||||
return self.get_name()
|
||||
|
||||
def get_name(self):
|
||||
return self._role_name
|
||||
|
||||
@staticmethod
|
||||
def load(role_include, parent_role=None):
|
||||
# FIXME: add back in the role caching support
|
||||
try:
|
||||
# The ROLE_CACHE is a dictionary of role names, with each entry
|
||||
# containing another dictionary corresponding to a set of parameters
|
||||
# specified for a role as the key and the Role() object itself.
|
||||
# We use frozenset to make the dictionary hashable.
|
||||
|
||||
#hashed_params = frozenset(role_include.get_role_params().iteritems())
|
||||
hashed_params = hash_params(role_include.get_role_params())
|
||||
if role_include.role in ROLE_CACHE:
|
||||
for (entry, role_obj) in ROLE_CACHE[role_include.role].iteritems():
|
||||
if hashed_params == entry:
|
||||
if parent_role:
|
||||
role_obj.add_parent(parent_role)
|
||||
return role_obj
|
||||
|
||||
r = Role()
|
||||
r._load_role_data(role_include, parent_role=parent_role)
|
||||
|
||||
if role_include.role not in ROLE_CACHE:
|
||||
ROLE_CACHE[role_include.role] = dict()
|
||||
|
||||
ROLE_CACHE[role_include.role][hashed_params] = r
|
||||
return r
|
||||
|
||||
except RuntimeError:
|
||||
# FIXME: needs a better way to access the ds in the role include
|
||||
raise AnsibleError("A recursion loop was detected with the roles specified. Make sure child roles do not have dependencies on parent roles", obj=role_include._ds)
|
||||
|
||||
def _load_role_data(self, role_include, parent_role=None):
|
||||
self._role_name = role_include.role
|
||||
self._role_path = role_include.get_role_path()
|
||||
self._role_params = role_include.get_role_params()
|
||||
self._variable_manager = role_include.get_variable_manager()
|
||||
self._loader = role_include.get_loader()
|
||||
|
||||
if parent_role:
|
||||
self.add_parent(parent_role)
|
||||
|
||||
# copy over all field attributes, except for when and tags, which
|
||||
# are special cases and need to preserve pre-existing values
|
||||
for (attr_name, _) in iteritems(self._get_base_attributes()):
|
||||
if attr_name not in ('when', 'tags'):
|
||||
setattr(self, attr_name, getattr(role_include, attr_name))
|
||||
|
||||
current_when = getattr(self, 'when')[:]
|
||||
current_when.extend(role_include.when)
|
||||
setattr(self, 'when', current_when)
|
||||
|
||||
current_tags = getattr(self, 'tags')[:]
|
||||
current_tags.extend(role_include.tags)
|
||||
setattr(self, 'tags', current_tags)
|
||||
|
||||
# dynamically load any plugins from the role directory
|
||||
for name, obj in get_all_plugin_loaders():
|
||||
if obj.subdir:
|
||||
plugin_path = os.path.join(self._role_path, obj.subdir)
|
||||
if os.path.isdir(plugin_path):
|
||||
obj.add_directory(plugin_path)
|
||||
|
||||
# load the role's other files, if they exist
|
||||
metadata = self._load_role_yaml('meta')
|
||||
if metadata:
|
||||
self._metadata = RoleMetadata.load(metadata, owner=self, loader=self._loader)
|
||||
self._dependencies = self._load_dependencies()
|
||||
|
||||
task_data = self._load_role_yaml('tasks')
|
||||
if task_data:
|
||||
self._task_blocks = load_list_of_blocks(task_data, play=None, role=self, loader=self._loader)
|
||||
|
||||
handler_data = self._load_role_yaml('handlers')
|
||||
if handler_data:
|
||||
self._handler_blocks = load_list_of_blocks(handler_data, play=None, role=self, loader=self._loader)
|
||||
|
||||
# vars and default vars are regular dictionaries
|
||||
self._role_vars = self._load_role_yaml('vars')
|
||||
if not isinstance(self._role_vars, (dict, NoneType)):
|
||||
raise AnsibleParserError("The vars/main.yml file for role '%s' must contain a dictionary of variables" % self._role_name)
|
||||
elif self._role_vars is None:
|
||||
self._role_vars = dict()
|
||||
|
||||
self._default_vars = self._load_role_yaml('defaults')
|
||||
if not isinstance(self._default_vars, (dict, NoneType)):
|
||||
raise AnsibleParserError("The default/main.yml file for role '%s' must contain a dictionary of variables" % self._role_name)
|
||||
elif self._default_vars is None:
|
||||
self._default_vars = dict()
|
||||
|
||||
def _load_role_yaml(self, subdir):
|
||||
file_path = os.path.join(self._role_path, subdir)
|
||||
if self._loader.path_exists(file_path) and self._loader.is_directory(file_path):
|
||||
main_file = self._resolve_main(file_path)
|
||||
if self._loader.path_exists(main_file):
|
||||
return self._loader.load_from_file(main_file)
|
||||
return None
|
||||
|
||||
def _resolve_main(self, basepath):
|
||||
''' flexibly handle variations in main filenames '''
|
||||
possible_mains = (
|
||||
os.path.join(basepath, 'main.yml'),
|
||||
os.path.join(basepath, 'main.yaml'),
|
||||
os.path.join(basepath, 'main.json'),
|
||||
os.path.join(basepath, 'main'),
|
||||
)
|
||||
|
||||
if sum([self._loader.is_file(x) for x in possible_mains]) > 1:
|
||||
raise AnsibleError("found multiple main files at %s, only one allowed" % (basepath))
|
||||
else:
|
||||
for m in possible_mains:
|
||||
if self._loader.is_file(m):
|
||||
return m # exactly one main file
|
||||
return possible_mains[0] # zero mains (we still need to return something)
|
||||
|
||||
def _load_dependencies(self):
|
||||
'''
|
||||
Recursively loads role dependencies from the metadata list of
|
||||
dependencies, if it exists
|
||||
'''
|
||||
|
||||
deps = []
|
||||
if self._metadata:
|
||||
for role_include in self._metadata.dependencies:
|
||||
r = Role.load(role_include, parent_role=self)
|
||||
deps.append(r)
|
||||
|
||||
return deps
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# other functions
|
||||
|
||||
def add_parent(self, parent_role):
|
||||
''' adds a role to the list of this roles parents '''
|
||||
assert isinstance(parent_role, Role)
|
||||
|
||||
if parent_role not in self._parents:
|
||||
self._parents.append(parent_role)
|
||||
|
||||
def get_parents(self):
|
||||
return self._parents
|
||||
|
||||
def get_default_vars(self):
|
||||
# FIXME: get these from dependent roles too
|
||||
default_vars = dict()
|
||||
for dep in self.get_all_dependencies():
|
||||
default_vars = combine_vars(default_vars, dep.get_default_vars())
|
||||
default_vars = combine_vars(default_vars, self._default_vars)
|
||||
return default_vars
|
||||
|
||||
def get_inherited_vars(self):
|
||||
inherited_vars = dict()
|
||||
for parent in self._parents:
|
||||
inherited_vars = combine_vars(inherited_vars, parent.get_inherited_vars())
|
||||
inherited_vars = combine_vars(inherited_vars, parent._role_vars)
|
||||
inherited_vars = combine_vars(inherited_vars, parent._role_params)
|
||||
return inherited_vars
|
||||
|
||||
def get_vars(self):
|
||||
all_vars = self.get_inherited_vars()
|
||||
|
||||
for dep in self.get_all_dependencies():
|
||||
all_vars = combine_vars(all_vars, dep.get_vars())
|
||||
|
||||
all_vars = combine_vars(all_vars, self._role_vars)
|
||||
all_vars = combine_vars(all_vars, self._role_params)
|
||||
|
||||
return all_vars
|
||||
|
||||
def get_direct_dependencies(self):
|
||||
return self._dependencies[:]
|
||||
|
||||
def get_all_dependencies(self):
|
||||
'''
|
||||
Returns a list of all deps, built recursively from all child dependencies,
|
||||
in the proper order in which they should be executed or evaluated.
|
||||
'''
|
||||
|
||||
child_deps = []
|
||||
|
||||
for dep in self.get_direct_dependencies():
|
||||
for child_dep in dep.get_all_dependencies():
|
||||
child_deps.append(child_dep)
|
||||
child_deps.append(dep)
|
||||
|
||||
return child_deps
|
||||
|
||||
def get_task_blocks(self):
|
||||
return self._task_blocks[:]
|
||||
|
||||
def get_handler_blocks(self):
|
||||
return self._handler_blocks[:]
|
||||
|
||||
def has_run(self):
|
||||
'''
|
||||
Returns true if this role has been iterated over completely and
|
||||
at least one task was run
|
||||
'''
|
||||
|
||||
return self._had_task_run and self._completed
|
||||
|
||||
def compile(self, play, dep_chain=[]):
|
||||
'''
|
||||
Returns the task list for this role, which is created by first
|
||||
recursively compiling the tasks for all direct dependencies, and
|
||||
then adding on the tasks for this role.
|
||||
|
||||
The role compile() also remembers and saves the dependency chain
|
||||
with each task, so tasks know by which route they were found, and
|
||||
can correctly take their parent's tags/conditionals into account.
|
||||
'''
|
||||
|
||||
block_list = []
|
||||
|
||||
# update the dependency chain here
|
||||
new_dep_chain = dep_chain + [self]
|
||||
|
||||
deps = self.get_direct_dependencies()
|
||||
for dep in deps:
|
||||
dep_blocks = dep.compile(play=play, dep_chain=new_dep_chain)
|
||||
for dep_block in dep_blocks:
|
||||
new_dep_block = dep_block.copy()
|
||||
new_dep_block._dep_chain = new_dep_chain
|
||||
new_dep_block._play = play
|
||||
block_list.append(new_dep_block)
|
||||
|
||||
block_list.extend(self._task_blocks)
|
||||
|
||||
return block_list
|
||||
|
||||
def serialize(self, include_deps=True):
|
||||
res = super(Role, self).serialize()
|
||||
|
||||
res['_role_name'] = self._role_name
|
||||
res['_role_path'] = self._role_path
|
||||
res['_role_vars'] = self._role_vars
|
||||
res['_role_params'] = self._role_params
|
||||
res['_default_vars'] = self._default_vars
|
||||
res['_had_task_run'] = self._had_task_run
|
||||
res['_completed'] = self._completed
|
||||
|
||||
if self._metadata:
|
||||
res['_metadata'] = self._metadata.serialize()
|
||||
|
||||
if include_deps:
|
||||
deps = []
|
||||
for role in self.get_direct_dependencies():
|
||||
deps.append(role.serialize())
|
||||
res['_dependencies'] = deps
|
||||
|
||||
parents = []
|
||||
for parent in self._parents:
|
||||
parents.append(parent.serialize(include_deps=False))
|
||||
res['_parents'] = parents
|
||||
|
||||
return res
|
||||
|
||||
def deserialize(self, data, include_deps=True):
|
||||
self._role_name = data.get('_role_name', '')
|
||||
self._role_path = data.get('_role_path', '')
|
||||
self._role_vars = data.get('_role_vars', dict())
|
||||
self._role_params = data.get('_role_params', dict())
|
||||
self._default_vars = data.get('_default_vars', dict())
|
||||
self._had_task_run = data.get('_had_task_run', False)
|
||||
self._completed = data.get('_completed', False)
|
||||
|
||||
if include_deps:
|
||||
deps = []
|
||||
for dep in data.get('_dependencies', []):
|
||||
r = Role()
|
||||
r.deserialize(dep)
|
||||
deps.append(r)
|
||||
setattr(self, '_dependencies', deps)
|
||||
|
||||
parent_data = data.get('_parents', [])
|
||||
parents = []
|
||||
for parent in parent_data:
|
||||
r = Role()
|
||||
r.deserialize(parent, include_deps=False)
|
||||
parents.append(r)
|
||||
setattr(self, '_parents', parents)
|
||||
|
||||
metadata_data = data.get('_metadata')
|
||||
if metadata_data:
|
||||
m = RoleMetadata()
|
||||
m.deserialize(metadata_data)
|
||||
self._metadata = m
|
||||
|
||||
super(Role, self).deserialize(data)
|
||||
|
||||
def set_loader(self, loader):
|
||||
self._loader = loader
|
||||
for parent in self._parents:
|
||||
parent.set_loader(loader)
|
||||
for dep in self.get_direct_dependencies():
|
||||
dep.set_loader(loader)
|
||||
|
175
lib/ansible/playbook/role/definition.py
Normal file
175
lib/ansible/playbook/role/definition.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
# (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 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.utils.path import unfrackpath
|
||||
|
||||
|
||||
__all__ = ['RoleDefinition']
|
||||
|
||||
|
||||
class RoleDefinition(Base, Become, Conditional, Taggable):
|
||||
|
||||
_role = FieldAttribute(isa='string')
|
||||
|
||||
def __init__(self, role_basedir=None):
|
||||
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):
|
||||
|
||||
assert isinstance(ds, dict) or isinstance(ds, string_types)
|
||||
|
||||
if isinstance(ds, dict):
|
||||
ds = super(RoleDefinition, self).preprocess_data(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
|
||||
|
||||
# save the original ds for use later
|
||||
self._ds = ds
|
||||
|
||||
# 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:
|
||||
raise AnsibleError('role definitions must contain a role name', obj=ds)
|
||||
|
||||
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
|
||||
'''
|
||||
|
||||
role_path = unfrackpath(role_name)
|
||||
|
||||
if self._loader.path_exists(role_path):
|
||||
role_name = os.path.basename(role_name)
|
||||
return (role_name, role_path)
|
||||
else:
|
||||
# we always start the search for roles in the base directory of the playbook
|
||||
role_search_paths = [os.path.join(self._loader.get_basedir(), 'roles'), './roles', './']
|
||||
|
||||
# also search in the configured roles path
|
||||
if C.DEFAULT_ROLES_PATH:
|
||||
configured_paths = C.DEFAULT_ROLES_PATH.split(os.pathsep)
|
||||
role_search_paths.extend(configured_paths)
|
||||
|
||||
# finally, 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)
|
||||
|
||||
# now iterate through the possible paths and return the first one we find
|
||||
for path in role_search_paths:
|
||||
role_path = unfrackpath(os.path.join(path, role_name))
|
||||
if self._loader.path_exists(role_path):
|
||||
return (role_name, role_path)
|
||||
|
||||
# FIXME: make the parser smart about list/string entries in
|
||||
# the yaml so the error line/file can be reported here
|
||||
|
||||
raise AnsibleError("the role '%s' was not found" % role_name)
|
||||
|
||||
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()
|
||||
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)
|
||||
if key not in [attr_name for (attr_name, attr_value) in self._get_base_attributes().iteritems()]:
|
||||
# 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
|
49
lib/ansible/playbook/role/include.py
Normal file
49
lib/ansible/playbook/role/include.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
# (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 six import iteritems, string_types
|
||||
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.playbook.attribute import Attribute, FieldAttribute
|
||||
from ansible.playbook.role.definition import RoleDefinition
|
||||
|
||||
|
||||
__all__ = ['RoleInclude']
|
||||
|
||||
|
||||
class RoleInclude(RoleDefinition):
|
||||
|
||||
"""
|
||||
FIXME: docstring
|
||||
"""
|
||||
|
||||
def __init__(self, role_basedir=None):
|
||||
super(RoleInclude, self).__init__(role_basedir=role_basedir)
|
||||
|
||||
@staticmethod
|
||||
def load(data, current_role_path=None, parent_role=None, variable_manager=None, loader=None):
|
||||
assert isinstance(data, string_types) or isinstance(data, dict)
|
||||
|
||||
ri = RoleInclude(role_basedir=current_role_path)
|
||||
return ri.load_data(data, variable_manager=variable_manager, loader=loader)
|
||||
|
91
lib/ansible/playbook/role/metadata.py
Normal file
91
lib/ansible/playbook/role/metadata.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
# (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
|
||||
|
||||
import os
|
||||
|
||||
from six import iteritems, string_types
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.playbook.attribute import Attribute, FieldAttribute
|
||||
from ansible.playbook.base import Base
|
||||
from ansible.playbook.helpers import load_list_of_roles
|
||||
from ansible.playbook.role.include import RoleInclude
|
||||
|
||||
|
||||
__all__ = ['RoleMetadata']
|
||||
|
||||
|
||||
class RoleMetadata(Base):
|
||||
'''
|
||||
This class wraps the parsing and validation of the optional metadata
|
||||
within each Role (meta/main.yml).
|
||||
'''
|
||||
|
||||
_allow_duplicates = FieldAttribute(isa='bool', default=False)
|
||||
_dependencies = FieldAttribute(isa='list', default=[])
|
||||
_galaxy_info = FieldAttribute(isa='GalaxyInfo')
|
||||
|
||||
def __init__(self, owner=None):
|
||||
self._owner = owner
|
||||
super(RoleMetadata, self).__init__()
|
||||
|
||||
@staticmethod
|
||||
def load(data, owner, variable_manager=None, loader=None):
|
||||
'''
|
||||
Returns a new RoleMetadata object based on the datastructure passed in.
|
||||
'''
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise AnsibleParserError("the 'meta/main.yml' for role %s is not a dictionary" % owner.get_name())
|
||||
|
||||
m = RoleMetadata(owner=owner).load_data(data, variable_manager=variable_manager, loader=loader)
|
||||
return m
|
||||
|
||||
def _load_dependencies(self, attr, ds):
|
||||
'''
|
||||
This is a helper loading function for the dependencies list,
|
||||
which returns a list of RoleInclude objects
|
||||
'''
|
||||
|
||||
current_role_path = None
|
||||
if self._owner:
|
||||
current_role_path = os.path.dirname(self._owner._role_path)
|
||||
|
||||
return load_list_of_roles(ds, current_role_path=current_role_path, variable_manager=self._variable_manager, loader=self._loader)
|
||||
|
||||
def _load_galaxy_info(self, attr, ds):
|
||||
'''
|
||||
This is a helper loading function for the galaxy info entry
|
||||
in the metadata, which returns a GalaxyInfo object rather than
|
||||
a simple dictionary.
|
||||
'''
|
||||
|
||||
return ds
|
||||
|
||||
def serialize(self):
|
||||
return dict(
|
||||
allow_duplicates = self._allow_duplicates,
|
||||
dependencies = self._dependencies,
|
||||
)
|
||||
|
||||
def deserialize(self, data):
|
||||
setattr(self, 'allow_duplicates', data.get('allow_duplicates', False))
|
||||
setattr(self, 'dependencies', data.get('dependencies', []))
|
166
lib/ansible/playbook/role/requirement.py
Normal file
166
lib/ansible/playbook/role/requirement.py
Normal file
|
@ -0,0 +1,166 @@
|
|||
# (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 six import iteritems, string_types
|
||||
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.playbook.role.definition import RoleDefinition
|
||||
|
||||
__all__ = ['RoleRequirement']
|
||||
|
||||
|
||||
class RoleRequirement(RoleDefinition):
|
||||
|
||||
"""
|
||||
FIXME: document various ways role specs can be specified
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _get_valid_spec_keys(self):
|
||||
return (
|
||||
'name',
|
||||
'role',
|
||||
'scm',
|
||||
'src',
|
||||
'version',
|
||||
)
|
||||
|
||||
def parse(self, ds):
|
||||
'''
|
||||
FIXME: docstring
|
||||
'''
|
||||
|
||||
assert type(ds) == dict or isinstance(ds, string_types)
|
||||
|
||||
role_name = ''
|
||||
role_params = dict()
|
||||
new_ds = dict()
|
||||
|
||||
if isinstance(ds, string_types):
|
||||
role_name = ds
|
||||
else:
|
||||
ds = self._preprocess_role_spec(ds)
|
||||
(new_ds, role_params) = self._split_role_params(ds)
|
||||
|
||||
# pull the role name out of the ds
|
||||
role_name = new_ds.get('role_name')
|
||||
del ds['role_name']
|
||||
|
||||
return (new_ds, role_name, role_params)
|
||||
|
||||
def _preprocess_role_spec(self, ds):
|
||||
if 'role' in ds:
|
||||
# Old style: {role: "galaxy.role,version,name", other_vars: "here" }
|
||||
role_info = self._role_spec_parse(ds['role'])
|
||||
if isinstance(role_info, dict):
|
||||
# Warning: Slight change in behaviour here. name may be being
|
||||
# overloaded. Previously, name was only a parameter to the role.
|
||||
# Now it is both a parameter to the role and the name that
|
||||
# ansible-galaxy will install under on the local system.
|
||||
if 'name' in ds and 'name' in role_info:
|
||||
del role_info['name']
|
||||
ds.update(role_info)
|
||||
else:
|
||||
# New style: { src: 'galaxy.role,version,name', other_vars: "here" }
|
||||
if 'github.com' in ds["src"] and 'http' in ds["src"] and '+' not in ds["src"] and not ds["src"].endswith('.tar.gz'):
|
||||
ds["src"] = "git+" + ds["src"]
|
||||
|
||||
if '+' in ds["src"]:
|
||||
(scm, src) = ds["src"].split('+')
|
||||
ds["scm"] = scm
|
||||
ds["src"] = src
|
||||
|
||||
if 'name' in ds:
|
||||
ds["role"] = ds["name"]
|
||||
del ds["name"]
|
||||
else:
|
||||
ds["role"] = self._repo_url_to_role_name(ds["src"])
|
||||
|
||||
# set some values to a default value, if none were specified
|
||||
ds.setdefault('version', '')
|
||||
ds.setdefault('scm', None)
|
||||
|
||||
return ds
|
||||
|
||||
def _repo_url_to_role_name(self, repo_url):
|
||||
# gets the role name out of a repo like
|
||||
# http://git.example.com/repos/repo.git" => "repo"
|
||||
|
||||
if '://' not in repo_url and '@' not in repo_url:
|
||||
return repo_url
|
||||
trailing_path = repo_url.split('/')[-1]
|
||||
if trailing_path.endswith('.git'):
|
||||
trailing_path = trailing_path[:-4]
|
||||
if trailing_path.endswith('.tar.gz'):
|
||||
trailing_path = trailing_path[:-7]
|
||||
if ',' in trailing_path:
|
||||
trailing_path = trailing_path.split(',')[0]
|
||||
return trailing_path
|
||||
|
||||
def _role_spec_parse(self, role_spec):
|
||||
# takes a repo and a version like
|
||||
# git+http://git.example.com/repos/repo.git,v1.0
|
||||
# and returns a list of properties such as:
|
||||
# {
|
||||
# 'scm': 'git',
|
||||
# 'src': 'http://git.example.com/repos/repo.git',
|
||||
# 'version': 'v1.0',
|
||||
# 'name': 'repo'
|
||||
# }
|
||||
|
||||
default_role_versions = dict(git='master', hg='tip')
|
||||
|
||||
role_spec = role_spec.strip()
|
||||
role_version = ''
|
||||
if role_spec == "" or role_spec.startswith("#"):
|
||||
return (None, None, None, None)
|
||||
|
||||
tokens = [s.strip() for s in role_spec.split(',')]
|
||||
|
||||
# assume https://github.com URLs are git+https:// URLs and not
|
||||
# tarballs unless they end in '.zip'
|
||||
if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'):
|
||||
tokens[0] = 'git+' + tokens[0]
|
||||
|
||||
if '+' in tokens[0]:
|
||||
(scm, role_url) = tokens[0].split('+')
|
||||
else:
|
||||
scm = None
|
||||
role_url = tokens[0]
|
||||
|
||||
if len(tokens) >= 2:
|
||||
role_version = tokens[1]
|
||||
|
||||
if len(tokens) == 3:
|
||||
role_name = tokens[2]
|
||||
else:
|
||||
role_name = self._repo_url_to_role_name(tokens[0])
|
||||
|
||||
if scm and not role_version:
|
||||
role_version = default_role_versions.get(scm, '')
|
||||
|
||||
return dict(scm=scm, src=role_url, version=role_version, role_name=role_name)
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue