Vendor Galaxy Roles and Collections
This commit is contained in:
parent
c1e1897cda
commit
2aed20393f
3553 changed files with 387444 additions and 2 deletions
246
ansible_collections/community/sops/plugins/vars/sops.py
Normal file
246
ansible_collections/community/sops/plugins/vars/sops.py
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
# Copyright (c) 2018 Edoardo Tenani <e.tenani@arduino.cc> (@endorama)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: sops
|
||||
author: Edoardo Tenani (@endorama) <e.tenani@arduino.cc>
|
||||
short_description: Loading SOPS-encrypted vars files
|
||||
version_added: '0.1.0'
|
||||
description:
|
||||
- Load encrypted YAML files into corresponding groups/hosts in C(group_vars/) and C(host_vars/) directories.
|
||||
- Files are encrypted prior to reading, making this plugin an effective companion to P(ansible.builtin.host_group_vars#vars)
|
||||
plugin.
|
||||
- Files are restricted to V(.sops.yaml), V(.sops.yml), V(.sops.json) extensions, unless configured otherwise with O(valid_extensions).
|
||||
- Hidden files are ignored.
|
||||
options:
|
||||
valid_extensions:
|
||||
default: [".sops.yml", ".sops.yaml", ".sops.json"]
|
||||
description:
|
||||
- Check all of these extensions when looking for 'variable' files.
|
||||
- These files must be SOPS encrypted YAML or JSON files.
|
||||
- By default the plugin will produce errors when encountering files matching these extensions that are not SOPS encrypted.
|
||||
This behavior can be controlled with the O(handle_unencrypted_files) option.
|
||||
type: list
|
||||
elements: string
|
||||
ini:
|
||||
- key: valid_extensions
|
||||
section: community.sops
|
||||
version_added: 1.7.0
|
||||
env:
|
||||
- name: ANSIBLE_VARS_SOPS_PLUGIN_VALID_EXTENSIONS
|
||||
version_added: 1.7.0
|
||||
stage:
|
||||
version_added: 0.2.0
|
||||
ini:
|
||||
- key: vars_stage
|
||||
section: community.sops
|
||||
env:
|
||||
- name: ANSIBLE_VARS_SOPS_PLUGIN_STAGE
|
||||
cache:
|
||||
description:
|
||||
- Whether to cache decrypted files or not.
|
||||
- If the cache is disabled, the files will be decrypted for almost every task. This is very slow!
|
||||
- Only disable caching if you modify the variable files during a playbook run and want the updated result to be available
|
||||
from the next task on.
|
||||
- 'Note that setting O(stage=inventory) has the same effect as setting O(cache=true): the variables will be loaded only
|
||||
once (during inventory loading) and the vars plugin will not be called for every task.'
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 0.2.0
|
||||
ini:
|
||||
- key: vars_cache
|
||||
section: community.sops
|
||||
env:
|
||||
- name: ANSIBLE_VARS_SOPS_PLUGIN_CACHE
|
||||
disable_vars_plugin_temporarily:
|
||||
description:
|
||||
- Temporarily disable this plugin.
|
||||
- Useful if ansible-inventory is supposed to be run without decrypting secrets (in AWX for instance).
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 1.3.0
|
||||
env:
|
||||
- name: SOPS_ANSIBLE_AWX_DISABLE_VARS_PLUGIN_TEMPORARILY
|
||||
handle_unencrypted_files:
|
||||
description:
|
||||
- How to handle files that match the extensions in O(valid_extensions) that are not SOPS encrypted.
|
||||
- The default value V(error) will produce an error.
|
||||
- The value V(skip) will simply skip these files. This requires SOPS 3.9.0 or later.
|
||||
- The value V(warn) will skip these files and emit a warning. This requires SOPS 3.9.0 or later.
|
||||
- B(Note) that this will not help if the store SOPS uses cannot parse the file, for example because it is no valid JSON/YAML/...
|
||||
file despite its file extension. For extensions other than the default ones SOPS uses the binary store, which tries
|
||||
to parse the file as JSON.
|
||||
type: string
|
||||
choices:
|
||||
- skip
|
||||
- warn
|
||||
- error
|
||||
default: error
|
||||
version_added: 1.8.0
|
||||
ini:
|
||||
- key: handle_unencrypted_files
|
||||
section: community.sops
|
||||
env:
|
||||
- name: ANSIBLE_VARS_SOPS_PLUGIN_HANDLE_UNENCRYPTED_FILES
|
||||
extends_documentation_fragment:
|
||||
- ansible.builtin.vars_plugin_staging
|
||||
- community.sops.sops
|
||||
- community.sops.sops.ansible_env
|
||||
- community.sops.sops.ansible_ini
|
||||
seealso:
|
||||
- plugin: community.sops.sops
|
||||
plugin_type: lookup
|
||||
description: The sops lookup can be used decrypt SOPS-encrypted files.
|
||||
- plugin: community.sops.decrypt
|
||||
plugin_type: filter
|
||||
description: The decrypt filter can be used to decrypt SOPS-encrypted in-memory data.
|
||||
- module: community.sops.load_vars
|
||||
"""
|
||||
|
||||
import os
|
||||
from collections.abc import Sequence, Mapping
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.inventory.host import Host
|
||||
from ansible.inventory.group import Group
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible.plugins.vars import BaseVarsPlugin
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.vars import combine_vars
|
||||
from ansible_collections.community.sops.plugins.module_utils.sops import Sops, SopsError
|
||||
|
||||
try:
|
||||
from ansible.template import trust_as_template as _trust_as_template
|
||||
HAS_DATATAGGING = True
|
||||
except ImportError:
|
||||
HAS_DATATAGGING = False
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
FOUND = {}
|
||||
DECRYPTED = {}
|
||||
|
||||
|
||||
def _make_safe(value):
|
||||
if isinstance(value, str):
|
||||
# must come *before* Sequence, as strings are also instances of Sequence
|
||||
if HAS_DATATAGGING and isinstance(value, str):
|
||||
return _trust_as_template(value)
|
||||
return value
|
||||
if isinstance(value, Sequence):
|
||||
return [_make_safe(v) for v in value]
|
||||
if isinstance(value, Mapping):
|
||||
return dict((k, _make_safe(v)) for k, v in value.items())
|
||||
return value
|
||||
|
||||
|
||||
class VarsModule(BaseVarsPlugin):
|
||||
|
||||
def get_vars(self, loader, path, entities, cache=None):
|
||||
''' parses the inventory file '''
|
||||
|
||||
if not isinstance(entities, list):
|
||||
entities = [entities]
|
||||
|
||||
super().get_vars(loader, path, entities)
|
||||
|
||||
def get_option_value(argument_name):
|
||||
return self.get_option(argument_name)
|
||||
|
||||
if cache is None:
|
||||
cache = self.get_option('cache')
|
||||
|
||||
if self.get_option('disable_vars_plugin_temporarily'):
|
||||
return {}
|
||||
|
||||
valid_extensions = self.get_option('valid_extensions')
|
||||
handle_unencrypted_files = self.get_option('handle_unencrypted_files')
|
||||
|
||||
data = {}
|
||||
for entity in entities:
|
||||
if isinstance(entity, Host):
|
||||
subdir = 'host_vars'
|
||||
elif isinstance(entity, Group):
|
||||
subdir = 'group_vars'
|
||||
else:
|
||||
raise AnsibleParserError("Supplied entity must be Host or Group, got %s instead" % (type(entity)))
|
||||
|
||||
# avoid 'chroot' type inventory hostnames /path/to/chroot
|
||||
if not entity.name.startswith(os.path.sep):
|
||||
try:
|
||||
found_files = []
|
||||
# load vars
|
||||
b_opath = os.path.realpath(to_bytes(os.path.join(self._basedir, subdir)))
|
||||
opath = to_text(b_opath)
|
||||
key = '%s.%s' % (entity.name, opath)
|
||||
self._display.vvvv("key: %s" % (key))
|
||||
if cache and key in FOUND:
|
||||
found_files = FOUND[key]
|
||||
else:
|
||||
# no need to do much if path does not exist for basedir
|
||||
if os.path.exists(b_opath):
|
||||
if os.path.isdir(b_opath):
|
||||
self._display.debug("\tprocessing dir %s" % opath)
|
||||
# NOTE: iterating without extension allow retrieving files recursively
|
||||
# A filter is then applied by iterating on all results and filtering by
|
||||
# extension.
|
||||
# See:
|
||||
# - https://github.com/ansible-collections/community.sops/pull/6
|
||||
found_files = loader.find_vars_files(opath, entity.name, extensions=valid_extensions, allow_dir=False)
|
||||
found_files.extend([file_path for file_path in loader.find_vars_files(opath, entity.name)
|
||||
if any(to_text(file_path).endswith(extension) for extension in valid_extensions)])
|
||||
FOUND[key] = found_files
|
||||
else:
|
||||
self._display.warning("Found %s that is not a directory, skipping: %s" % (subdir, opath))
|
||||
|
||||
for found in found_files:
|
||||
if cache and found in DECRYPTED:
|
||||
file_content = DECRYPTED[found]
|
||||
else:
|
||||
sops_runner = Sops.get_sops_runner_from_options(get_option_value, display=display)
|
||||
if handle_unencrypted_files != 'error' and not sops_runner.has_filestatus():
|
||||
raise AnsibleParserError(
|
||||
'Cannot use handle_unencrypted_files=%s with SOPS %s' % (handle_unencrypted_files, sops_runner.version_string)
|
||||
)
|
||||
try:
|
||||
file_content = sops_runner.decrypt(found, get_option_value=get_option_value)
|
||||
except SopsError as exc:
|
||||
skip = False
|
||||
if sops_runner.has_filestatus():
|
||||
# Check whether sops thinks the file might be encrypted. If it thinks it is not,
|
||||
# skip it. Otherwise, re-raise the original error
|
||||
try:
|
||||
file_status = sops_runner.get_filestatus(found)
|
||||
if not file_status.encrypted:
|
||||
if handle_unencrypted_files == 'skip':
|
||||
self._display.vvvv("SOPS vars plugin: skipping unencrypted file %s" % found)
|
||||
skip = True
|
||||
elif handle_unencrypted_files == 'warn':
|
||||
self._display.warning("SOPS vars plugin: skipping unencrypted file %s" % found)
|
||||
skip = True
|
||||
elif handle_unencrypted_files == 'error':
|
||||
raise AnsibleParserError("SOPS vars plugin: file %s is not encrypted" % found)
|
||||
except SopsError as status_exc:
|
||||
# The filestatus operation can fail for example if sops cannot parse the file
|
||||
# as JSON/YAML. In that case, also re-raise the original error
|
||||
self._display.warning("SOPS vars plugin: cannot obtain file status of %s: %s" % (found, status_exc))
|
||||
if skip:
|
||||
continue
|
||||
raise
|
||||
DECRYPTED[found] = file_content
|
||||
new_data = _make_safe(loader.load(file_content))
|
||||
if new_data: # ignore empty files
|
||||
data = combine_vars(data, new_data)
|
||||
|
||||
except AnsibleParserError:
|
||||
raise
|
||||
except SopsError as e:
|
||||
raise AnsibleParserError(to_native(e))
|
||||
except Exception as e:
|
||||
raise AnsibleParserError('Unexpected error in the SOPS vars plugin: %s' % to_native(e))
|
||||
|
||||
return data
|
||||
Loading…
Add table
Add a link
Reference in a new issue