96 lines
3.1 KiB
Python

""" Custom Ansible Lookup Plugin for reading dotenv files.
Ansible officially supports Python 2.7 and 3.5+, but this requires Python 3.8+.
"""
from __future__ import annotations
from typing import Sequence
from ansible.errors import AnsibleFileNotFound, AnsibleLookupError
from ansible.plugins.lookup import LookupBase
from re import compile
__all__ = "DOCUMENTATION", "EXAMPLES", "RETURN", "LookupModule"
# Options not documented will be ignored during processing.
DOCUMENTATION = """
name: dotenv
author:
- Michael Klatt <mdklatt(at)alumni.ou.edu>
short_description: Retrieve values from dotenv files
requirements:
- Anisble must be running under Python 3.8+.
description:
- Retrieve values from dotenv values.
seealso:
- name: RFC 2 - .env file
description: Smartmob RFC for a .env file standard
link: https://smartmob-rfc.readthedocs.io/en/latest/2-dotenv.html
notes:
- The RFC 2 continuation line syntax is not yet supported.
options:
_terms:
description: The key(s) to look up.
required: True
file:
description: Path to the dotenv file.
default: '.env'
""" # YAML
RETURN = """
_raw:
description:
- value(s) of the search term(s) in the dotenv file
type: list
elements: str
""" # YAML
EXAMPLES = """
- name: Retrieve value from the .env file in the current working directory.
debug:
msg: "{{ lookup('dotenv', 'VAR') }}"
- name: Use a non-default dotenv file.
debug:
msg: "{{ lookup('dotenv', 'VAR', file='path/to/.env') }}"
""" # YAML
class LookupModule(LookupBase): # class name is not arbitrary, DO NOT CHANGE
""" Look up values from a dotenv file.
"""
def run(self, terms: Sequence[str], variables=None, **options) -> list:
""" Execute the lookup.
:param terms: search terms
:param variables: mapping of defined Ansible variables
:param options: options passed directly as keyword arguments
:return: list of found values
"""
# Adapted from 'ansible.builtin.ini' lookup.
# <https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/lookup/ini.py>
# <https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#developing-lookup-plugins>
self.set_options(var_options=variables, direct=options)
params = self.get_options()
path = self.find_file_in_search_path(variables, "files", params["file"])
if not path:
raise AnsibleFileNotFound(f"Could not find file {params['file']}")
var_pattern = compile(r"^\s*([a-zA-Z_]+[a-zA-Z0-9_]*)\s*=\s*(\S*)")
variables = {}
for line in open(path, "rt").readlines():
# Parse the dotenv file permissively, accepting valid NAME=VALUE
# lines while ignoring everything else.
# TODO: Support RFC 2 line continuation syntax.
if match := var_pattern.match(line):
variables[match.group(1)] = match.group(2)
try:
return [variables[key] for key in terms]
except KeyError as err:
key = err.args[0]
raise AnsibleLookupError(f"No value for '{key}' in {path}")