Skip to content

Commit

Permalink
Merge pull request #5798 from grom72/common-restore-ansible-env
Browse files Browse the repository at this point in the history
common: restore the env.py ansible library
  • Loading branch information
janekmi committed Jul 13, 2023
2 parents 16f472e + 91711db commit 5657cc7
Showing 1 changed file with 331 additions and 0 deletions.
331 changes: 331 additions & 0 deletions utils/ansible/library/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
#!/usr/bin/python
#
# SPDX-License-Identifier: BSD-3-Clause
# Copyright 2019-2023, Intel Corporation
#

import os
from ansible.module_utils.basic import AnsibleModule

DOCUMENTATION = '''
---
module: env
short_description: Module for managing environmental variables on remote hosts
description:
- "Module for set and unset environmental variables on remote hosts.
Setting is done by writing export definitions to the file* (this should help with keeping persistence of values
between reboots) and then execute 'source' command (in order to feed current session with exported variables).
- ----
- * file: the file under the care of this module, specified by 'path' parameter. By default it is a file in
/etc/profile.d/"
options:
name:
description:
- This is the name of the variable to manage
required: true
value:
description:
- This is the value of the variable to manage. If 'state'='present', this option is required.
required: false
state:
description:
- If 'present', ensure that given variable is set to given value. This is the default.
required: false
default: present
choices: [ present ]
path:
description:
- This is the path of a file where export definitions will be written. Usually you don't want to change it.
required: false
default: /etc/profile.d/ansible_env_module_definitions.sh
author:
- dpysx
'''

EXAMPLES = '''
# Set http_proxy:
- name: set http proxy
env:
name: http_proxy
value: http://example.com:80
'''

RETURN = '''
old_value:
description: Value of variable, before the module was called.
type: str
'''


# #######################################################################################
# ########################## General helpers ##########################################
# #######################################################################################


def check_prerequisites(module, result):
"""Check if passed module and result are valid types from AnsibleModule."""
if not isinstance(module, AnsibleModule):
raise TypeError("Given module is not valid!")

if not isinstance(result, dict):
raise TypeError("Given result is not valid!")


# #######################################################################################
# ################### Helpers for dealing with system commands ########################
# #######################################################################################


def escape_quotes(string):
"""Escape every double quote char with a backslash."""
return string.replace('"', '\\"')


def run_command(module, result, command):
"""Execute command and return its stdout; when status not zero, fail module execution."""
check_prerequisites(module, result)

command = 'bash -lc "' + escape_quotes(command) + '"'

rc, stdout, stderr = module.run_command(command)

if rc != 0:
fail_message = "Command '" + command + "' failed with status code " + str(
rc) + ", stdout: '" + stdout + "', stderr: '" + stderr + "'."
module.fail_json(msg=fail_message, **result)

return stdout


# #######################################################################################
# ###################### Helpers for handling checking env vars #######################
# #######################################################################################


def get_env_value_or_empty(module, result, name):
"""Obtain value of variable and return it; if not present, return empty string."""
check_prerequisites(module, result)

print_variable = 'echo "$' + name + '"'
variable_value = run_command(module, result, print_variable)

return variable_value.replace('\n', '')


def is_env_present(module, result, name):
"""Check if variable with given name is present in system."""
check_prerequisites(module, result)

env_value = get_env_value_or_empty(module, result, name)
if env_value == "":
return False
else:
return True


# #######################################################################################
# ########################## Helpers for file handling ################################
# #######################################################################################


valid_shebang = "#!/usr/bin/env bash\n"


def read_file_lines(module, result, filename):
"""Read file and return its lines in a form of list."""
content = []
try:
file = open(filename, "r")
content = file.readlines()
file.close()
except IOError:
fail_message = "Cannot open file '" + filename + "' for reading."
module.fail_json(msg=fail_message, **result)

return content


def is_valid_shebang(module, result, filename):
"""Check if file has valid, bash shebang line."""
check_prerequisites(module, result)

file_lines = read_file_lines(module, result, filename)

global valid_shebang
if len(file_lines) == 0 or file_lines[0] != valid_shebang:
return False
else:
return True


def write_file_lines(module, result, filename, lines):
"""Write all lines to the file."""
check_prerequisites(module, result)

changed = False
if not os.access(filename, os.F_OK):
changed = True

try:
file = open(filename, 'w')
except IOError:
fail_message = "Cannot open file '" + filename + "' for write."
module.fail_json(msg=fail_message, **result)

try:
content = []
for line in lines:
if not line.endswith('\n'):
s = line + "\n"
else:
s = line
content.append(s)

file.writelines(content)
changed = True
except:
fail_message = "Cannot write to file '" + filename + "'."
module.fail_json(msg=fail_message, **result)
finally:
file.close()

result['changed'] = changed


def write_variable_to_file(module, result, name, value, filename):
"""Write export definition of the variable to the file. Removes possible duplicates."""
check_prerequisites(module, result)
global valid_shebang

content = []
env_entry_start = "export " + name + "="
env_entry = env_entry_start + '"' + escape_quotes(value) + '"\n'

if os.access(filename, os.F_OK):
content = read_file_lines(module, result, filename)

if not is_valid_shebang(module, result, filename):
content.insert(0, valid_shebang)

# remove all entries for current variable:
filtered_content = []
for line in content:
if not line.startswith(env_entry_start):
filtered_content.append(line)

content = filtered_content
content.append(env_entry)

write_file_lines(module, result, filename, content)
result['changed'] = True

else:
content.append(valid_shebang)
content.append(env_entry)
write_file_lines(module, result, filename, content)
try:
os.chmod(filename, 0o755)
result['changed'] = True
except:
fail_message = "Cannot change file '" + filename + "' permissions to 755."
module.fail_json(msg=fail_message, **result)


# #######################################################################################
# ############### Helpers for exporting / unset vars to current session ###############
# #######################################################################################


def import_variables_from_file(module, result, filename):
"""Execute 'source' command with file parameter in order to import definitions to the current session."""
check_prerequisites(module, result)

result['changed'] = True

load_variables_from_file = 'source "' + filename + '"'
run_command(module, result, load_variables_from_file)


def export_variable(module, result, name, value):
"""Execute 'export' command in order to add exported definition."""
check_prerequisites(module, result)

result['changed'] = True
export_var = "export " + name + '="' + escape_quotes(value) + '"'
run_command(module, result, export_var)


# #######################################################################################
# ########################## Main module functions ####################################
# #######################################################################################


def present_flow(module, result, name, value, filename):
"""Execute module flow for 'present' module mode."""
check_prerequisites(module, result)

current_value = get_env_value_or_empty(module, result, name)
result['old_value'] = current_value

if value is not None:

if is_env_present(module, result, name) and current_value == value:
module.exit_json(**result)

result['changed'] = True

if not module.check_mode:
write_variable_to_file(module, result, name, value, filename)
import_variables_from_file(module, result, filename)
export_variable(module, result, name, value)

module.exit_json(**result)

else:
if is_env_present(module, result, name):
module.exit_json(**result)
else:
module.fail_json("variable is not set!", **result)


def run_module():
"""Prepare parameter and return value object, select and execute proper module flow."""

module_args = dict(
state=dict(type='str', default='present', choices=['present']),
path=dict(type='str', default='/etc/profile.d/ansible_env_module_definitions.sh'),
name=dict(type='str', required=True),
value=dict(type='str'),
)

result = dict(
changed=False,
old_value=''
)

module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)

# state = module.params['state']
name = module.params['name']
value = module.params['value']
path = module.params['path']

# arguments' reasonableness check
if value is None:
module.fail_json(msg="There should be 'value' set as well!", **result)

present_flow(module, result, name, value, path)

# flow functions should end module's execution; when otherwise, there is some ancient evil in here:
module.fail_json(msg="Unexpected execution path, this is a bug in this module!", **result)


def main():
run_module()


if __name__ == '__main__':
main()

0 comments on commit 5657cc7

Please sign in to comment.