"""Ansible variables file reader with vault supportWhy not use the Ansible Python API? We don't have a lot to do here, and the CLIare less likely to be subject to changes."""importcli2importfunctoolsimportsubprocessimportyamlfrompathlibimportPath
[docs]@classmethoddeffrom_yaml(cls,loader,node):""" Convert a representation node to a Python object. """returnsubprocess.check_output(f'echo \'{node.value}\''f' | {cls.ansible_vault}'f' decrypt --vault-password-file {cls.pass_path}',shell=True,).decode().strip()
[docs]classVariables(dict):""" Ansible variables reader. In general, it should be instanciated with :py:attr:`root_path` and :py:attr:`pass_path` to fully function correctly. Example: .. code-block:: python import cansible variables = cansible.Variables( root_path=Path(__file__).parent, pass_path='~/.vault_password', ) print(variables['playbooks/vars/example.yml']) Every file read is cached in the variables object. .. py:attribute:: root_path Unless you feed this with only absolute path, you'll need a root_path so that relative paths can be resolved. This should be your collection root. .. py:attribute:: pass_path Unless you don't use ansible-vault, you'll need to give the pass to the vault password here. """def__init__(self,root_path=None,pass_path=None):self.root_path=Path(root_path)ifroot_pathelseNoneself.pass_path=Path(pass_path)ifpass_pathelseNonedef__getitem__(self,key):ifkeynotinself:self.read(key)returnsuper().__getitem__(key)@functools.cached_propertydefansible_vault(self):returncli2.which('ansible-vault')
[docs]defread(self,path):""" Read an ansible YAML variable file. :param path: Absolute path or path relative to :py:attr:`root_path` """key=pathpath=Path(path)ifpath.is_absolute():path=pathelifself.root_path:path=self.root_path/pathelse:raiseUnresolvablePathError(f'{path} must be absolute if root_path not set')ifnotpath.exists():raisePathNotFoundError(f'{path} does not exist')withpath.open('r')asf:content=f.read()ifcontent.strip().startswith('$ANSIBLE_VAULT'):ifnotself.pass_path:raiseVaultPasswordFileRequiredError('Vault password required in pass_path')ifnotself.pass_path.exists():raiseVaultPasswordFileNotFoundError(f'{self.pass_path} does not exist')args=[self.ansible_vault,'view','--vault-password-file',str(self.pass_path),str(path),]content=subprocess.check_output(args)# todo: find a thread safe way to use our YAMLObjectVault.ansible_vault=self.ansible_vaultVault.pass_path=self.pass_pathself[key]=yaml.load(content,Loader=yaml.FullLoader)returnself[key]