1 ##############################################################################
2 # Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others.
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 ##############################################################################
26 if isinstance(var, bool):
29 return var.lower() in ("true", "yes")
32 def parse_yaml(yaml_file):
33 with open(yaml_file) as f:
34 parsed_dict = yaml.safe_load(f)
38 def dump_yaml(data, file):
40 Dumps data to a file as yaml
41 :param data: yaml to be written to file
42 :param file: filename to write to
45 logging.debug("Writing file {} with "
46 "yaml data:\n{}".format(file, yaml.safe_dump(data)))
47 with open(file, "w") as fh:
48 yaml.safe_dump(data, fh, default_flow_style=False)
51 def dict_objects_to_str(dictionary):
52 if isinstance(dictionary, list):
54 for element in dictionary:
55 if isinstance(element, dict):
56 tmp_list.append(dict_objects_to_str(element))
58 tmp_list.append(str(element))
60 elif not isinstance(dictionary, dict):
61 if not isinstance(dictionary, bool):
62 return str(dictionary)
65 return dict((k, dict_objects_to_str(v)) for
66 k, v in dictionary.items())
69 def run_ansible(ansible_vars, playbook, host='localhost', user='root',
70 tmp_dir=None, dry_run=False):
72 Executes ansible playbook and checks for errors
73 :param ansible_vars: dictionary of variables to inject into ansible run
74 :param playbook: playbook to execute
75 :param tmp_dir: temp directory to store ansible command
76 :param dry_run: Do not actually apply changes
79 logging.info("Executing ansible playbook: {}".format(playbook))
80 inv_host = "{},".format(host)
81 if host == 'localhost':
85 ansible_command = ['ansible-playbook', '--become', '-i', inv_host,
86 '-u', user, '-c', conn_type, '-T', '30',
89 ansible_command.append('--check')
91 if isinstance(ansible_vars, dict) and ansible_vars:
92 logging.debug("Ansible variables to be set:\n{}".format(
93 pprint.pformat(ansible_vars)))
94 ansible_command.append('--extra-vars')
95 ansible_command.append(json.dumps(ansible_vars))
97 ansible_tmp = os.path.join(tmp_dir,
98 os.path.basename(playbook) + '.rerun')
99 # FIXME(trozet): extra vars are printed without single quotes
100 # so a dev has to add them manually to the command to rerun
101 # the playbook. Need to test if we can just add the single quotes
102 # to the json dumps to the ansible command and see if that works
103 with open(ansible_tmp, 'w') as fh:
104 fh.write("ANSIBLE_HOST_KEY_CHECKING=FALSE {}".format(
105 ' '.join(ansible_command)))
107 my_env = os.environ.copy()
108 my_env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
109 logging.info("Executing playbook...this may take some time")
110 p = subprocess.Popen(ansible_command,
111 stdin=subprocess.PIPE,
112 stdout=subprocess.PIPE,
115 universal_newlines=True)
117 x = p.stdout.readline()
121 # append lines to task
123 # log the line and read another
124 x = p.stdout.readline()
125 # deliver the task to info when we get a blank line
128 logging.info(task.replace('\\n', '\n'))
130 x = p.stdout.readline()
131 # clean up and get return code
136 e = "Ansible playbook failed. See Ansible logs for details."
141 def fetch_upstream_and_unpack(dest, url, targets):
143 Fetches targets from a url destination and downloads them if they are
144 newer. Also unpacks tar files in dest dir.
145 :param dest: Directory to download and unpack files to
146 :param url: URL where target files are located
147 :param targets: List of target files to download
150 os.makedirs(dest, exist_ok=True)
151 assert isinstance(targets, list)
152 for target in targets:
153 download_target = True
154 target_url = urllib.parse.urljoin(url, target)
155 target_dest = os.path.join(dest, target)
156 logging.debug("Fetching and comparing upstream target: \n{}".format(
159 u = urllib.request.urlopen(target_url)
160 except urllib.error.URLError as e:
161 logging.error("Failed to fetch target url. Error: {}".format(
164 if os.path.isfile(target_dest):
165 logging.debug("Previous file found: {}".format(target_dest))
167 headers = metadata.items()
168 target_url_date = None
169 for header in headers:
170 if isinstance(header, tuple) and len(header) == 2:
171 if header[0] == 'Last-Modified':
172 target_url_date = header[1]
174 if target_url_date is not None:
175 target_dest_mtime = os.path.getmtime(target_dest)
176 target_url_mtime = time.mktime(
177 datetime.datetime.strptime(target_url_date,
180 if target_url_mtime > target_dest_mtime:
181 logging.debug('URL target is newer than disk...will '
184 logging.info('URL target does not need to be downloaded')
185 download_target = False
187 logging.debug('Unable to find last modified url date')
189 urllib.request.urlretrieve(target_url, filename=target_dest)
190 logging.info("Target downloaded: {}".format(target))
191 if target.endswith('.tar'):
192 logging.info('Unpacking tar file')
193 tar = tarfile.open(target_dest)
194 tar.extractall(path=dest)
198 def install_ansible():
199 # we only install for CentOS/Fedora for now
203 elif 'fedora' in dist:
208 # yum python module only exists for 2.x, so use subprocess
210 subprocess.check_call([pkg_mgr, '-y', 'install', 'ansible'])
211 except subprocess.CalledProcessError:
212 logging.warning('Unable to install Ansible')