10 from typing import List
11 from pathlib import Path
12 from urllib.parse import urlparse
13 from urllib.request import Request, urlopen
15 from bs4 import BeautifulSoup # type: ignore
17 JENKINS_IUKS_BASE_URL = "https://nightly.tails.boum.org/build_IUKs"
18 RSYNC_SERVER_HOSTNAME = "rsync.lizard"
19 LOG_FORMAT = "%(asctime)-15s %(levelname)s %(message)s"
20 log = logging.getLogger()
24 parser = argparse.ArgumentParser(
25 description="Copy IUKs from Jenkins to our rsync server \
26 and verify that they match those built locally")
27 parser.add_argument("--hashes-file",
31 parser.add_argument("--jenkins-build-id",
35 parser.add_argument("--work-dir", type=str, action="store", default=".")
36 parser.add_argument("-q",
40 parser.add_argument("--debug", action="store_true", help="debug output")
41 parser.add_argument("--skip-sending-hashes-file",
43 help="Assume the hashes file was uploaded already")
44 parser.add_argument("--skip-downloading-iuks",
46 help="Assume the IUKs were already downloaded")
47 args = parser.parse_args()
50 logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
52 logging.basicConfig(level=logging.WARN, format=LOG_FORMAT)
54 logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
56 if not Path(args.hashes_file).exists():
57 log.error("%s does not exist", args.hashes_file)
60 if not args.skip_sending_hashes_file:
62 hashes_file=args.hashes_file,
63 desthost=RSYNC_SERVER_HOSTNAME,
64 destdir=args.work_dir,
67 if not args.skip_downloading_iuks:
68 download_iuks_from_jenkins(
69 hashes_file=args.hashes_file,
70 desthost=RSYNC_SERVER_HOSTNAME,
71 destdir=args.work_dir,
72 jenkins_iuks_base_url=JENKINS_IUKS_BASE_URL,
73 jenkins_build_id=args.jenkins_build_id,
77 desthost=RSYNC_SERVER_HOSTNAME,
78 iuks_dir=args.work_dir,
79 hashes_file=Path(args.work_dir, args.hashes_file).name,
83 def send_hashes_file(hashes_file: str, desthost: str, destdir: str) -> None:
84 log.info("Sending %(f)s to %(d)s on %(h)s…" % {
90 ["scp", hashes_file, "%s:%s" % (desthost, destdir)], check=True)
93 def iuks_listed_in(hashes_file: str) -> List[str]:
94 with Path(hashes_file).open() as f:
96 return [line.split(' ')[-1].rstrip() for line in lines]
99 def get_jenkins_iuks_urls(jenkins_iuks_base_url: str,
100 jenkins_build_id: int) -> List[str]:
102 source_version_index_url = jenkins_iuks_base_url + \
103 "/configurations/axis-SOURCE_VERSION"
104 for source_version_url in [
105 source_version_index_url + '/' + link.get('href')
106 for link in BeautifulSoup(
107 urlopen(Request(source_version_index_url)),
108 'html.parser').find_all(href=re.compile('^[1-9]'))
110 axis_label_index_url = source_version_url + "axis-label_exp/"
111 log.debug("Looking at %s", axis_label_index_url)
113 axis_label_index_url + link.get('href')
114 for link in BeautifulSoup(urlopen(Request(axis_label_index_url)),
115 'html.parser').find_all(
116 href=re.compile('^[a-z]'))
118 if len(label_urls) == 0:
119 log.debug("Found no label URL in %s, ignoring this source version",
120 axis_label_index_url)
122 if len(label_urls) > 1:
123 log.error("Found too many label URLs in %s: %s",
124 axis_label_index_url, label_urls)
126 label_url = label_urls[0]
128 artifacts_index_url = label_url + '/builds/' + str(
129 jenkins_build_id) + '/archive/'
130 log.debug("Looking at %s", artifacts_index_url)
132 artifacts_index_url + link.get('href') for link in BeautifulSoup(
133 urlopen(Request(artifacts_index_url)), 'html.parser').find_all(
134 href=re.compile('[.]iuk$'))
136 if len(iuk_urls) == 0:
137 log.debug("Found no IUK URL in %s, ignoring this source version",
140 if len(iuk_urls) > 1:
141 log.error("Found too many IUK URLs in %s: %s", artifacts_index_url,
145 iuk_url = iuk_urls[0]
147 log.debug("Found IUK URLs: %s", urls)
151 def download_iuks_from_jenkins(hashes_file: str, desthost: str, destdir: str,
152 jenkins_iuks_base_url: str,
153 jenkins_build_id: int) -> None:
154 log.info("Downloading IUKs from Jenkins to %s…", desthost)
155 expected_iuks = iuks_listed_in(hashes_file)
156 log.debug("IUKS: %s", ', '.join(expected_iuks))
157 jenkins_iuks_urls = get_jenkins_iuks_urls(jenkins_iuks_base_url,
160 os.path.basename(urlparse(url).path) for url in jenkins_iuks_urls
162 if set(expected_iuks) != set(jenkins_iuks):
164 "Jenkins' set of IUKs differs from local one:\n"
167 expected_iuks, jenkins_iuks)
169 for iuk_url in jenkins_iuks_urls:
170 log.debug("Downloading %s to %s", iuk_url, destdir)
172 "ssh", desthost, "wget", "--quiet", "--no-clobber",
173 "--directory-prefix=%s" % destdir, iuk_url
178 def verify_iuks(desthost: str, iuks_dir: str, hashes_file: str) -> None:
179 log.info("Verifying that IUKs built on Jenkins match those you've built…")
183 "cd '%(d)s' && sha256sum --check --strict '%(f)s'" % {
185 "f": Path(hashes_file).name,
189 except subprocess.CalledProcessError:
190 print("\nERROR: IUKs built on Jenkins don't match yours\n",
194 if __name__ == "__main__":