Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

common: restore the env.py ansible library #5798

Merged
merged 1 commit into from
Jul 13, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()