Increase ssh timeout and add retry for Ansible
[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, '-T', '30',
80                        playbook, '-vv']
81     if dry_run:
82         ansible_command.append('--check')
83
84     if isinstance(ansible_vars, dict) and ansible_vars:
85         logging.debug("Ansible variables to be set:\n{}".format(
86             pprint.pformat(ansible_vars)))
87         ansible_command.append('--extra-vars')
88         ansible_command.append(json.dumps(ansible_vars))
89         if tmp_dir:
90             ansible_tmp = os.path.join(tmp_dir,
91                                        os.path.basename(playbook) + '.rerun')
92             # FIXME(trozet): extra vars are printed without single quotes
93             # so a dev has to add them manually to the command to rerun
94             # the playbook.  Need to test if we can just add the single quotes
95             # to the json dumps to the ansible command and see if that works
96             with open(ansible_tmp, 'w') as fh:
97                 fh.write("ANSIBLE_HOST_KEY_CHECKING=FALSE {}".format(
98                     ' '.join(ansible_command)))
99
100     my_env = os.environ.copy()
101     my_env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
102     logging.info("Executing playbook...this may take some time")
103     p = subprocess.Popen(ansible_command,
104                          stdin=subprocess.PIPE,
105                          stdout=subprocess.PIPE,
106                          bufsize=1,
107                          env=my_env,
108                          universal_newlines=True)
109     # read first line
110     x = p.stdout.readline()
111     # initialize task
112     task = ''
113     while x:
114         # append lines to task
115         task += x
116         # log the line and read another
117         x = p.stdout.readline()
118         # deliver the task to info when we get a blank line
119         if not x.strip():
120             task += x
121             logging.info(task.replace('\\n', '\n'))
122             task = ''
123             x = p.stdout.readline()
124     # clean up and get return code
125     p.stdout.close()
126     rc = p.wait()
127     if rc:
128         # raise errors
129         e = "Ansible playbook failed. See Ansible logs for details."
130         logging.error(e)
131         raise Exception(e)