ansible: emulate /etc/environment reloading behaviour of vanilla.

This change is relatively incomplete -- ideally we could snapshot
os.environ and /etc/environment at startup and respect key deletions
too, but that's a lot more work. Wait for a bug report instead.

Closes #338.
pull/350/head
David Wilson 6 years ago
parent 81c8156965
commit 1473f49505

@ -44,6 +44,7 @@ import imp
import json
import logging
import os
import shlex
import sys
import tempfile
import types
@ -78,6 +79,22 @@ for symbol in 'res_init', '__res_init':
except AttributeError:
pass
# For tasks running on Linux machines, with vanilla Ansible, edits to
# /etc/environment and ~/.pam_environment are reflected if become:true, due to
# sudo reinvoking pam_env. If multiplexing is disabled, then edits are also
# reflected with become:false. Rather than emulate existing semantics, simply
# always ensure edits are reflects for the next task.
try:
etc_env_st = os.stat('/etc/environment')
except OSError:
etc_env_st = None
try:
pam_env_st = os.stat(os.path.expanduser('~/.pam_environment'))
except OSError:
pam_env_st = None
iteritems = getattr(dict, 'iteritems', dict.items)
LOG = logging.getLogger(__name__)
@ -104,6 +121,54 @@ def reopen_readonly(fp):
os.close(fd)
def parse_env(fp):
"""
Parse /etc/environ using roughly the same syntax as pam_env.
"""
# https://github.com/linux-pam/linux-pam/blob/v1.3.1/modules/pam_env/pam_env.c#L207
for line in fp:
# ' #export foo=some var ' -> ['#export', 'foo=some var ']
bits = shlex.split(line, comments=True)
if not bits:
continue
if bits[0] == 'export':
bits.pop(0)
key, sep, value = (' '.join(bits)).partition('=')
if sep:
os.environ[key] = value
def reload_env(old_st, path):
"""
Compare the :func:`os.stat` for the pam_env style environmnt file `path`
with the previous result `old_st`, which may be :data:`None` if the
previous stat attempt failed. Reload its contents if the file has changed
or appeared since last attempt.
:returns:
New :func:`os.stat` result. The new call to :func:`reload_env` should
pass it as the value of `old_st`.
"""
try:
path = os.path.expanduser(path)
st = os.stat(path)
except OSError:
return None
if old_st == st:
return old_st
if st is None:
LOG.debug('reload_env(%r): file has disappeared', path)
return st
LOG.debug('reload_env(%r): file has changed or appeared, reloading', path)
with open(path) as fp:
parse_env(fp)
return st
class Runner(object):
"""
Ansible module runner. After instantiation (with kwargs supplied by the
@ -163,8 +228,22 @@ class Runner(object):
env = dict(self.extra_env or {})
if self.env:
env.update(self.env)
self._setup_environ()
self._env = TemporaryEnvironment(env)
def _setup_environ(self):
"""
Ensure /etc/environment and ~/.pam_environment are reloaded if their
content appears to differ since execution of the previous task. This
must happen before TemporaryEnvironment is installed, to ensure changes
persist across tasks.
"""
global etc_env_st
etc_env_st = reload_env(etc_env_st, '/etc/environment')
global pam_env_st
pam_env_st = reload_env(pam_env_st, '~/.pam_environment')
def revert(self):
"""
Revert any changes made to the process after running a module. The base

@ -1,7 +1,7 @@
- import_playbook: builtin_command_module.yml
- import_playbook: custom_bash_hashbang_argument.yml
- import_playbook: custom_bash_old_style_module.yml
- import_playbook: custom_bash_want_json_module.yml
- import_playbook: custom_bash_hashbang_argument.yml
- import_playbook: custom_binary_producing_json.yml
- import_playbook: custom_binary_producing_junk.yml
- import_playbook: custom_binary_single_null.yml
@ -13,4 +13,5 @@
- import_playbook: custom_python_want_json_module.yml
- import_playbook: custom_script_interpreter.yml
- import_playbook: environment_isolation.yml
- import_playbook: etc_environment.yml
- import_playbook: forking_behaviour.yml

@ -0,0 +1,66 @@
# issue #338: ensure /etc/environment is reloaded if it changes.
# Actually this test uses ~/.pam_environment, which is using the same logic,
# but less likely to brick a development workstation
- name: integration/runner/etc_environment.yml
hosts: test-targets
any_errors_fatal: true
gather_facts: true
tasks:
- meta: end_play
when: ansible_virtualization_type != "docker"
# ~/.pam_environment
- file:
path: ~/.pam_environment
state: absent
- shell: echo $MAGIC_NEW_ENV
register: echo
- assert:
that: echo.stdout == ""
- copy:
dest: ~/.pam_environment
content: |
MAGIC_NEW_ENV=321
- shell: echo $MAGIC_NEW_ENV
register: echo
- assert:
that: echo.stdout == "321"
- file:
path: ~/.pam_environment
state: absent
# /etc/environment
- file:
path: /etc/environment
state: absent
- shell: echo $MAGIC_ETC_ENV
register: echo
- assert:
that: echo.stdout == ""
- copy:
dest: /etc/environment
content: |
MAGIC_ETC_ENV=555
- shell: echo $MAGIC_ENV_ENV
register: echo
- assert:
that: echo.stdout == "555"
- file:
path: /etc/environment
state: absent
Loading…
Cancel
Save