b1837b9b2f9604f8351a9151fb4f81ab2c20606c
[apex.git] / apex / common / utils.py
1 ##############################################################################
2 # Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 import json
11 import logging
12 import os
13 import pprint
14 import subprocess
15 import yaml
16
17
18 def str2bool(var):
19     if isinstance(var, bool):
20         return var
21     else:
22         return var.lower() in ("true", "yes")
23
24
25 def parse_yaml(yaml_file):
26     with open(yaml_file) as f:
27         parsed_dict = yaml.safe_load(f)
28         return parsed_dict
29
30
31 def dump_yaml(data, file):
32     """
33     Dumps data to a file as yaml
34     :param data: yaml to be written to file
35     :param file: filename to write to
36     :return:
37     """
38     logging.debug("Writing file {} with "
39                   "yaml data:\n{}".format(file, yaml.safe_dump(data)))
40     with open(file, "w") as fh:
41         yaml.safe_dump(data, fh, default_flow_style=False)
42
43
44 def dict_objects_to_str(dictionary):
45         if isinstance(dictionary, list):
46             tmp_list = []
47             for element in dictionary:
48                 if isinstance(element, dict):
49                     tmp_list.append(dict_objects_to_str(element))
50                 else:
51                     tmp_list.append(str(element))
52             return tmp_list
53         elif not isinstance(dictionary, dict):
54             if not isinstance(dictionary, bool):
55                 return str(dictionary)
56             else:
57                 return dictionary
58         return dict((k, dict_objects_to_str(v)) for
59                     k, v in dictionary.items())
60
61
62 def run_ansible(ansible_vars, playbook, host='localhost', user='root',
63                 tmp_dir=None, dry_run=False):
64     """
65     Executes ansible playbook and checks for errors
66     :param ansible_vars: dictionary of variables to inject into ansible run
67     :param playbook: playbook to execute
68     :param tmp_dir: temp directory to store ansible command
69     :param dry_run: Do not actually apply changes
70     :return: None
71     """
72     logging.info("Executing ansible playbook: {}".format(playbook))
73     inv_host = "{},".format(host)
74     if host == 'localhost':
75         conn_type = 'local'
76     else:
77         conn_type = 'smart'
78     ansible_command = ['ansible-playbook', '--become', '-i', inv_host,
79                        '-u', user, '-c', conn_type, playbook, '-vv']
80     if dry_run:
81         ansible_command.append('--check')
82
83     if isinstance(ansible_vars, dict) and ansible_vars:
84         logging.debug("Ansible variables to be set:\n{}".format(
85             pprint.pformat(ansible_vars)))
86         ansible_command.append('--extra-vars')
87         ansible_command.append(json.dumps(ansible_vars))
88         if tmp_dir:
89             ansible_tmp = os.path.join(tmp_dir,
90                                        os.path.basename(playbook) + '.rerun')
91             # FIXME(trozet): extra vars are printed without single quotes
92             # so a dev has to add them manually to the command to rerun
93             # the playbook.  Need to test if we can just add the single quotes
94             # to the json dumps to the ansible command and see if that works
95             with open(ansible_tmp, 'w') as fh:
96                 fh.write("ANSIBLE_HOST_KEY_CHECKING=FALSE {}".format(
97                     ' '.join(ansible_command)))
98
99     my_env = os.environ.copy()
100     my_env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
101     logging.info("Executing playbook...this may take some time")
102     p = subprocess.Popen(ansible_command,
103                          stdin=subprocess.PIPE,
104                          stdout=subprocess.PIPE,
105                          bufsize=1,
106                          env=my_env,
107                          universal_newlines=True)
108     # read first line
109     x = p.stdout.readline()
110     # initialize task
111     task = ''
112     while x:
113         # append lines to task
114         task += x
115         # log the line and read another
116         x = p.stdout.readline()
117         # deliver the task to info when we get a blank line
118         if not x.strip():
119             task += x
120             logging.info(task.replace('\\n', '\n'))
121             task = ''
122             x = p.stdout.readline()
123     # clean up and get return code
124     p.stdout.close()
125     rc = p.wait()
126     if rc:
127         # raise errors
128         e = "Ansible playbook failed. See Ansible logs for details."
129         logging.error(e)
130         raise Exception(e)