diff --git a/.gitignore b/.gitignore index ac6ef4edc..a2bec3ad4 100644 --- a/.gitignore +++ b/.gitignore @@ -99,11 +99,7 @@ depcomp /src/selinux/cockpit.pp /pkg/*/test-*.log /pkg/*/test-*.trs -/bots/images/*.qcow2 -/bots/images/*.partial -/bots/images/*.xz -/bots/images/*.img -/bots/make-checkout-workdir +/bots/ /test/images/* /test/verify/naughty-*/* /test/container-probe* diff --git a/.tasks b/.tasks index 6b2220d3d..28accdde5 100755 --- a/.tasks +++ b/.tasks @@ -23,8 +23,6 @@ fi # File issues for these tasks if [ $chance -gt 9 ]; then bots/po-trigger - bots/image-trigger bots/npm-trigger - bots/naughty-trigger bots/learn-trigger fi diff --git a/bots/.gitignore b/bots/.gitignore deleted file mode 100644 index e12e8338d..000000000 --- a/bots/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -*.pyc -*.qcow2 -*.partial -*.xz -/*.log -/build-results/ diff --git a/bots/HACKING.md b/bots/HACKING.md deleted file mode 100644 index 26fd298b1..000000000 --- a/bots/HACKING.md +++ /dev/null @@ -1,38 +0,0 @@ -# Hacking on the Cockpit Bots - -These are automated bots and testing that works on the Cockpit project. This -includes updating operating system images, bringing in changes from other -projects, releasing Cockpit and more. - -## Environment for the bots - -The bots work in containers that are built in the [cockpituous](https://github.com/cockpit-project/cockpituous) -repository. New dependencies should be added there in the `tests/Dockerfile` -file in that repository. - -## Invoking the bots - - 1. The containers in the `cockpitous` repository invoke the `.tasks` file -at root of this repository. - 1. The ```.tasks``` file prints out a list of possible tasks on standard out. - 1. The printed tasks are sorted in alphabetical reverse order, and one of the -first items in the list is executed. - -## The bots themselves - -Most bots are python scripts. They live in this `bots/` directory. Shared code -is in the `bots/tasks` directory. - -## Bots filing issues - -Many bots file or work with issues in GitHub repository. We can use issues to tell -bots what to do. Often certan bots will just file issues for tasks that are outstanding. -And in many cases other bots will then perform those tasks. - -These bots are listed in the `bots/issue-scan` file. They are written using the -`bots/tasks/__init__.py` code, and you can see `bots/example-task` for an -example of one. - -## Bots printing output - -The bot output is posted using the cockpitous [sink](https://github.com/cockpit-project/cockpituous/tree/master/sink) code. See that link for how it works. diff --git a/bots/README.md b/bots/README.md deleted file mode 100644 index 05e17234b..000000000 --- a/bots/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Cockpit Bots - -These are automated bots and tools that work on Cockpit. This -includes updating operating system images, testing changes, -releasing Cockpit and more. - -## Images - -In order to test Cockpit-related projects, they are staged into an operating -system image. These images are tracked in the ```bots/images``` directory. - -These well known image names are expected to contain no ```.``` -characters and have no file name extension. - -For managing these images: - - * image-download: Download test images - * image-upload: Upload test images - * image-create: Create test machine images - * image-customize: Generic tool to install packages, upload files, or run - commands in a test machine image - * image-prepare: Build and install Cockpit packages into a test machine image - (specific to the cockpit project itself, thus it is in test/, not bots/) - -For debugging the images: - - * bots/vm-run: Run a test machine image - * bots/vm-reset: Remove all overlays from image-customize, image-prepare, etc - from test/images/ - -In case of `qemu-system-x86_64: -netdev bridge,br=cockpit1,id=bridge0: bridge helper failed` -error, please [allow][1] `qemu-bridge-helper` to access the bridge settings. - -To check when images will automatically be refreshed by the bots -use the image-trigger tool: - - $ bots/image-trigger -vd - -## Tests - -The bots automatically run the tests as needed on pull requests -and branches. To check when and where tests will be run, use the -tests-scan tool: - - $ bots/tests-scan -vd - -#### Note on eslintrc interaction - -As eslint looks for additional configurations, eslintrc.(json|yaml) files, in -parent directories, it is recommended to have `"root": true` in the eslint -configuration of any project which is using eslint and is tested through -cockpit-bots. - -## Integration with GitHub - -A number of machines are watching our GitHub repository and are -executing tests for pull requests as well as making new images. - -Most of this happens automatically, but you can influence their -actions with the tests-trigger utility in this directory. - -### Setup - -You need a GitHub token in ~/.config/github-token. You can create one -for your account at - - https://github.com/settings/tokens - -When generating a new personal access token, the scope only needs to -encompass public_repo (or repo if you're accessing a private repo). - -### Retrying a failed test - -If you want to run the "verify/fedora-atomic" testsuite again for pull -request #1234, run tests-trigger like so: - - $ bots/tests-trigger 1234 verify/fedora-atomic - -### Testing a pull request by a non-whitelisted user - -If you want to run all tests on pull request #1234 that has been -opened by someone who is not in our white-list, run tests-trigger -like so: - - $ bots/tests-trigger -f 1234 - -Of course, you should make sure that the pull request is proper and -doesn't execute evil code during tests. - -### Refreshing a test image - -Test images are refreshed automatically once per week, and even if the -last refresh has failed, the machines wait one week before trying again. - -If you want the machines to refresh the fedora-atomic image immediately, -run image-trigger like so: - - $ bots/image-trigger fedora-atomic - -### Creating new images for a pull request - -If as part of some new feature you need to change the content of some -or all images, you can ask the machines to create those images. - -If you want to have a new fedora-atomic image for pull request #1234, add -a bullet point to that pull request's description like so, and add the -"bot" label to the pull request. - - * [ ] image-refresh fedora-atomic - -The machines will post comments to the pull request about their -progress and at the end there will be links to commits with the new -images. You can then include these commits into the pull request in -any way you like. - -If you are certain about the changes to the images, it is probably a -good idea to make a dedicated pull request just for the images. That -pull request can then hopefully be merged to master faster. If -instead the images are created on the main feature pull request and -sit there for a long time, they might cause annoying merge conflicts. - -[1]: https://blog.christophersmart.com/2016/08/31/configuring-qemu-bridge-helper-after-access-denied-by-acl-file-error/ diff --git a/bots/example-task b/bots/example-task deleted file mode 100755 index ec944ed3b..000000000 --- a/bots/example-task +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2016 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# To use this example add a line to an issue with the "bot" label -# -# * [ ] example-bot 20 -# - -import os -import sys -import time - -sys.dont_write_bytecode = True - -import task - -BOTS = os.path.abspath(os.path.dirname(__file__)) -BASE = os.path.normpath(os.path.join(BOTS, "..")) - -def run(argument, verbose=False, **kwargs): - try: - int(argument) - except (TypeError, ValueError): - return "Failed to parse argument" - - sys.stdout.write("Example message to log\n") - - # Attach the package.json script as an example - task.attach("./package.json") - time.sleep(20) - -if __name__ == '__main__': - task.main(function=run, title="Example bot task") diff --git a/bots/flakes-refresh b/bots/flakes-refresh deleted file mode 100755 index baa90e449..000000000 --- a/bots/flakes-refresh +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2018 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import sys -import time -import os -import urllib -import json -import re -import ssl - -sys.dont_write_bytecode = True - -import task - -BOTS = os.path.dirname(os.path.realpath(__file__)) - -NUMBER_OPEN_ISSUES = 7 # How many issues do we want to have open at a given time? - -# How far back does our data go? If a flake gets fixed but is still -# flaky after this many days, the bots open another issue. - -WINDOW_DAYS = 21 - -# This parses the output JSONL format discussed here, where various -# values are grouped: -# -# https://github.com/cockpit-project/cockpituous/blob/master/learn/README.md - -# Here we're looking for a field in a record that only has one value -def value(record, field): - values = record.get(field, []) - if len(values) == 1: - return values[0][0] or "" - return None - -# Here we're looking for the count of a specific field/value in the record -def count(record, field, only): - values = record.get(field, []) - for value, count in values: - if value != only: - continue - return count - return 0 - -# For linking flakes to test logs - -def slurp_one(url, n, logs): - items_url = url + str(n) + "/items.jsonl" - try: - with urllib.request.urlopen(items_url) as f: - for line in f.readlines(): - try: - record = json.loads(line.decode('utf-8')) - logs.setdefault(record["test"], [ ]).append(record["url"]) - except ValueError as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - except urllib.error.URLError as ex: - if ex.code == 404: - return False - raise - return True - -def slurp_failure_logs(url): - logs = { } - n = 0 - while slurp_one(url, n, logs): - n = n + 1 - return logs - -def get_failure_logs(failure_logs, name, context): - match = context.replace("/", "-") - return sorted(filter(lambda url: match in url, failure_logs[name]), reverse=True)[0:10] - -# Main - -def run(context, verbose=False, **kwargs): - api = task.github.GitHub() - - open_issues = api.issues(labels=[ "flake" ]) - create_count = NUMBER_OPEN_ISSUES - len(open_issues) - - if create_count <= 0: - return 0 - - if verbose: - sys.stderr.write("Going to create %s new flake issue(s)\n" % create_count) - - host = os.environ.get("COCKPIT_LEARN_SERVICE_HOST", "learn-cockpit.apps.ci.centos.org") - port = os.environ.get("COCKPIT_LEARN_SERVICE_PORT", "443") - url = "{0}://{1}:{2}/active/".format("https" if port == "443" else "http", host, port) - cafile = os.path.join(BOTS, "images", "files", "ca.pem") - context = ssl.create_default_context(cafile=cafile) - - failure_logs = slurp_failure_logs(url) - - # Retrieve the URL - statistics = [ ] - with urllib.request.urlopen(url + "statistics.jsonl", context=context) as f: - for line in f.readlines(): - try: - record = json.loads(line.decode('utf-8')) - statistics.append(record) - except ValueError as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - - tests = { } - - for record in statistics: - test = value(record, "test") - context = value(record, "context") - status = value(record, "status") - tracker = value(record, "tracker") - - # Flaky tests only score on those that fail and are not tracked - if test is not None and status == "failure" and not tracker: - merged = count(record, "merged", True) - not_merged = count(record, "merged", False) - null_merged = count(record, "merged", None) - total = merged + not_merged + null_merged - - # And the key is that they were merged anyway - if total > 10: - tests.setdefault(test, [ ]).append((merged / total, context, record)) - - scores = [ ] - - for n, t in tests.items(): - scores.append((sum(map(lambda f: f[0], t))/len(t), n, t)) - - closed_issues = api.issues(labels=["flake"], state="closed", since=(time.time() - (WINDOW_DAYS * 86400))) - - def find_in_issues(issues, name): - for issue in issues: - if name in issue["title"]: - return True - return False - - def url_desc(url): - m = re.search("pull-[0-9]+", url) - return m.group(0) if m else url - - def failure_description(name, f, logs): - return ("%s%% on %s\n" % (int(f[0]*100), f[1]) + - "".join(map(lambda url: " - [%s](%s)\n" % (url_desc(url), url), - get_failure_logs(logs, name, f[1])))) - - scores.sort(reverse=True) - for score, name, failures in scores: - if find_in_issues(open_issues, name) or find_in_issues(closed_issues, name): - continue - - if verbose: - sys.stderr.write("Opening issue for %s\n" % name) - source = "
Source material\n\n```json\n%s\n```\n
\n" % "\n".join(map(lambda f: json.dumps(f[2], indent=2), failures)) - data = { - "title": "%s is flaky" % name, - "body": ("\n".join(map(lambda f: failure_description(name, f, failure_logs), failures)) + - "\n\n" + source), - "labels": [ "flake" ] - } - api.post("issues", data) - create_count -= 1 - if create_count == 0: - break - - return 0 - -if __name__ == '__main__': - task.main(function=run, title="Create issues for test flakes") diff --git a/bots/github-info b/bots/github-info deleted file mode 100755 index e1538c93b..000000000 --- a/bots/github-info +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# Shared GitHub code. When run as a script, we print out info about -# our GitHub interacition. - -import argparse -import datetime -import sys - -sys.dont_write_bytecode = True - -from task import github - -def httpdate(dt): - """Return a string representation of a date according to RFC 1123 - (HTTP/1.1). - - The supplied date must be in UTC. - - From: http://stackoverflow.com/a/225106 - - """ - weekday = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dt.weekday()] - month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", - "Oct", "Nov", "Dec"][dt.month - 1] - return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (weekday, dt.day, month, - dt.year, dt.hour, dt.minute, dt.second) - -def main(): - parser = argparse.ArgumentParser(description='Test GitHub rate limits') - parser.parse_args() - - # in order for the limit not to be affected by the call itself, - # use a conditional request with a timestamp in the future - - future_timestamp = datetime.datetime.utcnow() + datetime.timedelta(seconds=3600) - - api = github.GitHub() - headers = { 'If-Modified-Since': httpdate(future_timestamp) } - response = api.request("GET", "git/refs/heads/master", "", headers) - sys.stdout.write("Rate limits:\n") - for entry in ["X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"]: - entries = [t for t in response['headers'].items() if t[0].lower() == entry.lower()] - if entries: - if entry == "X-RateLimit-Reset": - try: - readable = datetime.datetime.utcfromtimestamp(float(entries[0][1])).isoformat() - except: - readable = "parse error" - pass - sys.stdout.write("{0}: {1} ({2})\n".format(entry, entries[0][1], readable)) - else: - sys.stdout.write("{0}: {1}\n".format(entry, entries[0][1])) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/image-create b/bots/image-create deleted file mode 100755 index d25ef587d..000000000 --- a/bots/image-create +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env python3 -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# image-create -- Make a root image suitable for use with vm-run. -# -# Installs the OS indicated by TEST_OS into the image -# for test machine and tweaks it to be useable with -# vm-run and testlib.py. - -import argparse -import os -import shutil -import subprocess -import sys -import time -import tempfile - -BOTS = os.path.abspath(os.path.dirname(__file__)) -BASE = os.path.normpath(os.path.join(BOTS, "..")) - -from machine import testvm - -parser = argparse.ArgumentParser(description='Create a virtual machine image') -parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose progress details') -parser.add_argument('-s', '--sit', action='store_true', help='Sit and wait if setup script fails') -parser.add_argument('-n', '--no-save', action='store_true', help='Don\'t save the new image') -parser.add_argument('-u', '--upload', action='store_true', help='Upload the image after creation') -parser.add_argument('--no-build', action='store_true', dest='no_build', - help='Don''t build packages and create the vm without build capabilities') -parser.add_argument("--store", default=None, help="Where to send images") -parser.add_argument('image', help='The image to create') -args = parser.parse_args() - -# default to --no-build for some images -if args.image in ["candlepin", "continuous-atomic", "fedora-atomic", "ipa", "rhel-atomic", "selenium", "openshift"]: - if not args.no_build: - if args.verbose: - print("Creating machine without build capabilities based on the image type") - args.no_build = True - -class MachineBuilder: - def __init__(self, machine): - tempdir = testvm.get_temp_dir() - self.machine = machine - - os.makedirs(tempdir, 0o750, exist_ok=True) - - # Use a tmp filename - self.target_file = self.machine.image_file - fp, self.machine.image_file = tempfile.mkstemp(dir=tempdir, prefix=self.machine.image, suffix=".qcow2") - os.close(fp) - - def bootstrap_system(self): - assert not self.machine._domain - - os.makedirs(self.machine.run_dir, 0o750, exist_ok=True) - - bootstrap_script = os.path.join(testvm.SCRIPTS_DIR, "%s.bootstrap" % (self.machine.image, )) - - - if os.path.isfile(bootstrap_script): - subprocess.check_call([ bootstrap_script, self.machine.image_file ]) - else: - raise testvm.Failure("Unsupported OS %s: %s not found." % (self.machine.image, bootstrap_script)) - - def run_setup_script(self, script): - """Prepare a test image further by running some commands in it.""" - self.machine.start() - try: - self.machine.wait_boot(timeout_sec=120) - self.machine.upload([ os.path.join(testvm.SCRIPTS_DIR, "lib") ], "/var/lib/testvm") - self.machine.upload([script], "/var/tmp/SETUP") - self.machine.upload([ os.path.join(testvm.SCRIPTS_DIR, "lib", "base") ], - "/var/tmp/cockpit-base") - - if "rhel" in self.machine.image: - self.machine.upload([ os.path.expanduser("~/.rhel") ], "/root/") - - env = { - "TEST_OS": self.machine.image, - "DO_BUILD": "0" if args.no_build else "1", - } - self.machine.message("run setup script on guest") - - try: - self.machine.execute(script="/var/tmp/SETUP " + self.machine.image, - environment=env, quiet=not self.machine.verbose, timeout=7200) - self.machine.execute(command="rm -f /var/tmp/SETUP") - self.machine.execute(command="rm -rf /root/.rhel") - - if self.machine.image == 'openshift': - # update our local openshift kube config file to match the new image - self.machine.download("/root/.kube/config", os.path.join(BOTS, "images/files/openshift.kubeconfig")) - - except subprocess.CalledProcessError as ex: - if args.sit: - sys.stderr.write(self.machine.diagnose()) - input ("Press RET to continue... ") - raise testvm.Failure("setup failed with code {0}\n".format(ex.returncode)) - - finally: - self.machine.stop(timeout_sec=500) - - def boot_system(self): - """Start the system to make sure it can boot, then shutdown cleanly - This also takes care of any selinux relabeling setup triggered - Don't wait for an ip address during start, since the system might reboot""" - self.machine.start() - try: - self.machine.wait_boot(timeout_sec=120) - finally: - self.machine.stop(timeout_sec=120) - - def build(self): - self.bootstrap_system() - - # gather the scripts, separated by reboots - script = os.path.join(testvm.SCRIPTS_DIR, "%s.setup" % (self.machine.image, )) - - if not os.path.exists(script): - return - - self.machine.message("Running setup script %s" % (script)) - self.run_setup_script(script) - - tries_left = 3 - successfully_booted = False - while tries_left > 0: - try: - # make sure we can boot the system - self.boot_system() - successfully_booted = True - break - except: - # we might need to wait for the image to become available again - # accessing it in maintain=True mode successively can trigger qemu errors - time.sleep(3) - tries_left -= 1 - if not successfully_booted: - raise testvm.Failure("Unable to verify that machine boot works.") - - def save(self): - data_dir = testvm.get_images_data_dir() - - os.makedirs(data_dir, 0o750, exist_ok=True) - - if not os.path.exists(self.machine.image_file): - raise testvm.Failure("Nothing to save.") - - partial = os.path.join(data_dir, self.machine.image + ".partial") - - # Copy image via convert, to make it sparse again - subprocess.check_call([ "qemu-img", "convert", "-c", "-O", "qcow2", self.machine.image_file, partial ]) - - # Hash the image here - (sha, x1, x2) = subprocess.check_output([ "sha256sum", partial ], universal_newlines=True).partition(" ") - if not sha: - raise testvm.Failure("sha256sum returned invalid output") - - name = self.machine.image + "-" + sha + ".qcow2" - data_file = os.path.join(data_dir, name) - shutil.move(partial, data_file) - - # Remove temp image file - os.unlink(self.machine.image_file) - - # Update the images symlink - if os.path.islink(self.target_file): - os.unlink(self.target_file) - os.symlink(name, self.target_file) - - # Handle alternate images data directory - image_file = os.path.join(testvm.IMAGES_DIR, name) - if not os.path.exists(image_file): - os.symlink(os.path.abspath(data_file), image_file) - -try: - testvm.VirtMachine.memory_mb = 2048 - machine = testvm.VirtMachine(verbose=args.verbose, image=args.image, maintain=True) - builder = MachineBuilder(machine) - builder.build() - if not args.no_save: - print("Saving...") - builder.save() - if args.upload: - print("Uploading...") - cmd = [ os.path.join(BOTS, "image-upload") ] - if args.store: - cmd += [ "--store", args.store ] - cmd += [ args.image ] - subprocess.check_call(cmd) - -except testvm.Failure as ex: - sys.stderr.write("image-create: %s\n" % ex) - sys.exit(1) diff --git a/bots/image-customize b/bots/image-customize deleted file mode 100755 index a3e559ec3..000000000 --- a/bots/image-customize +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python3 -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import argparse -import os -import re -import sys -import subprocess - -BOTS = os.path.abspath(os.path.dirname(__file__)) -BASE = os.path.normpath(os.path.join(BOTS, "..")) -TEST = os.path.join(BASE, "test") -os.environ["PATH"] = "{0}:{1}".format(os.environ.get("PATH"), BOTS) - -from machine import testvm - -parser = argparse.ArgumentParser( - description='Run command inside or install packages into a Cockpit virtual machine', - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) -parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose progress details') -parser.add_argument('-i', '--install', action='append', dest="packagelist", default=[], help='Install packages') -parser.add_argument('-I', '--install-command', action='store', dest="installcommand", - default="yum --setopt=skip_missing_names_on_install=False -y install", - help="Command used to install packages in machine") -parser.add_argument('-r', '--run-command', action='append', dest="commandlist", - default=[], help='Run command inside virtual machine') -parser.add_argument('-s', '--script', action='append', dest="scriptlist", - default=[], help='Run selected script inside virtual machine') -parser.add_argument('-u', '--upload', action='append', dest="uploadlist", - default=[], help='Upload file/dir to destination file/dir separated by ":" example: -u file.txt:/var/lib') -parser.add_argument('--base-image', help='Base image name, if "image" does not match a standard Cockpit VM image name') -parser.add_argument('--resize', help="Resize the image. Size in bytes with using K, M, or G suffix.") -parser.add_argument('image', help='The image to use (destination name when using --base-image)') -args = parser.parse_args() - -if not args.base_image: - args.base_image = os.path.basename(args.image) - -args.base_image = testvm.get_test_image(args.base_image) - -# Create the necessary layered image for the build/install -def prepare_install_image(base_image, install_image): - if "/" not in base_image: - base_image = os.path.join(testvm.IMAGES_DIR, base_image) - if "/" not in install_image: - install_image = os.path.join(os.path.join(TEST, "images"), os.path.basename(install_image)) - - # In vm-customize we don't force recreate images - if not os.path.exists(install_image): - install_image_dir = os.path.dirname(install_image) - os.makedirs(install_image_dir, exist_ok=True) - base_image = os.path.realpath(base_image) - qcow2_image = "{0}.qcow2".format(install_image) - subprocess.check_call([ "qemu-img", "create", "-q", "-f", "qcow2", - "-o", "backing_file={0},backing_fmt=qcow2".format(base_image), qcow2_image ]) - if os.path.lexists(install_image): - os.unlink(install_image) - os.symlink(os.path.basename(qcow2_image), install_image) - - if args.resize: - subprocess.check_call(["qemu-img", "resize", install_image, args.resize]) - - return install_image - -def run_command(machine_instance, commandlist): - """Run command inside image""" - for foo in commandlist: - try: - machine_instance.execute(foo, timeout=1800) - except subprocess.CalledProcessError as e: - sys.stderr.write("%s\n" % e) - sys.exit(e.returncode) - -def run_script(machine_instance, scriptlist): - """Run script inside image""" - for foo in scriptlist: - if os.path.isfile(foo): - pname = os.path.basename(foo) - uploadpath = "/var/tmp/" + pname - machine_instance.upload([os.path.abspath(foo)], uploadpath) - machine_instance.execute("chmod a+x %s" % uploadpath) - try: - machine_instance.execute(uploadpath, timeout=1800) - except subprocess.CalledProcessError as e: - sys.stderr.write("%s\n" % e) - sys.exit(e.returncode) - else: - sys.stderr.write("Bad path to script: %s\n" % foo) - -def upload_files(machine_instance, uploadfiles): - """Upload files/directories inside image""" - for foo in uploadfiles: - srcfile, dest = foo.split(":") - src_absolute = os.path.join(os.getcwd(), srcfile) - machine_instance.upload([src_absolute], dest) - -def install_packages(machine_instance, packagelist, install_command): - """Install packages into a test image - It could be done via local rpms or normal package installation - """ - allpackages = [] - for foo in packagelist: - if os.path.isfile(foo): - pname = os.path.basename(foo) - machine_instance.upload([foo], "/var/tmp/" + pname) - allpackages.append("/var/tmp/" + pname) - elif not re.search("/", foo): - allpackages.append(foo) - else: - sys.stderr.write("Bad package name or path: %s\n" % foo) - if allpackages: - machine_instance.execute(install_command + " " + ' '.join(allpackages), timeout=1800) - -if args.commandlist or args.packagelist or args.scriptlist or args.uploadlist or args.resize: - if '/' not in args.base_image: - subprocess.check_call(["image-download", args.base_image]) - machine = testvm.VirtMachine(maintain=True, - verbose=args.verbose, image=prepare_install_image(args.base_image, args.image)) - machine.start() - machine.wait_boot() - try: - if args.uploadlist: - upload_files(machine, args.uploadlist) - if args.commandlist: - run_command(machine, args.commandlist) - if args.packagelist: - install_packages(machine, args.packagelist, args.installcommand) - if args.scriptlist: - run_script(machine, args.scriptlist) - finally: - machine.stop() diff --git a/bots/image-download b/bots/image-download deleted file mode 100755 index dcf4a61d1..000000000 --- a/bots/image-download +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# -# Download images or other state -# -# Images usually have a name specific link committed to git. These -# are referred to as 'committed' -# -# Other state is simply referenced by name without a link in git -# This is referred to as 'state' -# -# The stores are places to look for images or other state -# - -import argparse -import email -import io -import os -import shutil -import socket -import stat -import subprocess -import sys -import tempfile -import time -import fcntl -import urllib.parse - -from machine import testvm -from task import REDHAT_STORE - -CONFIG = "~/.config/image-stores" -DEFAULT = [ - "http://cockpit-images.verify.svc.cluster.local", - "https://images-cockpit.apps.ci.centos.org/", - "https://209.132.184.41:8493/", - REDHAT_STORE -] - -BOTS = os.path.dirname(os.path.realpath(__file__)) - -DEVNULL = open("/dev/null", "r+") -EPOCH = "Thu, 1 Jan 1970 00:00:00 GMT" - -def find(name, stores, latest, quiet): - found = [ ] - ca = os.path.join(testvm.IMAGES_DIR, "files", "ca.pem") - - for store in stores: - url = urllib.parse.urlparse(store) - - defport = url.scheme == 'http' and 80 or 443 - - try: - ai = socket.getaddrinfo(url.hostname, url.port or defport, socket.AF_INET, 0, socket.IPPROTO_TCP) - except socket.gaierror: - ai = [ ] - message = store - - for (family, socktype, proto, canonname, sockaddr) in ai: - message = "{scheme}://{0}:{1}{path}".format(*sockaddr, scheme=url.scheme, path=url.path) - - def curl(*args): - try: - cmd = ["curl"] + list(args) + ["--head", "--silent", "--fail", "--cacert", ca, source] - start = time.time() - output = subprocess.check_output(cmd, universal_newlines=True) - found.append((cmd, output, message, time.time() - start)) - if not quiet: - sys.stderr.write(" > {0}\n".format(message)) - return True - except subprocess.CalledProcessError: - return False - - # first try with stores that accept the "cockpit-tests" host name - resolve = "cockpit-tests:{1}:{0}".format(*sockaddr) - source = urllib.parse.urljoin("{0}://cockpit-tests:{1}{2}".format(url.scheme, sockaddr[1], url.path), name) - if curl("--resolve", resolve): - continue - - # fall back for OpenShift proxied stores which send their own SSL cert initially; host name has to match that - source = urllib.parse.urljoin(store, name) - if curl(): - continue - - if not quiet: - sys.stderr.write(" x {0}\n".format(message)) - - # If we couldn't find the file, but it exists, we're good - if not found: - return None, None - - # Find the most recent version of this file - def header_date(args): - cmd, output, message, latency = args - try: - reply_line, headers_alone = output.split('\n', 1) - last_modified = email.message_from_file(io.StringIO(headers_alone)).get("Last-Modified", "") - return time.mktime(time.strptime(last_modified, '%a, %d %b %Y %H:%M:%S %Z')) - except ValueError: - return "" - - if latest: - found.sort(reverse=True, key=header_date) - else: - found.sort(reverse=False, key=lambda x: x[3]) - - # Return the command and message - return found[0][0], found[0][2] - -def download(dest, force, state, quiet, stores): - if not stores: - config = os.path.expanduser(CONFIG) - if os.path.exists(config): - with open(config, 'r') as fp: - stores = fp.read().strip().split("\n") - else: - stores = [] - stores += DEFAULT - - # The time condition for If-Modified-Since - exists = not force and os.path.exists(dest) - if exists: - since = dest - else: - since = EPOCH - - name = os.path.basename(dest) - cmd, message = find(name, stores, latest=state, quiet=quiet) - - # If we couldn't find the file, but it exists, we're good - if not cmd: - if exists: - return - raise RuntimeError("image-download: couldn't find file anywhere: {0}".format(name)) - - # Choose the first found item after sorting by date - if not quiet: - sys.stderr.write(" > {0}\n".format(urllib.parse.urljoin(message, name))) - - temp = dest + ".partial" - - # Adjust the command above that worked to make it visible and download real stuff - cmd.remove("--head") - cmd.append("--show-error") - if not quiet and os.isatty(sys.stdout.fileno()): - cmd.remove("--silent") - cmd.insert(1, "--progress-bar") - cmd.append("--remote-time") - cmd.append("--time-cond") - cmd.append(since) - cmd.append("--output") - cmd.append(temp) - if os.path.exists(temp): - if force: - os.remove(temp) - else: - cmd.append("-C") - cmd.append("-") - - # Always create the destination file (because --state) - else: - open(temp, 'a').close() - - curl = subprocess.Popen(cmd) - ret = curl.wait() - if ret != 0: - raise RuntimeError("curl: unable to download %s (returned: %s)" % (message, ret)) - - os.chmod(temp, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) - - # Due to time-cond the file size may be zero - # A new file downloaded, put it in place - if not exists or os.path.getsize(temp) > 0: - shutil.move(temp, dest) - -# Calculate a place to put images where links are not committed in git -def state_target(path): - data_dir = testvm.get_images_data_dir() - os.makedirs(data_dir, mode=0o775, exist_ok=True) - return os.path.join(data_dir, path) - -# Calculate a place to put images where links are committed in git -def committed_target(image): - link = os.path.join(testvm.IMAGES_DIR, image) - if not os.path.islink(link): - raise RuntimeError("image link does not exist: " + image) - - dest = os.readlink(link) - relative_dir = os.path.dirname(os.path.abspath(link)) - full_dest = os.path.join(relative_dir, dest) - while os.path.islink(full_dest): - link = full_dest - dest = os.readlink(link) - relative_dir = os.path.dirname(os.path.abspath(link)) - full_dest = os.path.join(relative_dir, dest) - - dest = os.path.join(testvm.get_images_data_dir(), dest) - - # We have the file but there is not valid link - if os.path.exists(dest): - try: - os.symlink(dest, os.path.join(testvm.IMAGES_DIR, os.readlink(link))) - except FileExistsError: - pass - - # The image file in the images directory, may be same as dest - image_file = os.path.join(testvm.IMAGES_DIR, os.readlink(link)) - - # Double check that symlink in place but never make a cycle. - if os.path.abspath(dest) != os.path.abspath(image_file): - try: - os.symlink(os.path.abspath(dest), image_file) - except FileExistsError: - pass - - return dest - -def wait_lock(target): - lockfile = os.path.join(tempfile.gettempdir(), ".cockpit-test-resources", os.path.basename(target) + ".lock") - os.makedirs(os.path.dirname(lockfile), exist_ok=True) - - # we need to keep the lock fd open throughout the entire runtime, so remember it in a global-scoped variable - wait_lock.f = open(lockfile, "w") - for retry in range(360): - try: - fcntl.flock(wait_lock.f, fcntl.LOCK_NB | fcntl.LOCK_EX) - return - except BlockingIOError: - if retry == 0: - print("Waiting for concurrent image-download of %s..." % os.path.basename(target)) - time.sleep(10) - else: - raise TimeoutError("timed out waiting for concurrent downloads of %s\n" % target) - -def download_images(image_list, force, quiet, state, store): - data_dir = testvm.get_images_data_dir() - os.makedirs(data_dir, exist_ok=True) - - # A default set of images are all links in git. These links have - # no directory part. Other links might exist, such as the - # auxiliary links created by committed_target above, and we ignore - # them. - if not image_list: - image_list = [] - if not state: - for filename in os.listdir(testvm.IMAGES_DIR): - link = os.path.join(testvm.IMAGES_DIR, filename) - if os.path.islink(link) and os.path.dirname(os.readlink(link)) == "": - image_list.append(filename) - - success = True - - for image in image_list: - image = testvm.get_test_image(image) - try: - if state: - target = state_target(image) - else: - target = committed_target(image) - - # don't download the same thing multiple times in parallel - wait_lock(target) - - if force or state or not os.path.exists(target): - download(target, force, state, quiet, store) - except Exception as ex: - success = False - sys.stderr.write("image-download: {0}\n".format(str(ex))) - - return success - -def main(): - parser = argparse.ArgumentParser(description='Download a bot state or images') - parser.add_argument("--force", action="store_true", help="Force unnecessary downloads") - parser.add_argument("--store", action="append", help="Where to find state or images") - parser.add_argument("--quiet", action="store_true", help="Make downloading quieter") - parser.add_argument("--state", action="store_true", help="Images or state not recorded in git") - parser.add_argument('image', nargs='*') - args = parser.parse_args() - - if not download_images(args.image, args.force, args.quiet, args.state, args.store): - return 1 - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/image-prune b/bots/image-prune deleted file mode 100755 index 95d509646..000000000 --- a/bots/image-prune +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# Days after which images expire if not in use -IMAGE_EXPIRE = 14 - -import argparse -import os -import subprocess -import sys -import time - -from contextlib import contextmanager - -from task import github - -from machine import testvm - -BOTS = os.path.dirname(os.path.realpath(__file__)) - -# threshold in G below which unreferenced qcow2 images will be pruned, even if they aren't old -PRUNE_THRESHOLD_G = float(os.environ.get("PRUNE_THRESHOLD_G", 15)) - -def enough_disk_space(): - """Check if available disk space in our data store is sufficient - """ - st = os.statvfs(testvm.get_images_data_dir()) - free = st.f_bavail * st.f_frsize / (1024*1024*1024) - return free >= PRUNE_THRESHOLD_G; - -def get_refs(open_pull_requests=True, offline=False): - """Return dictionary for available refs of the format {'rhel-7.4': 'ad50328990e44c22501bd5e454746d4b5e561b7c'} - - Expects to be called from the top level of the git checkout - If offline is true, git show-ref is used instead of listing the remote - """ - # get all remote heads and filter empty lines - # output of ls-remote has the format - # - # d864d3792db442e3de3d1811fa4bc371793a8f4f refs/heads/master - # ad50328990e44c22501bd5e454746d4b5e561b7c refs/heads/rhel-7.4 - - refs = { } - - considerable = {} - g = github.GitHub() - if open_pull_requests: - if offline: - raise Exception("Unable to consider open pull requests when in offline mode") - for p in g.pulls(): - files = g.get("pulls/{0}/files".format(p["number"])) - images = [] - for fl in files: - fl_name = fl['filename'] - if fl_name.startswith("bots/images/"): - fl_name_split = fl_name.split("/", 2) - if "/" not in fl_name_split[2]: - images.append(fl_name_split[2]) - if images: - sha = p["head"]["sha"] - considerable[sha] = images - subprocess.call(["git", "fetch", "origin", "pull/{0}/head".format(p["number"])]) - refs["pull request #{} ({})".format(p["number"], p["title"])] = sha - - git_cmd = "show-ref" if offline else "ls-remote" - ref_output = subprocess.check_output(["git", git_cmd], universal_newlines=True).splitlines() - # filter out the "refs/heads/" prefix and generate a dictionary - prefix = "refs/heads" - for ln in ref_output: - [ref, name] = ln.split() - if name.startswith(prefix): - refs[name[len(prefix):]] = ref - - return (refs, considerable) - -def get_image_links(ref, git_path): - """Return all image links for the given git ref - - Expects to be called from the top level of the git checkout - """ - # get all the links we have first - # trailing slash on path is important - if not git_path.endswith("/"): - git_path = "{0}/".format(git_path) - - try: - entries = subprocess.check_output(["git", "ls-tree", "--name-only", ref, git_path], universal_newlines=True).splitlines() - except subprocess.CalledProcessError as e: - if e.returncode == 128: - sys.stderr.write("Skipping {0} due to tree error.\n".format(ref)) - return [] - raise - links = [subprocess.check_output(["git", "show", "{0}:{1}".format(ref, entry)], universal_newlines=True) for entry in entries] - return [link for link in links if link.endswith(".qcow2")] - -@contextmanager -def remember_cwd(): - curdir = os.getcwd() - try: - yield - finally: - os.chdir(curdir) - -def get_image_names(quiet=False, open_pull_requests=True, offline=False): - """Return all image names used by all branches and optionally in open pull requests - """ - images = set() - # iterate over visible refs (mostly branches) - # this hinges on being in the top level directory of the the git checkout - with remember_cwd(): - os.chdir(os.path.join(BOTS, "..")) - (refs, considerable) = get_refs(open_pull_requests, offline) - # list images present in each branch / pull request - for name, ref in refs.items(): - if not quiet: - sys.stderr.write("Considering images from {0} ({1})\n".format(name, ref)) - for link in get_image_links(ref, "bots/images"): - if ref in considerable: - for consider in considerable[ref]: - if link.startswith(consider): - images.add(link) - else: - images.add(link) - - return images - -def prune_images(force, dryrun, quiet=False, open_pull_requests=True, offline=False, checkout_only=False): - """Prune images - """ - now = time.time() - - # everything we want to keep - if checkout_only: - targets = set() - else: - targets = get_image_names(quiet, open_pull_requests, offline) - - # what we have in the current checkout might already have been added by its branch, but check anyway - for filename in os.listdir(testvm.IMAGES_DIR): - path = os.path.join(testvm.IMAGES_DIR, filename) - - # only consider original image entries as trustworthy sources and ignore non-links - if path.endswith(".qcow2") or path.endswith(".partial") or not os.path.islink(path): - continue - - target = os.readlink(path) - targets.add(target) - - expiry_threshold = now - IMAGE_EXPIRE * 86400 - for filename in os.listdir(testvm.get_images_data_dir()): - path = os.path.join(testvm.get_images_data_dir(), filename) - if not force and (enough_disk_space() and os.lstat(path).st_mtime > expiry_threshold): - continue - if os.path.isfile(path) and (path.endswith(".xz") or path.endswith(".qcow2") or path.endswith(".partial")) and filename not in targets: - if not quiet or dryrun: - sys.stderr.write("Pruning {0}\n".format(filename)) - if not dryrun: - os.unlink(path) - - # now prune broken links - for filename in os.listdir(testvm.IMAGES_DIR): - path = os.path.join(testvm.IMAGES_DIR, filename) - - # don't prune original image entries and ignore non-links - if not path.endswith(".qcow2") or not os.path.islink(path): - continue - - # if the link isn't valid, prune - if not os.path.isfile(path): - if not quiet or dryrun: - sys.stderr.write("Pruning link {0}\n".format(path)) - if not dryrun: - os.unlink(path) - -def every_image(): - result = [] - for filename in os.listdir(testvm.IMAGES_DIR): - link = os.path.join(testvm.IMAGES_DIR, filename) - if os.path.islink(link): - result.append(filename) - return result - -def main(): - parser = argparse.ArgumentParser(description='Prune downloaded images') - parser.add_argument("--force", action="store_true", help="Delete images even if they aren't old") - parser.add_argument("--quiet", action="store_true", help="Make downloading quieter") - parser.add_argument("-d", "--dry-run-prune", dest="dryrun", action="store_true", help="Don't actually delete images and links") - parser.add_argument("-b", "--branches-only", dest="branches_only", action="store_true", help="Don't consider pull requests on GitHub, only look at branches") - parser.add_argument("-c", "--checkout-only", dest="checkout_only", action="store_true", help="Consider neither pull requests on GitHub nor branches, only look at the current checkout") - parser.add_argument("-o", "--offline", dest="offline", action="store_true", help="Don't access external sources such as GitHub") - args = parser.parse_args() - - try: - prune_images(args.force, args.dryrun, quiet=args.quiet, open_pull_requests=(not args.branches_only), offline=args.offline, checkout_only=args.checkout_only) - except RuntimeError as ex: - sys.stderr.write("image-prune: {0}\n".format(str(ex))) - return 1 - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/image-refresh b/bots/image-refresh deleted file mode 100755 index 49fdbf091..000000000 --- a/bots/image-refresh +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2016 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import os -import subprocess -import sys - -import task -from task import github, REDHAT_STORE - -TRIGGERS = { - "centos-7": [ - "centos-7@cockpit-project/starter-kit", - ], - "continuous-atomic": [ - "continuous-atomic@cockpit-project/cockpit-ostree", - "continuous-atomic@cockpit-project/cockpit/rhel-7.7", - ], - "debian-testing": [ - "debian-testing" - ], - "debian-stable": [ - "debian-stable" - ], - "fedora-29": [ - "fedora-atomic", - "fedora-29@cockpit-project/cockpit-podman", - ], - "fedora-30": [ - "fedora-30", - "fedora-30/selenium-chrome", - "fedora-30/selenium-firefox", - "fedora-30/selenium-edge", - "fedora-30/container-bastion", - "fedora-30@cockpit-project/starter-kit", - "fedora-30@cockpit-project/cockpit-podman", - "fedora-30@weldr/lorax", - "fedora-30/live-iso@weldr/lorax", - "fedora-30/qcow2@weldr/lorax", - "fedora-30/chrome@weldr/cockpit-composer", - "fedora-30/firefox@weldr/cockpit-composer", - "fedora-30/edge@weldr/cockpit-composer", - ], - "fedora-31": [ - "fedora-31", - "fedora-31@cockpit-project/cockpit-podman", - ], - "fedora-atomic": [ - "fedora-atomic", - "fedora-atomic@cockpit-project/cockpit-ostree", - ], - "fedora-testing": [ - "fedora-testing" - ], - "fedora-i386": [ - "fedora-i386" - ], - "ubuntu-1804": [ - "ubuntu-1804" - ], - "ubuntu-stable": [ - "ubuntu-stable" - ], - "openshift": [ - "rhel-7-7@cockpit-project/cockpit/rhel-7.7", - "rhel-7-8@cockpit-project/cockpit/rhel-7.8", - ], - "ipa": [ - "fedora-30", - "ubuntu-1804", - "debian-stable" - ], - "selenium": [ - "fedora-30/selenium-chrome", - "fedora-30/selenium-firefox", - "fedora-30/chrome@weldr/cockpit-composer", - "fedora-30/firefox@weldr/cockpit-composer", - "rhel-7-7/firefox@weldr/cockpit-composer", - "rhel-8-1/chrome@weldr/cockpit-composer", - ], - "rhel-7-7": [ - "rhel-7-7/firefox@weldr/cockpit-composer", - "rhel-7-7@cockpit-project/cockpit/rhel-7.7", - "rhel-atomic@cockpit-project/cockpit/rhel-7.7", - "continuous-atomic@cockpit-project/cockpit/rhel-7.7", - ], - "rhel-7-8": [ - "rhel-7-8@cockpit-project/cockpit/rhel-7.8", - ], - "rhel-8-0": [ - "rhel-8-0@cockpit-project/cockpit/rhel-8.0", - ], - "rhel-8-1": [ - "rhel-8-1", - "rhel-8-1-distropkg", - "rhel-8-1@cockpit-project/cockpit/rhel-8.1", - "rhel-8-1@cockpit-project/cockpit/rhel-8-appstream", - "rhel-8-1/chrome@weldr/cockpit-composer", - "rhel-8-1@cockpit-project/cockpit-podman", - ], - "rhel-atomic": [ - "rhel-atomic@cockpit-project/cockpit-ostree", - "rhel-atomic@cockpit-project/cockpit/rhel-7.7", - ] -} - -STORES = { - "rhel-7-7": REDHAT_STORE, - "rhel-7-8": REDHAT_STORE, - "rhel-8-0": REDHAT_STORE, - "rhel-8-1": REDHAT_STORE, - "rhel-atomic": REDHAT_STORE, - "windows-10": REDHAT_STORE, -} - -BOTS = os.path.abspath(os.path.dirname(__file__)) -BASE = os.path.normpath(os.path.join(BOTS, "..")) - -sys.dont_write_bytecode = True - -def run(image, verbose=False, **kwargs): - if not image: - raise RuntimeError("no image specified") - - triggers = TRIGGERS.get(image, [ ]) - store = STORES.get(image, None) - - # Cleanup any extraneous disk usage elsewhere - subprocess.check_call([ os.path.join(BOTS, "vm-reset") ]) - - cmd = [ os.path.join(BOTS, "image-create"), "--verbose", "--upload" ] - if store: - cmd += [ "--store", store ] - cmd += [ image ] - - os.environ['VIRT_BUILDER_NO_CACHE'] = "yes" - ret = subprocess.call(cmd) - if ret: - return ret - - branch = task.branch(image, "images: Update {0} image".format(image), pathspec="bots/images", **kwargs) - if branch: - pull = task.pull(branch, labels=['bot', 'no-test'], run_tests=False, **kwargs) - - # Trigger this pull request - api = github.GitHub() - head = pull["head"]["sha"] - for trigger in triggers: - api.post("statuses/{0}".format(head), { "state": "pending", "context": trigger, - "description": github.NOT_TESTED_DIRECT }) - -if __name__ == '__main__': - task.main(function=run, title="Refresh image") diff --git a/bots/image-trigger b/bots/image-trigger deleted file mode 100755 index eafb7038b..000000000 --- a/bots/image-trigger +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -DAYS = 7 - -REFRESH = { - "candlepin": { "refresh-days": 120 }, - "centos-7": { }, - "continuous-atomic": { }, - "debian-testing": { }, - "debian-stable": { }, - "fedora-29": { }, - "fedora-30": { }, - "fedora-31": { }, - "fedora-atomic": { }, - "fedora-testing": { }, - "fedora-i386": { }, - "ipa": { "refresh-days": 120 }, - "ubuntu-1804": { }, - "ubuntu-stable": { }, - "openshift": { "refresh-days": 30 }, - 'rhel-7-7': { }, - 'rhel-8-0': { }, - 'rhel-8-1': { }, - 'rhel-atomic': { }, - "selenium": { "refresh-days": 30 }, -} - -import argparse -import os -import sys -import tempfile -import time -import subprocess - -sys.dont_write_bytecode = True - -import task -from task import github - -def main(): - parser = argparse.ArgumentParser(description='Ensure necessary issue exists for image refresh') - parser.add_argument('-v', '--verbose', action="store_true", default=False, - help="Print verbose information") - parser.add_argument("image", nargs="?") - opts = parser.parse_args() - api = github.GitHub() - - try: - results = scan(api, opts.image, opts.verbose) - except RuntimeError as ex: - sys.stderr.write("image-trigger: " + str(ex) + "\n") - return 1 - - for result in results: - if result: - sys.stdout.write(result + "\n") - - return 0 - -# Prepare an image prune command -def scan_for_prune(): - tasks = [ ] - stamp = os.path.join(tempfile.gettempdir(), "cockpit-image-prune.stamp") - - # Don't prune more than once per hour - try: - mtime = os.stat(stamp).st_mtime - except OSError: - mtime = 0 - if mtime < time.time() - 3600: - tasks.append("PRIORITY=0000 touch {0} && bots/image-prune".format(stamp)) - - return tasks - -def scan(api, force, verbose): - subprocess.check_call([ "git", "fetch", "origin", "master" ]) - for (image, options) in REFRESH.items(): - perform = False - - if force: - perform = image == force - else: - days = options.get("refresh-days", DAYS) - perform = task.stale(days, os.path.join("bots", "images", image), "origin/master") - - if perform: - text = "Image refresh for {0}".format(image) - issue = task.issue(text, text, "image-refresh", image) - sys.stderr.write("#{0}: image-refresh {1}\n".format(issue["number"], image)) - - return scan_for_prune() - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/image-upload b/bots/image-upload deleted file mode 100755 index 3f16d4418..000000000 --- a/bots/image-upload +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - - -# The default settings here should match one of the default download stores -DEFAULT_UPLOAD = [ - "https://images-cockpit.apps.ci.centos.org/", - "https://209.132.184.41:8493/", -] - -TOKEN = "~/.config/github-token" - -import argparse -import getpass -import errno -import os -import socket -import subprocess -import sys -import urllib.parse - -from machine import testvm - -BOTS = os.path.dirname(__file__) - -def upload(store, source): - ca = os.path.join(BOTS, "images", "files", "ca.pem") - url = urllib.parse.urlparse(store) - - # Start building the command - cmd = ["curl", "--progress-bar", "--cacert", ca, "--fail", "--upload-file", source ] - - def try_curl(cmd): - print("Uploading to", cmd[-1]) - # Passing through a non terminal stdout is necessary to make progress work - curl = subprocess.Popen(cmd, stdout=subprocess.PIPE) - cat = subprocess.Popen(["cat"], stdin=curl.stdout) - curl.stdout.close() - ret = curl.wait() - cat.wait() - if ret != 0: - sys.stderr.write("image-upload: unable to upload image: {0}\n".format(cmd[-1])) - return ret - - # Parse the user name and token, if present - user = url.username or getpass.getuser() - try: - with open(os.path.expanduser(TOKEN), "r") as gt: - token = gt.read().strip() - cmd += [ "--user", user + ":" + token ] - except IOError as exc: - if exc.errno == errno.ENOENT: - pass - - # First try to use the original store URL, for stores with valid SSL cert on an OpenShift proxy - if try_curl(cmd + [store]) == 0: - return 0 - - # Fall back for stores that use our self-signed cockpit certificate - # Parse out the actual address to connect to and override certificate info - defport = url.scheme == 'http' and 80 or 443 - ai = socket.getaddrinfo(url.hostname, url.port or defport, socket.AF_INET, 0, socket.IPPROTO_TCP) - for (family, socktype, proto, canonname, sockaddr) in ai: - resolve = "cockpit-tests:{1}:{0}".format(*sockaddr) - curl_url = "https://cockpit-tests:{0}{1}".format(url.port or defport, url.path) - ret = try_curl(cmd + ["--resolve", resolve, curl_url]) - if ret == 0: - return 0 - - return 1 - - -def main(): - parser = argparse.ArgumentParser(description='Upload bot state or images') - parser.add_argument("--store", action="append", default=[], help="Where to send state or images") - parser.add_argument("--state", action="store_true", help="Images or state not recorded in git") - parser.add_argument('image', nargs='*') - args = parser.parse_args() - - data_dir = testvm.get_images_data_dir() - sources = [] - for image in args.image: - if args.state: - source = os.path.join(data_dir, image) - else: - link = os.path.join(testvm.IMAGES_DIR, image) - if not os.path.islink(link): - parser.error("image link does not exist: " + image) - source = os.path.join(data_dir, os.readlink(link)) - if not os.path.isfile(source): - parser.error("image does not exist: " + image) - sources.append(source) - - for source in sources: - for store in (args.store or DEFAULT_UPLOAD): - ret = upload(store, source) - if ret == 0: - return ret - else: - # all stores failed, so return last exit code - return ret - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/images/candlepin b/bots/images/candlepin deleted file mode 120000 index a5063c5f8..000000000 --- a/bots/images/candlepin +++ /dev/null @@ -1 +0,0 @@ -candlepin-3a39cecb7d2fea2e75b0093a891b3c476141406e20f332cb2a12f2dfb6e9d275.qcow2 \ No newline at end of file diff --git a/bots/images/centos-7 b/bots/images/centos-7 deleted file mode 120000 index 8d37e9146..000000000 --- a/bots/images/centos-7 +++ /dev/null @@ -1 +0,0 @@ -centos-7-b12881afba5b51520073d9633295a89121e4f52b2e8aee4e2c422a95064a902f.qcow2 \ No newline at end of file diff --git a/bots/images/cirros b/bots/images/cirros deleted file mode 120000 index a32092844..000000000 --- a/bots/images/cirros +++ /dev/null @@ -1 +0,0 @@ -cirros-d5fcb44e05f2dafc7eaab6bce906ba9cc06af51f84f1e7a527fe12102e34bbcf.qcow2 \ No newline at end of file diff --git a/bots/images/continuous-atomic b/bots/images/continuous-atomic deleted file mode 120000 index 6a513a8df..000000000 --- a/bots/images/continuous-atomic +++ /dev/null @@ -1 +0,0 @@ -continuous-atomic-a4c3407c689e53e6864f7ba92b95a0d7eea42b04545a7aeb77271b6f5521bd08.qcow2 \ No newline at end of file diff --git a/bots/images/debian-stable b/bots/images/debian-stable deleted file mode 120000 index 07b69cb21..000000000 --- a/bots/images/debian-stable +++ /dev/null @@ -1 +0,0 @@ -debian-stable-980197e60a14278239e4c06b39421e9bd16d9ad97e1d4bb7d405b8fb2a8b15a0.qcow2 \ No newline at end of file diff --git a/bots/images/debian-testing b/bots/images/debian-testing deleted file mode 120000 index 88cbe036f..000000000 --- a/bots/images/debian-testing +++ /dev/null @@ -1 +0,0 @@ -debian-testing-da77a67318002005f72e5f988978198fe6a53bea4bd60f2727a4123e98d6dd26.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-23-stock b/bots/images/fedora-23-stock deleted file mode 120000 index 297b4ad64..000000000 --- a/bots/images/fedora-23-stock +++ /dev/null @@ -1 +0,0 @@ -fedora-23-stock-1a7ce615dcf1772ff6514148513fc88e420b9179f32c5395e3a27dab3b107dcc.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-29 b/bots/images/fedora-29 deleted file mode 120000 index 1c62ab573..000000000 --- a/bots/images/fedora-29 +++ /dev/null @@ -1 +0,0 @@ -fedora-29-7191db4290794ba7bcf8eb30739d07a6cf59b072513b813b97755adb69162a95.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-30 b/bots/images/fedora-30 deleted file mode 120000 index 1b0786f04..000000000 --- a/bots/images/fedora-30 +++ /dev/null @@ -1 +0,0 @@ -fedora-30-219724b87c59cbb21378c3f1e5fe80e8963072dc0fa97c15c809cf19118f8433.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-31 b/bots/images/fedora-31 deleted file mode 120000 index f3d672dea..000000000 --- a/bots/images/fedora-31 +++ /dev/null @@ -1 +0,0 @@ -fedora-31-632704fccac608d265572c0500eeecae3d61e0d932683adb5b5092980032b3ef.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-atomic b/bots/images/fedora-atomic deleted file mode 120000 index e8b25dd16..000000000 --- a/bots/images/fedora-atomic +++ /dev/null @@ -1 +0,0 @@ -fedora-atomic-6a63990b2443f568bb3321efe55fe8bad8891d128ec4c5b818303cd52a34e1e0.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-i386 b/bots/images/fedora-i386 deleted file mode 120000 index 64e5b67dc..000000000 --- a/bots/images/fedora-i386 +++ /dev/null @@ -1 +0,0 @@ -fedora-i386-f5c6c9730facd6b7d00d5c07f59cf7bf3a9ce3de1270f174cf5d9aefcd86a297.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-stock b/bots/images/fedora-stock deleted file mode 120000 index e0c61350b..000000000 --- a/bots/images/fedora-stock +++ /dev/null @@ -1 +0,0 @@ -stock-fedora-22-x86_64-2.qcow2 \ No newline at end of file diff --git a/bots/images/fedora-testing b/bots/images/fedora-testing deleted file mode 120000 index be1a08978..000000000 --- a/bots/images/fedora-testing +++ /dev/null @@ -1 +0,0 @@ -fedora-testing-fedf1d06768b7cb69efbb2ef27ae665161c938d94f844d63de3c3fb20f509b8f.qcow2 \ No newline at end of file diff --git a/bots/images/files/ca.pem b/bots/images/files/ca.pem deleted file mode 100644 index 076adb614..000000000 --- a/bots/images/files/ca.pem +++ /dev/null @@ -1,21 +0,0 @@ -# This is the CA for cockpit-tests images and data - ------BEGIN CERTIFICATE----- -MIIDDDCCAfSgAwIBAgIJANdoyGJiUz+8MA0GCSqGSIb3DQEBCwUAMDUxEDAOBgNV -BAoMB0NvY2twaXQxFDASBgNVBAsMC0NvY2twaXR1b3VzMQswCQYDVQQDDAJDQTAg -Fw0xOTAyMDcxMDE4NDNaGA8zMDE4MDYxMDEwMTg0M1owNTEQMA4GA1UECgwHQ29j -a3BpdDEUMBIGA1UECwwLQ29ja3BpdHVvdXMxCzAJBgNVBAMMAkNBMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnvIZetd5yEhdE0c/9lYp1mC4M6qiu6E2 -wVMbJLwsOuCyCSaZs5eDap1kremHz7ms+Fq07TUsN/o5U7PBnNgM3z6Zbv78QN6R -wn6ovLHfCyVqpg0nPMh3Hzpd0HDZQ+3eBayL2xfmBhU8p1+/vWVBOe49SDO15YDM -/Ian7I/HRsnprz5PH3atquSf+B8/Q+lgbO0dHKhXlbnTsSy/Esee82HhYrDlxD3p -Ow7EcZ7HACh/2dvF70BQpjnxTEc//4LNgP7hiqk4phsGzM/9QSFHW8ol4XlBDUi0 -F5nNXZTs3jKITTOeda5mppuKoZoC+7iFk8dLvV0Y187xD38X2XgGnwIDAQABox0w -GzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEA -PHaVKb97ZN2m/sEVU+TGepVhCZ15frIaCJRuBPEs5rwcJjIctyRF4H6R6ec2b2lB -6ni9eqU6pPgS+rVJPsxqCpelQiCZALR7FYoA6+FtfpLkB5+zwJUfexr7Q6I7llWI -8OBOmtEADRv//2D+Iu6mM6nkzUK1K/wCcFS//roLjK/nKH2xd2lWbYk2Ro+nTPIm -slwgk6fAUXQcd5v/XqrySZ5jny73jMqo7SRVC5suNuAfiT0/YGvE5N99+I5AkD5I -R/R80/w1bDExfcqtx5UPBitMG2bx/gA07k4XbAGsEH5zvIdgsV9S5uYQEDjIRZys -ScLMpNOd3JyD7ncvr6Ga6g== ------END CERTIFICATE----- diff --git a/bots/images/files/openshift.kubeconfig b/bots/images/files/openshift.kubeconfig deleted file mode 100644 index 6036d6c40..000000000 --- a/bots/images/files/openshift.kubeconfig +++ /dev/null @@ -1,37 +0,0 @@ -apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM2akNDQWRLZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFtTVNRd0lnWURWUVFEREJ0dmNHVnUKYzJocFpuUXRjMmxuYm1WeVFERTFOamMxTWpjMk56UXdIaGNOTVRrd09UQXpNVFl5TVRFeldoY05NalF3T1RBeApNVFl5TVRFMFdqQW1NU1F3SWdZRFZRUUREQnR2Y0dWdWMyaHBablF0YzJsbmJtVnlRREUxTmpjMU1qYzJOelF3CmdnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURQOUVKZ05NdEk0TG1KSk9wRGNiUlQKbWNDazF1amJiQVVydTU3bjFFY0ZnTEhDNzZCQmxsME9sbzNQcnRUVWVTR0d5c3R3eW5HT0J3MytGOFBzL1hjZApwWmwrdGkySkMweGVVSnFNVDMyclRwTHVwWG1tczZGMi90d0dTc0lSTWx5NjgyZ01NaHlmV0wrT2FRZERaM3NWCjd6aDBPd2E3cE5wWWUwSE1VbUM5QzFPaEltc254YVF2Mzh4TGU5SjgvQXZxSXZMV21Wc1J3cnEveWhVakphOWkKMkZYV20wa2Z4WjBNckpRQWI2cGlTZzVEbE5pc2htbURPbU1QR21mWlI5ZkY2SHA5MWRuV3lId2NCVmpnVTJHagpRMmlqWWh5WGRMYS9RaDEzTG9BQUFiQ29aUjVKcVNUL3luQU1SWlFXTE1CRFYvSC95Wk53RFI1WUJ1YXI4LzlsCkFnTUJBQUdqSXpBaE1BNEdBMVVkRHdFQi93UUVBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwR0NTcUcKU0liM0RRRUJDd1VBQTRJQkFRQXdWSDk2SGtTQ1NWTlRXbmJOK0R1TE1Kb0l0M3ZRTW5aQ2hwcnAzWUxiZ0MvZgpxeGMxUmtXaXBTemNhYUJQSHU4RkR6aTlMOUpFcVIwVWhyeHN5Sk9iSGxJOVB5cnN2WnhpR05pc2UvOE1IKzRtCjFlMUVDZGNlT3pHOVJlK09SOGV4b25GaitJSk9ZNG9xanVtRFM2ZmdRS01Ja08vN29SZmhxRGZJREMveVUvTnUKc09xYmZnS1dMeWxxOWJKTEtQVUkwemw3YnUrSmNyK3g1anhQaTFLY05yY1BXaXNoVFpXNExrakh0Wkl5QkNyLwp5MTQ2eHZLc1hHMXY2aEJ4ZTBvRnFrcVNqMzhUYTRXV2NNUTVXd1lxbU5xMVhkRFN0T0UzMm1iZmNMZWJXSXRuCitDVGVOcldOSnQxQXE4Q3p2UDdOMEwwVkxrT1NPSmpoTVMzNVdKUnYKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - server: https://10.111.112.101:8443 - name: 10-111-112-101:8443 -contexts: -- context: - cluster: 10-111-112-101:8443 - user: scruffy/10-111-112-101:8443 - name: /10-111-112-101:8443/scruffy -- context: - cluster: 10-111-112-101:8443 - namespace: default - user: system:admin/10-111-112-101:8443 - name: default/10-111-112-101:8443/system:admin -- context: - cluster: 10-111-112-101:8443 - namespace: marmalade - user: scruffy/10-111-112-101:8443 - name: marmalade/10-111-112-101:8443/scruffy -- context: - cluster: 10-111-112-101:8443 - namespace: pizzazz - user: scruffy/10-111-112-101:8443 - name: pizzazz/10-111-112-101:8443/scruffy -current-context: default/10-111-112-101:8443/system:admin -kind: Config -preferences: {} -users: -- name: scruffy/10-111-112-101:8443 - user: - token: S6xMoSAI-Rs4shOP0aU4Y4hMEqQJB2KlBAHU1q86M5Y -- name: system:admin/10-111-112-101:8443 - user: - client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKRENDQWd5Z0F3SUJBZ0lCQ0RBTkJna3Foa2lHOXcwQkFRc0ZBREFtTVNRd0lnWURWUVFEREJ0dmNHVnUKYzJocFpuUXRjMmxuYm1WeVFERTFOamMxTWpjMk56UXdIaGNOTVRrd09UQXpNVFl5TVRFNFdoY05NakV3T1RBeQpNVFl5TVRFNVdqQk9NVFV3RlFZRFZRUUtFdzV6ZVhOMFpXMDZiV0Z6ZEdWeWN6QWNCZ05WQkFvVEZYTjVjM1JsCmJUcGpiSFZ6ZEdWeUxXRmtiV2x1Y3pFVk1CTUdBMVVFQXhNTWMzbHpkR1Z0T21Ga2JXbHVNSUlCSWpBTkJna3EKaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0UTJvT1NhaHlBSnpVdlYxMmtKdXc5ZFBjaDFiT29CNgpRNW1vdEM3b0k0V1F3SnhISnBWTWIxRkhWTm96dnNJQWZEMTZiaXZGd1VLWk5iQmRQNlVCZy90MlRST1JJTHZRCjBvYUg5OEd5NGZXUEE3TE9kekZ3THcxeW1BTk9UNjdYZFpUdzB0cEhOQUN6akgyd0t1cS9MRTFoVDNpTGFKSFEKQVRxdzRuOVNFSFQwbVJTYTBDcU5HRWUrZkxZQXNrd3FPOHE0UW5NOWY4QTdHUVdxY29lMFdlVDRha1VYOUVlbwpONUg1Vmt2V0VuMHhmTXBYWHQ3VGM0YUlPbk9Ba3lJbnkwQzU4TVhDNlJibHo0KzN1cHNwRlB3ZTJtS1o5bXliClZrWFcrZjJQcjJaSEZJdS9ORmx1MFJnOEdmWVVMRHgvTG9TSGFxbzBzRndNTFpwZ1RjTzdzd0lEQVFBQm96VXcKTXpBT0JnTlZIUThCQWY4RUJBTUNCYUF3RXdZRFZSMGxCQXd3Q2dZSUt3WUJCUVVIQXdJd0RBWURWUjBUQVFILwpCQUl3QURBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQUVGRzQyS3lyNkFBNCsrdjU4M1VablRlVE4xdFdLYWlLCjRURjdlWFNQNG1TRmtrYjF1RGpuekphZWUwejJUMTBpVkUvNGdsOXhKcXVUZ3k4b0RQSmFSUFJuc0kxbGVLTzkKMi90bkFJQ3kzbE5wL2U4MksybFZGVDcvdlEwL3Nqd1lVaVAzQnRoQTZkdHpMUWpFTE1abytNZm1ucnJCM0k2TgpCelk1a1pTN2hENTdZQ2FCZmRjZ0hlNWQzY0p4ZEQ4RGhMNVNBemFUTUsrcW54ZXEvU2U2TU42alRYOVBnamxIClJjK2liSnhYVkh2TmUwNC9sN0I3S2pGVW9qRG1aTE8rK290RFdldmx4SHRNRzNuV21POWo1Z0V4NVlLaXJPcjkKSTU0NHVEUGlINkdJQUdENytwaFVpMzFMVTFuYmIreWN2akpvWWVlTG1WRERzWGZ6Ri9xUzN3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdFEyb09TYWh5QUp6VXZWMTJrSnV3OWRQY2gxYk9vQjZRNW1vdEM3b0k0V1F3SnhICkpwVk1iMUZIVk5venZzSUFmRDE2Yml2RndVS1pOYkJkUDZVQmcvdDJUUk9SSUx2UTBvYUg5OEd5NGZXUEE3TE8KZHpGd0x3MXltQU5PVDY3WGRaVHcwdHBITkFDempIMndLdXEvTEUxaFQzaUxhSkhRQVRxdzRuOVNFSFQwbVJTYQowQ3FOR0VlK2ZMWUFza3dxTzhxNFFuTTlmOEE3R1FXcWNvZTBXZVQ0YWtVWDlFZW9ONUg1Vmt2V0VuMHhmTXBYClh0N1RjNGFJT25PQWt5SW55MEM1OE1YQzZSYmx6NCszdXBzcEZQd2UybUtaOW15YlZrWFcrZjJQcjJaSEZJdS8KTkZsdTBSZzhHZllVTER4L0xvU0hhcW8wc0Z3TUxacGdUY083c3dJREFRQUJBb0lCQVFDR3ZQY29NUHZNNFNYNQo0dm9seDdLdXhCazNqMmxKRER2dyt2VjF3a0szekxxQTNNeUdoaTB2Mm9qL09MT3hqcWJWenRyQ0NvbE0zY2N2CkVXVVQ3RFJJaUdidHpWWC95a1lKcGx5aG9PRURENyt5dk9xeUFYUy9UMzZzYWlscFczQzA3SGFjTkIweE1pUnMKdFV6Wlk0R0o4cndzYkVVek9QQlhPZHBSZFBjWmp1OEF3ZDBkS2oxQmwvMGVCL2RxZCsrR3o0ZEFNR1h0b1V6WApSVGtBSWtLWGJEUHVON1oyck5BMzVmWnhBOE44b0NzalYyODRlUnhiUURQejVUQXB1RExEdHZqYk8xTVZvdVpECjZyZUtmSStnbDlIUnZ6YlMxekhXWjVSZ1F2RUFIV0pKcHBBMGF2NHdEU0NKZjhLNnl2R3NvbjJ0UzAveUErY3YKVVMxWEJIckJBb0dCQU1nNW5IM2twclBUUFRkcTRVQmhRWndLWG50Nm5kL2huRmQ2d1h1VVY5UmRrU1ZXbmxkcQpMOVcwVDU5ODFMeXdEb1ZXL2swUTkvelFMRG9ONTZDOGpTTnBkZHZtQlBHY2tkNU1JN1RtTEwvTVRtNWRLOGZHClZGNnorMVRvMm9BcW83czFFSWhseXlmckZPTVI0NThsSENWVEdPNlI3Y2MzQy8rVU01MVNQY0tMQW9HQkFPZDgKNFNwWFg0TjJHZmRZQTRyKzdqNHFDWXdLYVpyWjlmckxZQXI2cnBja2xScDRZL0M3b0Nja2VTM3BNMDB6SVlQQgpMQis0YVRJeXdBaVNxY3I1YVhnM0tZTkVKNFFpaEpiRjNqV1I2cUdtc3kxMzBCWVpHQVg1VDRMdHVJc0ZIV2NvCkdjNDJQSXhXREtxdzJzYUZBZTBFa2lkb2dsTit5eGt6WWJMQWVWaDVBb0dCQUxyaTQwbXl5Vktod2NyZkxQNTkKaU5MUDd0Nk1WWjJwcE5jV1VsQTU1enptVk5zb0hVVjBiTStvcklVdDdCZHVzUzhPUXZERi9PSnhvRVpUd2phSwpwNlk5QW5CTkk2SXRSUTNidlp4VkY4R3lQaWJQT2xVT3JxTnlsUTN0QmoySkR5aG00RmFmeE44dWttRmJ5ajA2ClV5b1hoUGJ4S0tMQW82ZGJ1azJHZlBUL0FvR0FZYlFPb1QxaGZlNENCYWlyVGlaTlRnV1dJL3BkR2xPMmc1VUYKUTMwTTVaUTJMb2J6djY2aGFRUDI5WTdBN1d1UVVMamVzOEMwL2MvM3gyYUhyYmpaY0Rqd0Y0eFRsV3l3UTZiZQpKQVFqWVBrb0ZSL0Z2eDMyU3Njd2JSV0MxNEpnSjZNQVNVNFEvalp2Z0RmSER4VWllL1I2NzVFbnVMQUNidStGCjQ5bGpIaGtDZ1lBUFA2RGM1SCtyMC8vVWUrSlNVTzY1N2pIdW5WYU8rZE81bVJZTzZqR1NHc2JpWnZ3RmZjZEIKZXZBTFJQQ0NOS3F4U1dGU2RXc3p0WUNtZ3BHSVFpaXowMncvSk54bERHTVd1T002WnlsODl2WmdPYk1SWnBWWQpzZ0pMNGZJMFFLZmdhMkZoYUdML1Z4L2c0RE91Vy8zWjZ6NEI3YzYySXE1OVFhaEZURERERlE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/bots/images/ipa b/bots/images/ipa deleted file mode 120000 index 121026b66..000000000 --- a/bots/images/ipa +++ /dev/null @@ -1 +0,0 @@ -ipa-94b1c71c3c7d0eb739170278f537225fb347530ecc2a3282fb9224dbe334b84a.qcow2 \ No newline at end of file diff --git a/bots/images/openshift b/bots/images/openshift deleted file mode 120000 index e27f4f638..000000000 --- a/bots/images/openshift +++ /dev/null @@ -1 +0,0 @@ -openshift-c65dc46c4885d5b7cd1198d310cb0a62a515e0cf67aba4a0ba55caf2b7fddd39.qcow2 \ No newline at end of file diff --git a/bots/images/ovirt b/bots/images/ovirt deleted file mode 120000 index 1293da616..000000000 --- a/bots/images/ovirt +++ /dev/null @@ -1 +0,0 @@ -ovirt-f033c4457fecb1e9078eb16d7ac5239fe79455ca6b533f2a37de4f965cf174e7.qcow2 \ No newline at end of file diff --git a/bots/images/rhel-7-7 b/bots/images/rhel-7-7 deleted file mode 120000 index 7b0bf6b54..000000000 --- a/bots/images/rhel-7-7 +++ /dev/null @@ -1 +0,0 @@ -rhel-7-7-9d4481970308e14f9af75151118b6ffc9bf0fb2c1c9bda2f6c19d70b3cc612d4.qcow2 \ No newline at end of file diff --git a/bots/images/rhel-7-8 b/bots/images/rhel-7-8 deleted file mode 120000 index 167a6fe43..000000000 --- a/bots/images/rhel-7-8 +++ /dev/null @@ -1 +0,0 @@ -rhel-7-8-90e79cf4377714b69929e8613375987c0d7b6a8e5deff38bbfa9662ff583d5e0.qcow2 \ No newline at end of file diff --git a/bots/images/rhel-8-0 b/bots/images/rhel-8-0 deleted file mode 120000 index eb6195dac..000000000 --- a/bots/images/rhel-8-0 +++ /dev/null @@ -1 +0,0 @@ -rhel-8-0-a7af23080d6f6d5595b5fbc5331a2f7148069223659701c1870c844babc56833.qcow2 \ No newline at end of file diff --git a/bots/images/rhel-8-1 b/bots/images/rhel-8-1 deleted file mode 120000 index ab112db91..000000000 --- a/bots/images/rhel-8-1 +++ /dev/null @@ -1 +0,0 @@ -rhel-8-1-25b42538c7798dda35e910167a9de2af924ff28d81e8612bb5318469c2962c06.qcow2 \ No newline at end of file diff --git a/bots/images/rhel-atomic b/bots/images/rhel-atomic deleted file mode 120000 index 4074f04e0..000000000 --- a/bots/images/rhel-atomic +++ /dev/null @@ -1 +0,0 @@ -rhel-atomic-a1388f58b093cb83a15f4260f2fc3bbb42b85aae102e5f4e467f2635faeca4c9.qcow2 \ No newline at end of file diff --git a/bots/images/scripts/atomic.bootstrap b/bots/images/scripts/atomic.bootstrap deleted file mode 100755 index 04642dcc2..000000000 --- a/bots/images/scripts/atomic.bootstrap +++ /dev/null @@ -1,78 +0,0 @@ -#! /bin/bash - -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -set -ex - -out="$1" -base="$2" - -redirect_base=$(curl -s -w "%{redirect_url}" "$base" -o /dev/null) -if [ -n "$redirect_base" ]; then - base="$redirect_base" -fi - -# Lookup the newest base image recursively -url="$base" -while [ $# -gt 2 ]; do - fragment="$3" - - if [ "$fragment" = "sort" ]; then - backref="$4" - pattern="$5" - - result="`wget -q -O- $url | grep -oE "$pattern" | sed -E "s/${pattern}/\\\\${backref} \\0/" | sort -V -k1 | tail -1`" - fragment="`echo $result | cut -f2 -d' '`" - - - if [ -z "$fragment" ]; then - echo "Could not find '$pattern' at: $url" >&2 - exit 1 - fi - - shift; shift - fi - - base="$url" - url="$base/$fragment" - - shift -done - -# we link to the file so wget can properly detect if we have already downloaded it -# note that due to mirroring, timestamp comparison can result in unnecessary downloading -out_base="`dirname $out`" -intermediate="$out_base/$fragment" - -if [ "$intermediate" != "$out" ]; then - wget --no-clobber --directory-prefix="$out_base" "$base/$fragment" - cp "$intermediate" "$out" -else - rm -f "$out" - wget --directory-prefix="$out_base" "$base/$fragment" -fi - -# Make the image be at least 12 Gig. During boot, docker-storage-setup -# will grow the partitions etc as appropriate, and atomic.setup will -# explicitly grow the docker pool. - -vsize=$(qemu-img info "$out" --output=json | python3 -c 'import json, sys; print(json.load(sys.stdin)["virtual-size"])') - -if [ "$vsize" -lt 12884901888 ]; then - qemu-img resize "$out" 12884901888 -fi diff --git a/bots/images/scripts/candlepin.bootstrap b/bots/images/scripts/candlepin.bootstrap deleted file mode 120000 index 98c05f002..000000000 --- a/bots/images/scripts/candlepin.bootstrap +++ /dev/null @@ -1 +0,0 @@ -centos-7.bootstrap \ No newline at end of file diff --git a/bots/images/scripts/candlepin.setup b/bots/images/scripts/candlepin.setup deleted file mode 100755 index 51d786f98..000000000 --- a/bots/images/scripts/candlepin.setup +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -set -ex - -YUM_INSTALL="yum --setopt=skip_missing_names_on_install=False -y install" - -# We deploy candlepin via ansible -$YUM_INSTALL epel-release - -# Install dependencies -CANDLEPIN_DEPS="\ -ansible \ -git \ -openssl \ -" - -$YUM_INSTALL $CANDLEPIN_DEPS - -mkdir -p playbookdir; cd playbookdir; - -mkdir -p roles -git clone https://github.com/candlepin/ansible-role-candlepin.git roles/candlepin - -# Run the playbook -cat > inventory <<- EOF -[dev] -localhost -EOF - -useradd -m admin -echo admin:foobar | chpasswd -echo 'admin ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/admin - -cat > playbook.yml <<- EOF -- hosts: dev - - environment: - JAVA_HOME: /usr/lib/jvm/java-1.8.0/ - - roles: - - role: candlepin - candlepin_git_pull: True - candlepin_deploy_args: "-g -a -f -t" - candlepin_user: admin - candlepin_user_home: /home/admin - candlepin_checkout: /home/admin/candlepin -EOF - -ansible-playbook -i inventory -c local -v --skip-tags 'system_update' playbook.yml - -rm -rf playbookdir - -# reduce image size -yum clean all -/var/lib/testvm/zero-disk.setup - -# Final tweaks - -rm -rf /var/log/journal/* -echo "kernel.core_pattern=|/usr/lib/systemd/systemd-coredump %p %u %g %s %t %e" > /etc/sysctl.d/50-coredump.conf - -# Audit events to the journal -rm -f '/etc/systemd/system/multi-user.target.wants/auditd.service' -rm -rf /var/log/audit/ - diff --git a/bots/images/scripts/centos-7.bootstrap b/bots/images/scripts/centos-7.bootstrap deleted file mode 100755 index c6b2918d0..000000000 --- a/bots/images/scripts/centos-7.bootstrap +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "http://mirror.centos.org/centos/7/os/x86_64/" diff --git a/bots/images/scripts/centos-7.install b/bots/images/scripts/centos-7.install deleted file mode 100755 index 38e802ade..000000000 --- a/bots/images/scripts/centos-7.install +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/bash - -set -e - -# remove cockpit distro packages, testing with upstream master -rpm --erase --verbose cockpit cockpit-ws cockpit-bridge cockpit-system - -/var/lib/testvm/fedora.install "$@" diff --git a/bots/images/scripts/centos-7.setup b/bots/images/scripts/centos-7.setup deleted file mode 120000 index 4fbdfa18d..000000000 --- a/bots/images/scripts/centos-7.setup +++ /dev/null @@ -1 +0,0 @@ -rhel.setup \ No newline at end of file diff --git a/bots/images/scripts/cirros.bootstrap b/bots/images/scripts/cirros.bootstrap deleted file mode 100755 index 12409f0ea..000000000 --- a/bots/images/scripts/cirros.bootstrap +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -set -eux - -OUTPUT="$1" - -curl https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-i386-disk.img > "$OUTPUT" - -# prepare a cloud-init iso for disabling network source, to avoid a 90s timeout at boot -WORKDIR=$(mktemp -d) -trap "rm -rf '$WORKDIR'" EXIT INT QUIT PIPE -cd "$WORKDIR" - -cat > meta-data < user-data <. - -set -ex - -# The docker pool should grow automatically as needed, but we grow it -# explicitly here anyway. This is hopefully more reliable. -# HACK: docker falls over regularly, print its log if it does -systemctl start docker || journalctl -u docker -lvresize atomicos/root -l+50%FREE -r -if lvs atomicos/docker-pool 2>/dev/null; then - lvresize atomicos/docker-pool -l+100%FREE -elif lvs atomicos/docker-root-lv; then - lvresize atomicos/docker-root-lv -l+100%FREE -fi - -# Get the centos cockpit/ws image -docker pull registry.centos.org/cockpit/ws:latest -docker tag registry.centos.org/cockpit/ws cockpit/ws - -# docker images that we need for integration testing -/var/lib/testvm/docker-images.setup - -# Configure core dumps -echo "kernel.core_pattern=|/usr/lib/systemd/systemd-coredump %p %u %g %s %t %e" > /etc/sysctl.d/50-coredump.conf - -# Download the libssh RPM plus dependencies which we'll use for -# package overlay. The only way to do this is via a container -. /etc/os-release -REPO="updates" -if [ "$ID" = "rhel" ]; then - subscription-manager repos --enable rhel-7-server-extras-rpms - REPO="rhel-7-server-extras-rpms" - ID="rhel7" -fi -docker run --rm --volume=/etc/yum.repos.d:/etc/yum.repos.d:z --volume=/root/rpms:/tmp/rpms:rw,z "$ID:$VERSION_ID" /bin/sh -cex "yum install -y findutils createrepo_c && yum install -y --downloadonly --enablerepo=$REPO libssh && find /var -name '*.rpm' | while read rpm; do mv -v \$rpm /tmp/rpms; done; createrepo_c /tmp/rpms" -rm -f /etc/yum.repos.d/* -cat >/etc/yum.repos.d/deps.repo <> /etc/ssh/sshd_config - -# Final tweaks -rm -rf /var/log/journal/* diff --git a/bots/images/scripts/debian-stable.bootstrap b/bots/images/scripts/debian-stable.bootstrap deleted file mode 100755 index 0b4d85ff4..000000000 --- a/bots/images/scripts/debian-stable.bootstrap +++ /dev/null @@ -1,7 +0,0 @@ -#! /bin/sh -ex - -RELEASE=buster -RELEASENUM=10 -LATEST_DAILY=$(curl -s https://cloud.debian.org/images/cloud/$RELEASE/daily/ | sed -n '/> /etc/hosts - -if grep -q 'ID=ubuntu' /etc/os-release; then - PBUILDER_OPTS='COMPONENTS="main universe"' - - # We want to use/test NetworkManager instead of netplan/networkd for ethernets - mkdir -p /etc/NetworkManager/conf.d - touch /etc/NetworkManager/conf.d/10-globally-managed-devices.conf -fi - -# some cloud images have a pre-defined admin user or group, for them cloud-init admin creation fails -userdel -r admin || true -groupdel admin || true -useradd -m -U -c Administrator -G sudo -s /bin/bash admin -echo admin:foobar | chpasswd -cp -r ~root/.ssh ~admin/ -chown -R admin:admin ~admin/.ssh - -# avoid NM-wait-online hanging on disconnected interfaces -mkdir -p /etc/NetworkManager/conf.d/ -printf '[main]\nno-auto-default=*\n' > /etc/NetworkManager/conf.d/noauto.conf - -if [ "${1#debian}" != "$1" ]; then - # HACK: Debian's cloud-init generates a *.cfg file, but /etc/network/interfaces sources extension-less files - mv /etc/network/interfaces.d/50-cloud-init.cfg /etc/network/interfaces.d/50-cloud-init -fi - -# debian-testing image gets bootstrapped from debian stable; upgrade -if [ "$1" = "debian-testing" ]; then - rm --verbose -f /etc/apt/sources.list.d/* - echo 'deb http://deb.debian.org/debian testing main' > /etc/apt/sources.list -fi - -export DEBIAN_FRONTEND=noninteractive -apt-get -y update -# apt go-faster -echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/90nolanguages -apt-get install -y eatmydata -# remove packages that we don't need -for p in lxd snapd landscape-common accountsservice open-vm-tools ufw cloud-init; do eatmydata apt-get purge -y --auto-remove $p || true; done - -# HACK: work around fuse 2.9.9-1 install failure (https://bugs.debian.org/935496) -if [ "$1" = "debian-testing" ]; then - rm /dev/fuse - # this needs to happen right away, as upgrading other packages triggers udev events which recreate /dev/fuse - eatmydata apt-get install -y fuse -fi - -# HACK: debian-stable image got /usr/bin/qemu-img removed, even though qemu-utils package is installed -if [ "$1" = "debian-stable" ]; then - eatmydata apt-get install --reinstall -y qemu-utils -fi - -# install our dependencies -DEBIAN_FRONTEND=noninteractive eatmydata apt-get -y dist-upgrade -eatmydata apt-get -y install $TEST_PACKAGES $COCKPIT_DEPS $IPA_CLIENT_PACKAGES - -# Prepare for building -# - -# extract control files and adjust them for our release, so that we can parse the build deps -mkdir -p /tmp/out -curl -L https://github.com/cockpit-project/cockpit/archive/master.tar.gz | tar -C /tmp/out --strip-components=1 --wildcards -zxf - '*/debian/' -/tmp/out/tools/debian/adjust-for-release $(lsb_release -sc) - -# Disable build-dep installation for the real builds -cat > ~/.pbuilderrc <<- EOF -DISTRIBUTION=$RELEASE -PBUILDERSATISFYDEPENDSCMD=true -$PBUILDER_OPTS -EOF - -eatmydata apt-get -y install dpkg-dev pbuilder - -pbuilder --create --extrapackages "fakeroot $PBUILDER_EXTRA" -/usr/lib/pbuilder/pbuilder-satisfydepends-classic --control /tmp/out/tools/debian/control --force-version --echo|grep apt-get | pbuilder --login --save-after-login -rm -rf /tmp/out - -# Debian does not automatically start the default libvirt network -virsh net-autostart default - -# Don't automatically update on boot or daily -systemctl disable apt-daily.service apt-daily.timer || true - -# Enable coredumping via systemd -echo "kernel.core_pattern=|/lib/systemd/systemd-coredump %P %u %g %s %t %c %e" > /etc/sysctl.d/50-coredump.conf -printf 'DefaultLimitCORE=infinity\n' >> /etc/systemd/system.conf - -# HACK: we need to restart it in case aufs-dkms was installed after docker.io -# and thus docker.io auto-switches its backend -systemctl restart docker || journalctl -u docker -I=$(docker info) -if ! echo "$I" | grep -Eq 'Storage.*(aufs|overlay)'; then - echo "ERROR! docker does not use aufs or overlayfs" - exit 1 -fi - -# docker images that we need for integration testing -/var/lib/testvm/docker-images.setup - -rm -rf /var/lib/docker/devicemapper - -# in case there are unnecessary packages -eatmydata apt-get -y autoremove || true - -# disable udev network names, our tests expect the kernel schema -sed -i '/GRUB_CMDLINE_LINUX=/ s/"$/ net.ifnames=0 biosdevname=0"/' /etc/default/grub -rm -f /etc/udev/rules.d/70-persistent-net.rules /etc/udev/rules.d/75-cloud-ifupdown.rules -update-grub -sed -i 's/ens[^[:space:]:]*/eth0/' /etc/network/interfaces /etc/network/interfaces.d/* /etc/netplan/*.yaml || true -update-initramfs -u - - -# reduce image size -apt-get clean -pbuilder clean -rm -f /var/cache/apt/*cache.bin -/var/lib/testvm/zero-disk.setup - -# Final tweaks - -# Enable persistent journal -mkdir -p /var/log/journal - -# Allow root login with password -sed -i 's/^[# ]*PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config - -# At least debian-9 virt-install image only has RSA key -[ -e /etc/ssh/ssh_host_ed25519_key ] || ssh-keygen -f /etc/ssh/ssh_host_ed25519_key -N '' -t ed25519 -[ -e /etc/ssh/ssh_host_ecdsa_key ] || ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa - -# Prevent SSH from hanging for a long time when no external network access -echo 'UseDNS no' >> /etc/ssh/sshd_config - -# HACK: https://bugzilla.mindrot.org/show_bug.cgi?id=2512 -# Disable the restarting of sshd when networking changes -ln -snf /bin/true /etc/network/if-up.d/openssh-server - -# Stop showing 'To run a command as administrator (user "root"), use "sudo ". See "man -# sudo_root" for details.` message in admins terminal. -touch /home/admin/.sudo_as_admin_successful diff --git a/bots/images/scripts/fedora-23-stock.bootstrap b/bots/images/scripts/fedora-23-stock.bootstrap deleted file mode 100755 index c46bd1c24..000000000 --- a/bots/images/scripts/fedora-23-stock.bootstrap +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2015 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA. - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/23/Server/x86_64/os/" diff --git a/bots/images/scripts/fedora-23-stock.setup b/bots/images/scripts/fedora-23-stock.setup deleted file mode 100755 index 170761591..000000000 --- a/bots/images/scripts/fedora-23-stock.setup +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/bash - -useradd -c Administrator -G wheel admin -echo foobar | passwd --stdin admin - -dnf -y update -dnf -y install fedora-release-server -firewall-cmd --permanent --add-service cockpit - -# Phantom can't use TLS.. -sed -i -e 's/ExecStart=.*/\0 --no-tls/' /usr/lib/systemd/system/cockpit.service diff --git a/bots/images/scripts/fedora-29.bootstrap b/bots/images/scripts/fedora-29.bootstrap deleted file mode 100755 index 02c6e680f..000000000 --- a/bots/images/scripts/fedora-29.bootstrap +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2018 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA. - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "http://dl.fedoraproject.org/pub/fedora/linux/releases/29/Server/x86_64/os/" diff --git a/bots/images/scripts/fedora-29.install b/bots/images/scripts/fedora-29.install deleted file mode 100755 index 2ad1e852f..000000000 --- a/bots/images/scripts/fedora-29.install +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash - -set -e -/var/lib/testvm/fedora.install "$@" diff --git a/bots/images/scripts/fedora-29.setup b/bots/images/scripts/fedora-29.setup deleted file mode 120000 index f78434e0c..000000000 --- a/bots/images/scripts/fedora-29.setup +++ /dev/null @@ -1 +0,0 @@ -fedora.setup \ No newline at end of file diff --git a/bots/images/scripts/fedora-30.bootstrap b/bots/images/scripts/fedora-30.bootstrap deleted file mode 100755 index ab80e7528..000000000 --- a/bots/images/scripts/fedora-30.bootstrap +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2019 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA. - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "http://dl.fedoraproject.org/pub/fedora/linux/releases/30/Server/x86_64/os/" diff --git a/bots/images/scripts/fedora-30.install b/bots/images/scripts/fedora-30.install deleted file mode 100755 index 2ad1e852f..000000000 --- a/bots/images/scripts/fedora-30.install +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash - -set -e -/var/lib/testvm/fedora.install "$@" diff --git a/bots/images/scripts/fedora-30.setup b/bots/images/scripts/fedora-30.setup deleted file mode 120000 index f78434e0c..000000000 --- a/bots/images/scripts/fedora-30.setup +++ /dev/null @@ -1 +0,0 @@ -fedora.setup \ No newline at end of file diff --git a/bots/images/scripts/fedora-31.bootstrap b/bots/images/scripts/fedora-31.bootstrap deleted file mode 100755 index 2d6dc0e00..000000000 --- a/bots/images/scripts/fedora-31.bootstrap +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2019 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA. - -BASE=$(dirname $0) -# once fedora 31 is released, replace url: -# http://dl.fedoraproject.org/pub/fedora/linux/releases/31/Server/x86_64/os/ -$BASE/virt-install-fedora "$1" x86_64 "https://dl.fedoraproject.org/pub/fedora/linux/development/31/Everything/x86_64/os/" diff --git a/bots/images/scripts/fedora-31.install b/bots/images/scripts/fedora-31.install deleted file mode 100755 index 2ad1e852f..000000000 --- a/bots/images/scripts/fedora-31.install +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash - -set -e -/var/lib/testvm/fedora.install "$@" diff --git a/bots/images/scripts/fedora-31.setup b/bots/images/scripts/fedora-31.setup deleted file mode 120000 index f78434e0c..000000000 --- a/bots/images/scripts/fedora-31.setup +++ /dev/null @@ -1 +0,0 @@ -fedora.setup \ No newline at end of file diff --git a/bots/images/scripts/fedora-atomic.bootstrap b/bots/images/scripts/fedora-atomic.bootstrap deleted file mode 100755 index 44c140c31..000000000 --- a/bots/images/scripts/fedora-atomic.bootstrap +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/bash - -set -e - -url="https://download.fedoraproject.org/pub/alt/atomic/stable/" - -BASE=$(dirname $0) - -# The Fedora URLs have the version twice in the name. for example: -# https://dl.fedoraproject.org/pub/alt/atomic/stable/Fedora-Atomic-28-20180425.0/AtomicHost/x86_64/images/Fedora-AtomicHost-28-20180425.0.x86_64.qcow2 -$BASE/atomic.bootstrap "$1" "$url" \ - sort 3 "Fedora(-atomic)?-[0-9][0-9](-updates)?-([-0-9\.]+)" \ - "AtomicHost" "x86_64" "images" \ - sort 1 "Fedora-AtomicHost-([-0-9\.]+).x86_64.qcow2" diff --git a/bots/images/scripts/fedora-atomic.install b/bots/images/scripts/fedora-atomic.install deleted file mode 100755 index 089f15646..000000000 --- a/bots/images/scripts/fedora-atomic.install +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -set -e - -/var/lib/testvm/atomic.install --verbose --skip cockpit-kdump --extra "/root/rpms/libssh*" "$@" - -# HACK: https://github.com/projectatomic/rpm-ostree/issues/1360 -# rpm-ostree upgrade --check otherwise fails -mkdir -p /var/cache/rpm-ostree diff --git a/bots/images/scripts/fedora-atomic.setup b/bots/images/scripts/fedora-atomic.setup deleted file mode 100755 index c9af470cd..000000000 --- a/bots/images/scripts/fedora-atomic.setup +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -ex - -# HACK: https://bugzilla.redhat.com/show_bug.cgi?id=1341829 -# SELinux breaks coredumping on fedora-25 -printf '(allow init_t domain (process (rlimitinh)))\n' > domain.cil -semodule -i domain.cil - -# HACK: docker falls over regularly, print its log if it does -systemctl start docker || journalctl -u docker - -os=$(ls /ostree/repo/refs/remotes/fedora-atomic/*/) -docker pull "registry.fedoraproject.org/f$os/cockpit" -docker tag "registry.fedoraproject.org/f$os/cockpit" cockpit/ws - - -/var/lib/testvm/atomic.setup diff --git a/bots/images/scripts/fedora-i386.bootstrap b/bots/images/scripts/fedora-i386.bootstrap deleted file mode 100755 index 40e221c98..000000000 --- a/bots/images/scripts/fedora-i386.bootstrap +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2019 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA. - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" i386 "https://dl.fedoraproject.org/pub/fedora-secondary/releases/30/Server/i386/os/" diff --git a/bots/images/scripts/fedora-i386.install b/bots/images/scripts/fedora-i386.install deleted file mode 120000 index a4ccded6a..000000000 --- a/bots/images/scripts/fedora-i386.install +++ /dev/null @@ -1 +0,0 @@ -fedora-30.install \ No newline at end of file diff --git a/bots/images/scripts/fedora-i386.setup b/bots/images/scripts/fedora-i386.setup deleted file mode 120000 index f78434e0c..000000000 --- a/bots/images/scripts/fedora-i386.setup +++ /dev/null @@ -1 +0,0 @@ -fedora.setup \ No newline at end of file diff --git a/bots/images/scripts/fedora-stock.setup b/bots/images/scripts/fedora-stock.setup deleted file mode 100755 index 170761591..000000000 --- a/bots/images/scripts/fedora-stock.setup +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/bash - -useradd -c Administrator -G wheel admin -echo foobar | passwd --stdin admin - -dnf -y update -dnf -y install fedora-release-server -firewall-cmd --permanent --add-service cockpit - -# Phantom can't use TLS.. -sed -i -e 's/ExecStart=.*/\0 --no-tls/' /usr/lib/systemd/system/cockpit.service diff --git a/bots/images/scripts/fedora-testing.bootstrap b/bots/images/scripts/fedora-testing.bootstrap deleted file mode 120000 index 5a5ea46d9..000000000 --- a/bots/images/scripts/fedora-testing.bootstrap +++ /dev/null @@ -1 +0,0 @@ -fedora-30.bootstrap \ No newline at end of file diff --git a/bots/images/scripts/fedora-testing.install b/bots/images/scripts/fedora-testing.install deleted file mode 120000 index a4ccded6a..000000000 --- a/bots/images/scripts/fedora-testing.install +++ /dev/null @@ -1 +0,0 @@ -fedora-30.install \ No newline at end of file diff --git a/bots/images/scripts/fedora-testing.setup b/bots/images/scripts/fedora-testing.setup deleted file mode 120000 index f78434e0c..000000000 --- a/bots/images/scripts/fedora-testing.setup +++ /dev/null @@ -1 +0,0 @@ -fedora.setup \ No newline at end of file diff --git a/bots/images/scripts/fedora.setup b/bots/images/scripts/fedora.setup deleted file mode 100755 index 64e9e6980..000000000 --- a/bots/images/scripts/fedora.setup +++ /dev/null @@ -1,201 +0,0 @@ -#!/bin/bash - -set -ex -IMAGE="$1" - -# avoid failures when running image builds in a non-English locale (ssh transfers the host environment) -unset LANGUAGE -unset LANG -export LC_ALL=C.utf8 - -# keep this in sync with avocado/selenium image mapping in bots/tests-invoke -if [ "$IMAGE" = fedora-30 ]; then - AVOCADO=1 -fi - -# HACK - virt-resize might not be able to resize our xfs rootfs, -# depending on how it was compiled and which plugins are installed, -# and will just silently not do it. So we do it here. -# -if [ "$IMAGE" != fedora-31 ]; then - xfs_growfs / -fi -df -h / - -echo foobar | passwd --stdin root - -HAVE_KUBERNETES= -if [ $(uname -m) = x86_64 ]; then - HAVE_KUBERNETES=1 -fi - -# HACK docker not available on f31 -# https://github.com/cockpit-project/cockpit/issues/12670 -HAVE_DOCKER= -if [ "$1" != fedora-31 ]; then - HAVE_DOCKER=1 -fi - -# We install all dependencies of the cockpit packages since we want -# them to not spontaneously change from one test run to the next when -# the distribution repository is updated. -# -COCKPIT_DEPS="\ -device-mapper-multipath \ -etcd \ -glibc-all-langpacks \ -glib-networking \ -grubby \ -json-glib \ -kexec-tools \ -libssh \ -libvirt-daemon-kvm \ -libvirt-client \ -libvirt-dbus \ -NetworkManager-team \ -openssl \ -PackageKit \ -pcp \ -pcp-libs \ -qemu \ -realmd \ -selinux-policy-targeted \ -setroubleshoot-server \ -sos \ -sscg \ -system-logos \ -subscription-manager \ -tuned \ -virt-install \ -" - -[ -z "$HAVE_DOCKER" ] || COCKPIT_DEPS="$COCKPIT_DEPS atomic docker" - -COCKPIT_DEPS="$COCKPIT_DEPS udisks2 udisks2-lvm2 udisks2-iscsi" - -[ -z "$HAVE_KUBERNETES" ] || COCKPIT_DEPS="$COCKPIT_DEPS kubernetes" - -# We also install the packages necessary to join a FreeIPA domain so -# that we don't have to go to the network during a test run. -# -IPA_CLIENT_PACKAGES="\ -freeipa-client \ -oddjob \ -oddjob-mkhomedir \ -sssd \ -sssd-dbus \ -libsss_sudo \ -" - -TEST_PACKAGES="\ -systemtap-runtime-virtguest \ -valgrind \ -gdb \ -targetcli \ -dnf-automatic \ -cryptsetup \ -clevis-luks \ -socat \ -tang \ -podman \ -ntp \ -libvirt-daemon-config-network \ -" - -# HACK - For correct work of ABRT in Fedora 26 Alpha release a following -# packages are necessary. In Fedora 26 Beta and later these packages should be -# installed by default. See https://bugzilla.redhat.com/show_bug.cgi?id=1436941 -# -ABRT_PACKAGES="\ -abrt-desktop \ -libreport-plugin-systemd-journal \ -" - -rm -rf /etc/sysconfig/iptables - -maybe() { if type "$1" >/dev/null 2>&1; then "$@"; fi; } - -# For the D-Bus test server -maybe firewall-cmd --permanent --add-port 8765/tcp - -echo 'NETWORKING=yes' > /etc/sysconfig/network - -useradd -c Administrator -G wheel admin -echo foobar | passwd --stdin admin - -if [ "${IMAGE%-i386}" != "$IMAGE" ]; then - TEST_PACKAGES="${TEST_PACKAGES/podman /}" -fi - -if [ "${IMAGE%-testing}" != "$IMAGE" ]; then - dnf config-manager --set-enabled updates-testing -fi - -dnf $DNF_OPTS -y upgrade -dnf $DNF_OPTS -y install $TEST_PACKAGES $COCKPIT_DEPS $IPA_CLIENT_PACKAGES $ABRT_PACKAGES - -if [ -n "$AVOCADO" ]; then - - # enable python3 avocado support repository - dnf module install -y avocado:69lts - - dnf $DNF_OPTS -y install \ - fontconfig \ - npm \ - chromium-headless \ - python3-libvirt \ - python3-avocado \ - python3-avocado-plugins-output-html \ - python3-selenium - - npm -g install chrome-remote-interface - echo 'NODE_PATH=/usr/lib/node_modules' >> /etc/environment -fi - -dnf $DNF_OPTS -y install mock dnf-plugins-core rpm-build -useradd -c Builder -G mock builder - -if [ "${IMAGE%-testing}" != "$IMAGE" ]; then - # Enable updates-testing in mock - echo "config_opts['yum.conf'] += '[updates-testing]\nenabled=1'" >>/etc/mock/default.cfg -fi - -# HACK - mock --installdeps is broken, it seems that it forgets to -# copy the source rpm to a location that dnf can actually access. A -# workaround is to pass "--no-bootstrap-chroot". -# -# When you remove this hack, also remove it in fedora-*.install. -# -# https://bugzilla.redhat.com/show_bug.cgi?id=1447627 - -opsys=$(cut -d '-' -f 1 <<< "$IMAGE") -version=$(cut -d '-' -f 2 <<< "$IMAGE") -# If version is not number (testing/i386) then use Fedora 30 -if ! [ "$version" -eq "$version" ] 2>/dev/null; then version=30; fi - -su builder -c "/usr/bin/mock --no-bootstrap-chroot --verbose -i $(/var/lib/testvm/build-deps.sh "$opsys $version")" -su builder -c "/usr/bin/mock --install --verbose rpmlint" - -if [ -n "$HAVE_DOCKER" ]; then - # HACK: docker falls over regularly, print its log if it does - systemctl start docker || journalctl -u docker - # docker images that we need for integration testing - /var/lib/testvm/docker-images.setup -fi - -# Configure kubernetes -[ -z "$HAVE_KUBERNETES" ] || /var/lib/testvm/kubernetes.setup - -# reduce image size -dnf clean all -/var/lib/testvm/zero-disk.setup --keep-mock-cache - -ln -sf ../selinux/config /etc/sysconfig/selinux -printf "SELINUX=enforcing\nSELINUXTYPE=targeted\n" > /etc/selinux/config - -# Prevent SSH from hanging for a long time when no external network access -echo 'UseDNS no' >> /etc/ssh/sshd_config - -# Audit events to the journal -rm -f '/etc/systemd/system/multi-user.target.wants/auditd.service' -rm -rf /var/log/audit/ diff --git a/bots/images/scripts/ipa.bootstrap b/bots/images/scripts/ipa.bootstrap deleted file mode 120000 index 0de9144e8..000000000 --- a/bots/images/scripts/ipa.bootstrap +++ /dev/null @@ -1 +0,0 @@ -fedora-29.bootstrap \ No newline at end of file diff --git a/bots/images/scripts/ipa.setup b/bots/images/scripts/ipa.setup deleted file mode 100755 index 6a06aca8f..000000000 --- a/bots/images/scripts/ipa.setup +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -set -eufx - -# ipa requires an UTF-8 locale -export LC_ALL=C.UTF-8 - -echo foobar | passwd --stdin root - -dnf -y remove firewalld -dnf -y update -dnf -y install freeipa-server freeipa-server-dns bind bind-dyndb-ldap iptables - -iptables -F - -nmcli con add con-name "static-eth1" ifname eth1 type ethernet ip4 "10.111.112.100/20" ipv4.dns "10.111.112.100" gw4 "10.111.112.1" -nmcli con up "static-eth1" -hostnamectl set-hostname f0.cockpit.lan - -# Let's make sure that ipa-server-install doesn't block on -# /dev/random. -# -rm -f /dev/random -ln -s /dev/urandom /dev/random - -ipa-server-install -U -p foobarfoo -a foobarfoo -n cockpit.lan -r COCKPIT.LAN --setup-dns --no-forwarders - -# Make sure any initial password change is overridden -printf 'foobarfoo\nfoobarfoo\nfoobarfoo\n' | kinit admin@COCKPIT.LAN - -# Default password expiry of 90 days is impractical -ipa pwpolicy-mod --minlife=0 --maxlife=1000 -# Change password to apply new password policy -printf 'foobarfoo\nfoobarfoo\n' | ipa user-mod --password admin -ipa user-show --all admin - -# Allow "admins" IPA group members to run sudo -# This is an "unbreak my setup" step and ought to happen by default. -# See https://pagure.io/freeipa/issue/7538 -ipa-advise enable-admins-sudo | sh -ex - -ipa dnsconfig-mod --forwarder=8.8.8.8 - -ln -sf ../selinux/config /etc/sysconfig/selinux -echo 'SELINUX=permissive' > /etc/selinux/config - -# reduce image size -dnf clean all -/var/lib/testvm/zero-disk.setup diff --git a/bots/images/scripts/lib/atomic.install b/bots/images/scripts/lib/atomic.install deleted file mode 100755 index 26caea00d..000000000 --- a/bots/images/scripts/lib/atomic.install +++ /dev/null @@ -1,303 +0,0 @@ -#!/usr/bin/python2 - -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import subprocess -import os -import sys -import shutil -try: - from urllib.request import URLopener -except ImportError: - from urllib import URLopener # Python 2 -import argparse -import json - -BASEDIR = os.path.dirname(__file__) - -class AtomicCockpitInstaller: - branch = None - checkout_location = "/var/local-tree" - repo_location = "/var/local-repo" - rpm_location = "/usr/share/rpm" - key_id = "95A8BA1754D0E95E2B3A98A7EE15015654780CBD" - port = 12345 - - # Support installing random packages if needed. - external_packages = {} - - # Temporarily force cockpit-system instead of cockpit-shell - packages_force_install = [ "cockpit-system", - "cockpit-docker", - "cockpit-kdump", - "cockpit-networkmanager", - "cockpit-sosreport" ] - - def __init__(self, rpms=None, extra_rpms=None, verbose=False): - self.verbose = verbose - self.rpms = rpms - self.extra_rpms = extra_rpms - status = json.loads(subprocess.check_output(["rpm-ostree", "status", "--json"], universal_newlines=True)) - origin = None - for deployment in status.get("deployments", []): - if deployment.get("booted"): - origin = deployment["origin"] - - if not origin: - raise Exception("Couldn't find origin") - - self.branch = origin.split(":", 1)[-1] - - def setup_dirs(self): - if self.verbose: - print("setting up new ostree repo") - - try: - shutil.rmtree(self.repo_location) - except: - pass - - os.makedirs(self.repo_location) - subprocess.check_call(["ostree", "init", "--repo", self.repo_location, - "--mode", "archive-z2"]) - - if not os.path.exists(self.checkout_location): - if self.verbose: - print("cloning current branch") - - subprocess.check_call(["ostree", "checkout", self.branch, - self.checkout_location]) - - # move /usr/etc to /etc, makes rpm installs easier - subprocess.check_call(["mv", os.path.join(self.checkout_location, "usr", "etc"), - os.path.join(self.checkout_location, "etc")]) - - def switch_to_local_tree(self): - if self.verbose: - print("install new ostree commit") - - # Not an error if this fails - subprocess.call(["ostree", "remote", "delete", "local"]) - - subprocess.check_call(["ostree", "remote", "add", "local", - "file://{}".format(self.repo_location), - "--no-gpg-verify"]) - - # HACK: https://github.com/candlepin/subscription-manager/issues/1404 - subprocess.call(["systemctl", "disable", "rhsmcertd"]) - subprocess.call(["systemctl", "stop", "rhsmcertd"]) - - status = subprocess.check_output(["rpm-ostree", "status"]) - if b"local:" in status: - subprocess.check_call(["rpm-ostree", "upgrade"]) - else: - try: - subprocess.check_call(["setenforce", "0"]) - subprocess.check_call(["rpm-ostree", "rebase", - "local:{0}".format(self.branch)]) - except: - os.system("sysctl kernel.core_pattern") - os.system("coredumpctl || true") - raise - finally: - subprocess.check_call(["setenforce", "1"]) - - def commit_to_repo(self): - if self.verbose: - print("commit package changes to our repo") - - # move etc back to /usr/etc - subprocess.check_call(["mv", os.path.join(self.checkout_location, "etc"), - os.path.join(self.checkout_location, "usr", "etc")]) - - subprocess.check_call(["ostree", "commit", "-s", "cockpit-tree", - "--repo", self.repo_location, - "-b", self.branch, - "--add-metadata-string", "version=cockpit-base.1", - "--tree=dir={0}".format(self.checkout_location), - "--gpg-sign={0}".format(self.key_id), - "--gpg-homedir={0}".format(BASEDIR)]) - - def install_packages(self, packages, deps=True, replace=False): - args = ["rpm", "-U", "--root", self.checkout_location, - "--dbpath", self.rpm_location] - - if replace: - args.extend(["--replacepkgs", "--replacefiles"]) - - if not deps: - args.append("--nodeps") - - for package in packages: - args.append(os.path.abspath(os.path.join(os.getcwd(), package))) - - subprocess.check_call(args) - - def remove_packages(self, packages): - args = ["rpm", "-e", "--root", self.checkout_location, - "--dbpath", self.rpm_location] - args.extend(packages) - subprocess.check_call(args) - - def package_basename(self, package): - """ only accept package with the name 'cockpit-%s-*' and return 'cockpit-%s' or None""" - basename = "-".join(package.split("-")[:2]) - if basename.startswith("cockpit-"): - return basename - else: - return None - - def update_container(self): - """ Install the latest cockpit RPMs in our container""" - rpm_args = [] - for package in self.rpms: - if 'cockpit-ws' in package or 'cockpit-dashboard' in package or 'cockpit-bridge' in package: - rpm_args.append("/host" + package) - extra_args = [] - for package in self.extra_rpms: - extra_args.append("/host" + package) - - if rpm_args: - subprocess.check_call(["docker", "run", "--name", "build-cockpit", - "-d", "--privileged", "-v", "/:/host", - "cockpit/ws", "sleep", "1d"]) - if self.verbose: - print("updating cockpit-ws container") - - if extra_args: - subprocess.check_call(["docker", "exec", "build-cockpit", - "rpm", "--install", "--verbose", "--force"] + extra_args) - - subprocess.check_call(["docker", "exec", "build-cockpit", - "rpm", "--freshen", "--verbose", "--force"] + rpm_args) - - # if we update the RPMs, also update the scripts, to keep them in sync - subprocess.check_call(["docker", "exec", "build-cockpit", "sh", "-exc", - "cp /host/var/tmp/containers/ws/atomic-* /container/"]) - - subprocess.check_call(["docker", "commit", "build-cockpit", - "cockpit/ws"]) - subprocess.check_call(["docker", "kill", "build-cockpit"]) - subprocess.check_call(["docker", "rm", "build-cockpit"]) - - def package_basenames(self, package_names): - """ convert a list of package names to a list of their basenames """ - return list(filter(lambda s: s is not None, map(self.package_basename, package_names))) - - def get_installed_cockpit_packages(self): - """ get list installed cockpit packages """ - packages = subprocess.check_output("rpm -qa | grep cockpit", shell=True, universal_newlines=True) - - if self.verbose: - print("installed packages: {0}".format(packages)) - - installed_packages = packages.strip().split("\n") - return installed_packages - - def clean_network(self): - if self.verbose: - print("clean network configuration:") - subprocess.check_call(["rm", "-rf", "/var/lib/NetworkManager"]) - subprocess.check_call(["rm", "-rf", "/var/lib/dhcp"]) - - def run(self): - # Delete previous deployment if it's present - output = subprocess.check_output(["ostree", "admin", "status"]) - if output.count(b"origin refspec") != 1: - subprocess.check_call(["ostree", "admin", "undeploy", "1"]) - - self.setup_dirs() - - installed_packages = self.get_installed_cockpit_packages() - self.remove_packages(installed_packages) - - packages_to_install = self.package_basenames(installed_packages) - for p in self.packages_force_install: - if not p in packages_to_install: - if self.verbose: - print("adding package %s (forced)" % (p)) - packages_to_install.append(p) - - packages_to_install = list(filter(lambda p: any(os.path.split(p)[1].startswith(base) for base in packages_to_install), self.rpms)) - - if self.verbose: - print("packages to install:") - print(packages_to_install) - - if self.external_packages: - names = self.external_packages.keys() - if self.verbose: - print("external packages to install:") - print(list(names)) - - downloader = URLopener() - for name, url in self.external_packages.items(): - downloader.retrieve(url, name) - - self.install_packages(names, replace=True) - - for name in names: - os.remove(name) - - self.install_packages(packages_to_install) - no_deps = [x for x in self.rpms \ - if os.path.split(x)[-1].startswith("cockpit-tests") or - os.path.split(x)[-1].startswith("cockpit-machines")] - self.install_packages(no_deps, deps=False, replace=True) - - # If firewalld is installed, we need to poke a hole for cockpit, so - # that we can run firewall tests on it (change firewall-cmd to - # --add-service=cockpit once all supported atomics ship with the - # service file) - if subprocess.call(["systemctl", "enable", "--now", "firewalld"]) == 0: - subprocess.call(["firewall-cmd", "--permanent", "--add-port=9090/tcp"]) - - self.commit_to_repo() - self.switch_to_local_tree() - self.update_container() - self.clean_network() - -parser = argparse.ArgumentParser(description='Install Cockpit in Atomic') -parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose progress details') -parser.add_argument('-q', '--quick', action='store_true', help='Build faster') -parser.add_argument('--build', action='store_true', help='Build') -parser.add_argument('--install', action='store_true', help='Install') -parser.add_argument('--extra', action='append', default=[], help='Extra packages to install inside the container') -parser.add_argument('--skip', action='append', default=[], help='Packes to skip during installation') -args = parser.parse_args() - -if args.build: - sys.stderr.write("Can't build on Atomic\n") - sys.exit(1) - -if args.install: - os.chdir("build-results") - # Force skip cockpit-dashboard - if args.skip: - skip = list(args.skip) - else: - skip = [] - skip.append("cockpit-dashboard") - - rpms = [os.path.abspath(f) for f in os.listdir(".") - if (f.endswith(".rpm") and not f.endswith(".src.rpm") - and not any(f.startswith(s) for s in args.skip))] - cockpit_installer = AtomicCockpitInstaller(rpms=rpms, extra_rpms=args.extra, verbose=args.verbose) - cockpit_installer.run() - -# vim: ft=python diff --git a/bots/images/scripts/lib/atomic.setup b/bots/images/scripts/lib/atomic.setup deleted file mode 100755 index 4deb2d8ef..000000000 --- a/bots/images/scripts/lib/atomic.setup +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash - -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -set -ex - -# The docker pool should grow automatically as needed, but we grow it -# explicitly here anyway. This is hopefully more reliable. -# Newer Fedora versions configure docker to use the root LV -# HACK: docker falls over regularly, print its log if it does -systemctl start docker || journalctl -u docker -lvresize atomicos/root -l+60%FREE -r -if lvs atomicos/docker-pool 2>/dev/null; then - lvresize atomicos/docker-pool -l+100%FREE -elif lvs atomicos/docker-root-lv; then - lvresize atomicos/docker-root-lv -l+100%FREE -fi - -# docker images that we need for integration testing -/var/lib/testvm/docker-images.setup - -# Download the libssh RPM plus dependencies which we'll use for -# package overlay. The only way to do this is via a container -. /etc/os-release -REPO="updates" -if [ "$ID" = "rhel" ]; then - subscription-manager repos --enable rhel-7-server-extras-rpms - REPO="rhel-7-server-extras-rpms" - ID="rhel7" -fi -docker run --rm --volume=/etc/yum.repos.d:/etc/yum.repos.d:z --volume=/root/rpms:/tmp/rpms:rw,z "$ID:$VERSION_ID" /bin/sh -cex "yum install -y findutils createrepo yum-utils && (cd /tmp/; yumdownloader --enablerepo=$REPO libssh) && find /tmp -name '*.$(uname -m).*rpm' | while read rpm; do mv -v \$rpm /tmp/rpms; done; createrepo /tmp/rpms" -rm -f /etc/yum.repos.d/* -cat >/etc/yum.repos.d/deps.repo <> /etc/ssh/sshd_config - -# Final tweaks -rm -rf /var/log/journal/* diff --git a/bots/images/scripts/lib/base/Dockerfile b/bots/images/scripts/lib/base/Dockerfile deleted file mode 100644 index 6cbf9c7d9..000000000 --- a/bots/images/scripts/lib/base/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM fedora:30 - -ADD setup.sh /setup.sh - -RUN /setup.sh diff --git a/bots/images/scripts/lib/base/README.md b/bots/images/scripts/lib/base/README.md deleted file mode 100644 index cbbde7b22..000000000 --- a/bots/images/scripts/lib/base/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Cockpit Base -=========================== - -Simple base container that installs cockpit-ws dependencies. Used in testing -and development to speed up container build times. diff --git a/bots/images/scripts/lib/base/setup.sh b/bots/images/scripts/lib/base/setup.sh deleted file mode 100755 index a9c127b98..000000000 --- a/bots/images/scripts/lib/base/setup.sh +++ /dev/null @@ -1,26 +0,0 @@ -#! /bin/sh - -upgrade() { - # https://bugzilla.redhat.com/show_bug.cgi?id=1483553 - dnf -v -y update 2>err.txt - ecode=$? - if [ $ecode -ne 0 ] ; then - grep -q -F -e "BDB1539 Build signature doesn't match environment" err.txt - if [ $? -eq 0 ]; then - set -eu - rpm --rebuilddb - dnf -v -y update - else - cat err.txt - exit ${ecode} - fi - fi -} - -upgrade - -set -eu - -dnf install -y sed findutils glib-networking json-glib libssh openssl python3 - -dnf clean all diff --git a/bots/images/scripts/lib/build-deps.sh b/bots/images/scripts/lib/build-deps.sh deleted file mode 100755 index 045562e89..000000000 --- a/bots/images/scripts/lib/build-deps.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -eu - -# Download cockpit.spec, replace `npm-version` macro and then query all build requires -curl -s https://raw.githubusercontent.com/cockpit-project/cockpit/master/tools/cockpit.spec | - sed 's/%{npm-version:.*}/0/' | - sed '/Recommends:/d' | - rpmspec -D "$1" --buildrequires --query /dev/stdin | - sed 's/.*/"&"/' | - tr '\n' ' ' - -# support for backbranches -if [ "$1" = "rhel 7" ] || [ "$1" = "centos 7" ]; then - echo "golang-bin golang-src" -fi diff --git a/bots/images/scripts/lib/containers.install b/bots/images/scripts/lib/containers.install deleted file mode 100755 index 9c84d7010..000000000 --- a/bots/images/scripts/lib/containers.install +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# This file is part of Cockpit. -# -# Copyright (C) 2016 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . -set -ex - -# HACK: docker falls over regularly, print its log if it does -systemctl start docker || journalctl -u docker - -for NAME in bastion -do - mkdir -p "/var/tmp/containers/$NAME/rpms" - cp -f /var/tmp/build-results/*.rpm "/var/tmp/containers/$NAME/rpms/" - cd "/var/tmp/containers/$NAME/" - sed -i -e "s#FROM .*#FROM cockpit/base#" Dockerfile - docker build --build-arg OFFLINE=1 -t "cockpit/$NAME" . 1>&2; - rm -r "/var/tmp/containers/$NAME/rpms" -done - -journalctl --flush || true -journalctl --sync || killall systemd-journald || true -rm -rf /var/log/journal/* || true diff --git a/bots/images/scripts/lib/debian.bootstrap b/bots/images/scripts/lib/debian.bootstrap deleted file mode 100755 index 0b2cac021..000000000 --- a/bots/images/scripts/lib/debian.bootstrap +++ /dev/null @@ -1,13 +0,0 @@ -#! /bin/bash - -set -ex - -out=$1 - -# download cloud image; re-use a previously downloaded image -image_url="$2" -image=tmp/$(basename $image_url) -mkdir -p $(dirname $image) -[ -f "$image" ] || curl -o "$image" "$image_url" -cp "$image" "$out" -qemu-img resize -f qcow2 "$out" +8G diff --git a/bots/images/scripts/lib/debian.install b/bots/images/scripts/lib/debian.install deleted file mode 100755 index c1b5ce3bd..000000000 --- a/bots/images/scripts/lib/debian.install +++ /dev/null @@ -1,97 +0,0 @@ -#! /bin/sh - -set -ex - -export DEB_BUILD_OPTIONS="" - -do_build= -do_install= -stdout_dest="/dev/null" -args=$(getopt -o "vqs:" -l "verbose,quick,skip:,build,install" -- "$@") -eval set -- "$args" -while [ $# -gt 0 ]; do - case $1 in - -v|--verbose) - stdout_dest="/dev/stdout" - ;; - -q|--quick) - DEB_BUILD_OPTIONS="$DEB_BUILD_OPTIONS nocheck" - ;; - --build) - do_build=t - ;; - --install) - do_install=t - ;; - --) - shift - break - ;; - esac - shift -done -tar="$1" - - -# Build - -if [ -n "$do_build" ]; then - rm -rf build-results - mkdir build-results - resultdir=$PWD/build-results - upstream_ver=$(ls cockpit-*.tar.gz | sed 's/^.*-//; s/.tar.gz//' | head -n1) - - ln -sf cockpit-*.tar.gz cockpit_${upstream_ver}.orig.tar.gz - - rm -rf cockpit-*/ - tar -xzf cockpit-*.tar.gz - ( cd cockpit-*/ - cp -rp tools/debian debian - # put proper version into changelog, as we have versioned dependencies - sed -i "1 s/(.*)/($upstream_ver-1)/" debian/changelog - # Hack: Remove PCP build dependencies while pcp is not in testing - # (https://tracker.debian.org/pcp) - sed -i '/libpcp.*-dev/d' debian/control - dpkg-buildpackage -S -uc -us -nc - ) - - # Some unit tests want a real network interface - echo USENETWORK=yes >>~/.pbuilderrc - - # pbuilder < 0.228.6 has broken /dev/pts/ptmx permissions; affects Ubuntu < 17.04 - # see https://bugs.debian.org/841935 - if ! grep -q ptmxmode /usr/lib/pbuilder/pbuilder-modules; then - echo "Fixing /dev/pts/ptmx mode in pbuilder" - sed -i '/mount -t devpts none/ s/$/,ptmxmode=666,newinstance/' /usr/lib/pbuilder/pbuilder-modules - fi - - pbuilder build --buildresult "$resultdir" \ - --logfile "$resultdir/build.log" \ - cockpit_${upstream_ver}-1.dsc >$stdout_dest - lintian $resultdir/cockpit_*_$(dpkg --print-architecture).changes >&2 -fi - -# Install - -if [ -n "$do_install" ]; then - packages=$(find build-results -name "*.deb") - dpkg --install $packages - - # FIXME: our tests expect cockpit.socket to not be running after boot, only - # after start_cockpit(). - systemctl disable cockpit.socket - - # HACK: tuned breaks QEMU (https://launchpad.net/bugs/1774000) - systemctl disable tuned.service 2>/dev/null || true - - # avoid random dpkg database locks, they break our package related tests - systemctl disable apt-daily-upgrade.timer - - firewall-cmd --add-service=cockpit --permanent - # not managed by NM, so enable interface manually - firewall-cmd --zone=public --permanent --add-interface=eth1 - - journalctl --flush - journalctl --sync || killall systemd-journald - rm -rf /var/log/journal/* -fi diff --git a/bots/images/scripts/lib/docker-images.setup b/bots/images/scripts/lib/docker-images.setup deleted file mode 100755 index 79c7af73e..000000000 --- a/bots/images/scripts/lib/docker-images.setup +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -set -ex - -# This file is part of Cockpit. -# -# Copyright (C) 2016 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -if [ $(uname -m) = x86_64 ]; then - docker pull busybox:latest - docker pull busybox:buildroot-2014.02 - docker pull gcr.io/google_containers/pause:0.8.0 - docker pull k8s.gcr.io/pause-amd64:3.1 - # some aliases for different k8s variants - docker tag k8s.gcr.io/pause-amd64:3.1 gcr.io/google_containers/pause-amd64:3.0 - docker tag k8s.gcr.io/pause-amd64:3.1 k8s.gcr.io/pause:3.1 -fi - -# Download the i386 image and rename it -if [ $(uname -m) = i686 ]; then - docker pull i386/busybox:latest - docker tag docker.io/i386/busybox busybox - docker rmi docker.io/i386/busybox -fi diff --git a/bots/images/scripts/lib/fedora.install b/bots/images/scripts/lib/fedora.install deleted file mode 100755 index 811796dae..000000000 --- a/bots/images/scripts/lib/fedora.install +++ /dev/null @@ -1,120 +0,0 @@ -#! /bin/bash - -set -ex - -# don't update already installed cockpit packages -installed=$(rpm --query --all --queryformat "%{NAME}-\[0-9\]\n" "cockpit*") -skip="cockpit-doc-[0-9]" -if [ -n "$installed" ]; then - skip="$skip -$installed" -fi - -do_build= -do_install= -# we build RHEL 7.x in a CentOS mock, thus we can't parse os-release in the .spec -mock_opts="--define='os_version_id $(. /etc/os-release; echo $VERSION_ID)'" -args=$(getopt -o "vqs:" -l "verbose,quick,skip:,build,install,rhel,HACK-no-bootstrap-chroot" -- "$@") -eval set -- "$args" -while [ $# -gt 0 ]; do - case $1 in - -v|--verbose) - mock_opts="$mock_opts --verbose" - ;; - -q|--quick) - mock_opts="$mock_opts --nocheck --define='selinux 0'" - ;; - -s|--skip) - skip="$skip -$2" - shift - ;; - --build) - do_build=t - ;; - --install) - do_install=t - ;; - --rhel) - # For RHEL we actually build in EPEL, which is based - # on CentOS. On CentOS, the spec file has both - # %centos and %rhel defined, but it gives precedence - # to %centos, as it must. To make it produce the RHEL - # packages, we explicitly undefine %centos here. - mock_opts="$mock_opts --define='centos 0'" - ;; - --HACK-no-bootstrap-chroot) - mock_opts="$mock_opts --no-bootstrap-chroot" - ;; - --) - shift - break - ;; - esac - shift -done -tar=$1 - -# Build - -if [ -n "$do_build" ]; then - # Some tests need a non-loopback internet address, so we allow - # networking during build. Note that we use "--offline" below, so - # we should still be protected against unexpected package - # installations. - echo "config_opts['rpmbuild_networking'] = True" >>/etc/mock/site-defaults.cfg - # don't destroy the mock after building, we want to run rpmlint - echo "config_opts['cleanup_on_success'] = False" >>/etc/mock/site-defaults.cfg - # HACK: don't fall over on unavailable repositories, as we are offline - # (https://bugzilla.redhat.com/show_bug.cgi?id=1549291) - sed --follow-symlinks -i '/skip_if_unavailable=False/d' /etc/mock/default.cfg - - rm -rf build-results - srpm=$(/var/lib/testvm/make-srpm "$tar") - LC_ALL=C.UTF-8 su builder -c "/usr/bin/mock --offline --no-clean --resultdir build-results $mock_opts --rebuild $srpm" - - cat </tmp/rpmlint -#! /bin/bash -rm -rf /builddir/build -if type rpmlint >/dev/null 2>&1; then - # blacklist "E: no-changelogname-tag" rpmlint error, expected due to our template cockpit.spec - mkdir -p ~/.config - echo 'addFilter("E: no-changelogname-tag")' > ~/.config/rpmlint - # we expect the srpm to be clean - echo - echo '====== rpmlint on srpm =====' - rpmlint /builddir/build/SRPMS/*.src.rpm - # this still has lots of errors, run it for information only - echo - echo '====== rpmlint binary rpms (advisory) =====' - rpmlint /builddir/build/RPMS/ || true -else - echo '====== skipping rpmlint check, not installed =====' -fi -EOF - chmod +x /tmp/rpmlint - su builder -c "/usr/bin/mock --offline --copyin /tmp/rpmlint /var/tmp/rpmlint" - su builder -c "/usr/bin/mock --offline --shell /var/tmp/rpmlint" -fi - -# Install - -if [ -n "$do_install" ]; then - packages=$(find build-results -name "*.rpm" -not -name "*.src.rpm" | grep -vG "$skip") - rpm -U --force $packages - - if type firewall-cmd > /dev/null 2> /dev/null; then - systemctl start firewalld - firewall-cmd --add-service=cockpit --permanent - fi - - # Make sure we clean out the journal - journalctl --flush - journalctl --sync || killall systemd-journald - rm -rf /var/log/journal/* - rm -rf /var/lib/NetworkManager/dhclient-*.lease -fi - -if [ -n "$do_build" ]; then - su builder -c "/usr/bin/mock --clean" -fi diff --git a/bots/images/scripts/lib/kubernetes.setup b/bots/images/scripts/lib/kubernetes.setup deleted file mode 100755 index 02badd57f..000000000 --- a/bots/images/scripts/lib/kubernetes.setup +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -# Kubernetes is delivered in a non-functional state on Fedora and similar operating systems -# The following commands are needed to get it running. - -cd /etc/kubernetes/ - -cat < openssl.conf -oid_section = new_oids -[new_oids] -[req] -encrypt_key = no -string_mask = nombstr -req_extensions = v3_req -distinguished_name = v3_name -[v3_name] -commonName = kubernetes -[v3_req] -basicConstraints = CA:FALSE -subjectAltName = @alt_names -[alt_names] -DNS.1 = kubernetes -DNS.2 = kubernetes.default -DNS.3 = kubernetes.default.svc -DNS.4 = kubernetes.default.svc.cluster.local -IP.1 = 127.0.0.1 -IP.2 = 10.254.0.1 -EOF - -openssl genrsa -out ca.key 2048 -openssl req -x509 -new -nodes -key ca.key -days 3072 -out ca.crt -subj '/CN=kubernetes' -openssl genrsa -out server.key 2048 -openssl req -config openssl.conf -new -key server.key -out server.csr -subj '/CN=kubernetes' -openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3072 -extensions v3_req -extfile openssl.conf -# make keys readable for "kube" group and thus for kube-apiserver.service on newer OSes -if getent group kube >/dev/null; then - chgrp kube ca.key server.key - chmod 640 ca.key server.key -fi - -echo -e '{"user":"admin"}\n{"user":"scruffy","readonly": true}' > /etc/kubernetes/authorization -echo -e 'fubar,admin,10101\nscruffy,scruffy,10102' > /etc/kubernetes/passwd - -echo 'KUBE_API_ARGS="--service-account-key-file=/etc/kubernetes/server.key --client-ca-file=/etc/kubernetes/ca.crt --tls-cert-file=/etc/kubernetes/server.crt --tls-private-key-file=/etc/kubernetes/server.key --basic-auth-file=/etc/kubernetes/passwd --authorization-mode=ABAC --authorization-policy-file=/etc/kubernetes/authorization"' >> apiserver -echo 'KUBE_CONTROLLER_MANAGER_ARGS="--root-ca-file=/etc/kubernetes/ca.crt --service-account-private-key-file=/etc/kubernetes/server.key"' >> controller-manager - diff --git a/bots/images/scripts/lib/make-srpm b/bots/images/scripts/lib/make-srpm deleted file mode 100755 index b49ad2691..000000000 --- a/bots/images/scripts/lib/make-srpm +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -eu - -tar=$1 - -version=$(echo "$1" | sed -n 's|.*cockpit-\([^ /-]\+\)\.tar\..*|\1|p') -if [ -z "$version" ]; then - echo "make-srpm: couldn't parse version from tarball: $1" - exit 2 -fi - -# We actually modify the spec so that the srpm is standalone buildable -modify_spec() { -sed -e "/^Version:.*/d" -e "1i\ -%define wip wip\nVersion: $version\n" -} - -tmpdir=$(mktemp -d $PWD/srpm-build.XXXXXX) -tar xaf "$1" -O cockpit-$version/tools/cockpit.spec | modify_spec > $tmpdir/cockpit.spec - -rpmbuild -bs \ - --quiet \ - --define "_sourcedir $(dirname $1)" \ - --define "_specdir $tmpdir" \ - --define "_builddir $tmpdir" \ - --define "_srcrpmdir `pwd`" \ - --define "_rpmdir $tmpdir" \ - --define "_buildrootdir $tmpdir/.build" \ - $tmpdir/cockpit.spec - -rpm --qf '%{Name}-%{Version}-%{Release}.src.rpm\n' -q --specfile $tmpdir/cockpit.spec | head -n1 -rm -rf $tmpdir diff --git a/bots/images/scripts/lib/pubring.gpg b/bots/images/scripts/lib/pubring.gpg deleted file mode 100644 index 4d14e2e9a..000000000 Binary files a/bots/images/scripts/lib/pubring.gpg and /dev/null differ diff --git a/bots/images/scripts/lib/secring.gpg b/bots/images/scripts/lib/secring.gpg deleted file mode 100644 index 56e227da1..000000000 Binary files a/bots/images/scripts/lib/secring.gpg and /dev/null differ diff --git a/bots/images/scripts/lib/zero-disk.setup b/bots/images/scripts/lib/zero-disk.setup deleted file mode 100755 index 888b63c3e..000000000 --- a/bots/images/scripts/lib/zero-disk.setup +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -# This file is part of Cockpit. -# -# Copyright (C) 2016 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# We don't want to delete the pbuilder caches since we need them during build. -# Mock with --offline and dnf is sometimes happy without caches, and with yum it -# never is, so we provide an option to also leave the mock caches in place. -# -# We also want to keep cracklib since otherwise password quality -# checks break on Debian. - -if [ -f /root/.skip-zero-disk ]; then - echo "Skipping zero-disk.setup as /root/.skip-zero-disk exists" - exit 0 -fi - -keep="! -path /var/cache/pbuilder ! -path /var/cache/cracklib ! -path /var/cache/tomcat" -while [ $# -gt 0 ]; do - case $1 in - --keep-mock-cache) - keep="$keep ! -path /var/cache/mock" - ;; - esac - shift -done - -if [ -d "/var/cache" ]; then - find /var/cache/* -maxdepth 0 -depth -name "*" $keep -exec rm -rf {} \; -fi -rm -rf /var/tmp/* -rm -rf /var/log/journal/* - -dd if=/dev/zero of=/root/junk || true -sync -rm -f /root/junk diff --git a/bots/images/scripts/network-ifcfg-eth0 b/bots/images/scripts/network-ifcfg-eth0 deleted file mode 100644 index 76478d955..000000000 --- a/bots/images/scripts/network-ifcfg-eth0 +++ /dev/null @@ -1,3 +0,0 @@ -BOOTPROTO="dhcp" -DEVICE="eth0" -ONBOOT="yes" diff --git a/bots/images/scripts/network-ifcfg-eth1 b/bots/images/scripts/network-ifcfg-eth1 deleted file mode 100644 index f085b24fb..000000000 --- a/bots/images/scripts/network-ifcfg-eth1 +++ /dev/null @@ -1,3 +0,0 @@ -BOOTPROTO="none" -DEVICE="eth1" -ONBOOT="no" diff --git a/bots/images/scripts/openshift.bootstrap b/bots/images/scripts/openshift.bootstrap deleted file mode 100755 index c6b2918d0..000000000 --- a/bots/images/scripts/openshift.bootstrap +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "http://mirror.centos.org/centos/7/os/x86_64/" diff --git a/bots/images/scripts/openshift.install b/bots/images/scripts/openshift.install deleted file mode 100755 index 7db5af86b..000000000 --- a/bots/images/scripts/openshift.install +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -# By default this does nothing diff --git a/bots/images/scripts/openshift.setup b/bots/images/scripts/openshift.setup deleted file mode 100755 index 9275db620..000000000 --- a/bots/images/scripts/openshift.setup +++ /dev/null @@ -1,311 +0,0 @@ -#! /bin/bash - -set -eux - -# Wait for x for many minutes -function wait() { - for i in $(seq 1 100); do - if eval "$@"; then - return 0 - fi - sleep 6 - done - exit 6 -} - -function docker_images_has() { - docker images | tr -s ' ' | cut -d ' ' --output-delimiter=: -f1,2 | grep -q "$1" -} - -function docker_pull() { - docker pull $1 - echo "$1" >> /tmp/pulledImages - docker_images_has $1 -} -rm -f /tmp/pulledImages # will be populated by pulled images names - -# Cleanup the file system a bit -rm -rf /var/cache/yum -xfs_growfs / - -echo foobar | passwd --stdin root - -nmcli con add con-name "static-eth1" ifname eth1 type ethernet ip4 "10.111.112.101/20" gw4 10.111.112.1 ipv4.dns "10.111.112.1" -nmcli con up "static-eth1" - -echo "10.111.112.101 f1.cockpit.lan" >> /etc/hosts - -printf "OPENSHIFT CONSOLE\n https://10.111.112.101:8443\n Login: scruffy Password: scruffy\n\n" >> /etc/issue -printf "OPENSHIFT LISTENING ON LOCALHOST\n $ ssh -NL 8443:localhost:8443 root@10.111.112.101\n\n" >> /etc/issue - -# Disable these things -ln -sf ../selinux/config /etc/sysconfig/selinux -printf 'SELINUX=permissive\nSELINUXTYPE=targeted\n' > /etc/selinux/config -setenforce 0 -systemctl disable --now firewalld - -wait yum -y install docker python libselinux-python - -hostnamectl set-hostname f1.cockpit.lan - -# Setup a nfs server -wait yum install -y nfs-utils -mkdir /nfsexport -echo "/nfsexport *(rw,sync)" > /etc/exports - -# This name is put into /etc/hosts later -echo "INSECURE_REGISTRY='--insecure-registry registry:5000'" >> /etc/sysconfig/docker -systemctl enable docker - -# HACK: docker falls over regularly, print its log if it does -systemctl start docker || journalctl -u docker - -# Can't use latest because release on older versions are done out of order -set +x -RELEASES_JSON=$(curl -s https://api.github.com/repos/openshift/origin/releases) -VERSION=$(echo "$RELEASES_JSON" | LC_ALL=C.UTF-8 python -c "import json, sys, distutils.version; obj=json.load(sys.stdin); releases = [x.get('tag_name', '') for x in obj if not x.get('prerelease')]; print(sorted (releases, reverse=True, key=distutils.version.LooseVersion)[0])") || { - echo "Failed to parse latest release:" >&2 - echo "$RELEASES_JSON" >&2 - echo "------------------------------------" >&2 - exit 1 -} -set -x - -# origin is too rotund to build in a normal sized VM. The linker -# step runs out of memory. In addition origin has no Fedora packages -docker_pull "openshift/origin:$VERSION" -docker run --rm --entrypoint tar "openshift/origin:$VERSION" -C /usr/bin -c openshift oc kubectl | tar -C /usr/bin -xv - -# Runs a master if on the right address, otherwise runs a node -cat > /openshift-prep < /openshift-run -EOF - -chmod +x /openshift-prep -touch /openshift-run -chmod +x /openshift-run - -cat > /etc/systemd/system/openshift.service < Dockerfile -FROM openshift/origin-docker-registry:$VERSION -ADD *.crt /etc/pki/ca-trust/source/anchors/ -USER 0 -RUN update-ca-trust extract -USER 1001 -EOF -cp /openshift.local.config/master/ca.crt openshift-ca.crt -docker build --tag openshift/origin-docker-registry:$VERSION . -cd /tmp/ -rm -r /tmp/registry -cp /openshift.local.config/master/ca.crt /etc/pki/ca-trust/source/anchors/openshift-ca.crt -update-ca-trust extract - -mkdir -p /root/.kube -cp /openshift.local.config/master/admin.kubeconfig /root/.kube/config - -# Check if we can connect to openshift -wait oc get namespaces - -wait oc get scc/restricted - -# Tell openshift to allow root containers by default. Otherwise most -# development examples just plain fail to work -oc patch scc restricted -p '{ "runAsUser": { "type": "RunAsAny" } }' - -# Tell openshift to allow logins from the openshift web console on a localhost system -oc patch oauthclient/openshift-web-console -p '{"redirectURIs":["https://10.111.112.101:8443/console/", "https://localhost:9000/"]}' - -# Deploy the registry -# --credentials deprecated -rm -rf /usr/share/rhel/secrets -oc adm registry - -function endpoint_has_address() { - oc get endpoints $1 --template='{{.subsets}}' | grep -q addresses -} - -function images_has() { - oc get images | grep -q "$1" -} - -# Wait for registry deployment to happen -wait oc get endpoints docker-registry -wait endpoint_has_address docker-registry - -# Load in some remote images -echo '{"apiVersion":"v1","kind":"ImageStream","metadata": {"name":"busybox"},"spec":{"dockerImageRepository": "busybox"}}' > /tmp/imagestream.json -oc create -f /tmp/imagestream.json - -# Get registry address and configure docker for it -address="$(oc get services docker-registry | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}')" -echo "$address registry registry.cockpit.lan" >> /etc/hosts -echo "INSECURE_REGISTRY='--insecure-registry registry:5000 --insecure-registry $address'" >> /etc/sysconfig/docker - -# Log in as another user -printf "scruffy\r\nscruffy\r\n" | oc login -oc new-project marmalade - -token=$(oc whoami -t) -docker login -p "$token" -u unneeded registry:5000 - -echo '{"apiVersion":"v1","kind":"ImageStream","metadata": {"name":"busybee"}}' > /tmp/imagestream.json -oc create -f /tmp/imagestream.json -echo '{"apiVersion":"v1","kind":"ImageStream","metadata": {"name":"juggs"}}' > /tmp/imagestream.json -oc create -f /tmp/imagestream.json -echo '{"apiVersion":"v1","kind":"ImageStream","metadata": {"name":"origin"}}' > /tmp/imagestream.json -oc create -f /tmp/imagestream.json - -# Get ready to push busybox into place -docker_pull busybox -docker tag busybox registry:5000/marmalade/busybee:latest -docker tag busybox registry:5000/marmalade/busybee:0.x -docker push registry:5000/marmalade/busybee - -mkdir /tmp/juggs -cd /tmp/juggs -printf '#!/bin/sh\necho hello from container\nsleep 100000\n' > echo-script -printf 'FROM busybox\nMAINTAINER cockpit@example.com\nEXPOSE 8888\nADD echo-script /\nRUN chmod +x /echo-script\nCMD \"/echo-script\"' > Dockerfile -docker build -t registry:5000/marmalade/juggs:latest . -printf "FROM registry:5000/marmalade/juggs:latest\nVOLUME /test\nVOLUME /another\nWORKDIR /tmp" > Dockerfile -docker build -t registry:5000/marmalade/juggs:2.11 . -cp /usr/bin/openshift . -printf "FROM registry:5000/marmalade/juggs:latest\nADD openshift /usr/bin\nUSER nobody:wheel\nENTRYPOINT [\"top\", \"-b\"]\nCMD [\"-c\"]" > Dockerfile -docker build -t registry:5000/marmalade/juggs:2.5 . -printf "FROM registry:5000/marmalade/juggs:2.5\nSTOPSIGNAL SIGKILL\nONBUILD ADD . /app/src\nARG hello=test\nARG simple\nLABEL Test=Value\nLABEL version=\"1.0\"" > Dockerfile -docker build -t registry:5000/marmalade/juggs:2.8 . -printf "FROM registry:5000/marmalade/juggs:2.8\nLABEL description=\"This is a test description of an image. It can be as long as a paragraph, featuring a nice brogrammer sales pitch.\"\nLABEL name=\"Juggs Image\"\nLABEL build-date=2016-03-04\nLABEL url=\"http://hipsum.co/\"" > Dockerfile -docker build -t registry:5000/marmalade/juggs:2.9 . -cd /tmp -rm -r /tmp/juggs - -docker push registry:5000/marmalade/juggs - -# Tag this image twice -docker tag docker.io/busybox:latest registry:5000/marmalade/origin -docker push registry:5000/marmalade/origin -docker tag "openshift/origin:$VERSION" registry:5000/marmalade/origin -docker push registry:5000/marmalade/origin - -oc new-project pizzazz - -# Some big image streams -for i in $(seq 1 15); do - for j in $(seq 1 10); do - docker tag docker.io/busybox:latest registry:5000/pizzazz/stream$i:tag$j - done - docker push registry:5000/pizzazz/stream$i -done - -# And a monster sized one -for j in $(seq 1 100); do - docker tag docker.io/busybox:latest registry:5000/pizzazz/monster:tag$j -done -docker push registry:5000/pizzazz/monster - -# Use the admin context by default -oc config use-context default/10-111-112-101:8443/system:admin - -# Some roles for testing against -printf '{"kind":"List","apiVersion":"v1","items":[{"kind":"RoleBinding","apiVersion":"v1","metadata":{"name":"registry-editor","namespace":"marmalade","resourceVersion":"1"},"userNames":["scruffy","amanda"],"groupNames":null,"subjects":[{"kind":"User","name":"scruffy"},{"kind":"User","name":"amanda"}],"roleRef":{"name":"registry-editor"}},{"kind":"RoleBinding","apiVersion":"v1","metadata":{"name":"registry-viewer","namespace":"marmalade","resourceVersion":"1"},"userNames":["scruffy","tom","amanda"],"groupNames":["sports"],"subjects":[{"kind":"User","name":"scruffy"},{"kind":"User","name":"tom"},{"kind":"User","name":"amanda"},{"kind":"Group","name":"sports"}],"roleRef":{"name":"registry-viewer"}}]}' | oc create -f - -oc patch rolebinding/admin --namespace=marmalade -p '{"kind": "RoleBinding", "metadata":{"name":"admin","namespace":"marmalade"},"userNames":["scruffy"],"groupNames":null,"subjects":[{"kind":"User","name":"scruffys"}],"roleRef":{"name":"admin"}}' || true - -# For testing the Cockpit OAuth client -printf '{"kind":"OAuthClient","apiVersion":"v1","metadata":{"name":"cockpit-oauth-devel"},"respondWithChallenges":false,"secret":"secret","allowAnyScope":true,"redirectURIs":["http://localhost:9001"] }' | oc create -f - - -# Wait for it to download -wait images_has busybox - -# Setup basics for building images -docker build -t cockpit/base /var/tmp/cockpit-base - -# Print out the kubeconfig file for copy paste -echo "---------------------------------------------------------------" -cat /root/.kube/config - -# Wait a bit in case an operator wants to copy some info -sleep 20 - -# Use standard locations for kubelet kubeconfig. f1.cockpit.lan is the master hostname, which -# is its own node and we just copy that for the others -mkdir -p /var/lib/kubelet -cp /openshift.local.config/node-f1.cockpit.lan/node.kubeconfig /var/lib/kubelet/kubeconfig - -# Turn this on in sshd_config, not in use until binary is in place -printf 'AuthorizedKeysCommand /usr/local/bin/authorized-kube-keys --kubeconfig=/var/lib/kubelet/kubeconfig\nAuthorizedKeysCommandUser root' >> /etc/ssh/sshd_config - -# Pull down remaining images -/var/lib/testvm/docker-images.setup - -yum install -y cockpit-system - -docker info - -# reduce image size -yum clean all - -systemctl stop docker -# write all changes before filling the disk -sync -/var/lib/testvm/zero-disk.setup -systemctl start docker && sleep 10 - -# Verify all pulled docker images are really present -echo All present images: -docker images -echo "Total docker images:" -docker images | wc - -docker images --format "{{.Repository}}:{{.Tag}}" > /tmp/presentImages - -echo -echo All images actually pulled -cat /tmp/presentImages -echo - -echo -echo All images expected to be pulled -cat /tmp/pulledImages -echo - -# Verify all expected are actually pulled -while read img ; do - echo Verify "$img" - grep "$img" /tmp/presentImages || (echo "Error: Image $img is missing" && exit 10) -done < /tmp/pulledImages diff --git a/bots/images/scripts/ovirt.bootstrap b/bots/images/scripts/ovirt.bootstrap deleted file mode 120000 index 98c05f002..000000000 --- a/bots/images/scripts/ovirt.bootstrap +++ /dev/null @@ -1 +0,0 @@ -centos-7.bootstrap \ No newline at end of file diff --git a/bots/images/scripts/ovirt.install b/bots/images/scripts/ovirt.install deleted file mode 100755 index 93c0f55a0..000000000 --- a/bots/images/scripts/ovirt.install +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/bash - -set -e - -/var/lib/testvm/fedora.install "$@" diff --git a/bots/images/scripts/rhel-7-7.bootstrap b/bots/images/scripts/rhel-7-7.bootstrap deleted file mode 100755 index a0c0a30a3..000000000 --- a/bots/images/scripts/rhel-7-7.bootstrap +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -ex - -if [ -z "$SUBSCRIPTION_PATH" ] && [ -e ~/.rhel/login ]; then - SUBSCRIPTION_PATH=~/.rhel -fi - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "http://download.devel.redhat.com/released/RHEL-7/7.7/Server/x86_64/os/" $SUBSCRIPTION_PATH diff --git a/bots/images/scripts/rhel-7-7.install b/bots/images/scripts/rhel-7-7.install deleted file mode 100755 index cc9b92b06..000000000 --- a/bots/images/scripts/rhel-7-7.install +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/bash - -set -e - -# remove cockpit distro packages, testing with upstream master -rpm --erase --verbose cockpit cockpit-ws cockpit-bridge cockpit-system - -/var/lib/testvm/fedora.install --rhel "$@" diff --git a/bots/images/scripts/rhel-7-7.setup b/bots/images/scripts/rhel-7-7.setup deleted file mode 120000 index 4fbdfa18d..000000000 --- a/bots/images/scripts/rhel-7-7.setup +++ /dev/null @@ -1 +0,0 @@ -rhel.setup \ No newline at end of file diff --git a/bots/images/scripts/rhel-7-8.bootstrap b/bots/images/scripts/rhel-7-8.bootstrap deleted file mode 100755 index 7b90aed32..000000000 --- a/bots/images/scripts/rhel-7-8.bootstrap +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -ex - -if [ -z "$SUBSCRIPTION_PATH" ] && [ -e ~/.rhel/login ]; then - SUBSCRIPTION_PATH=~/.rhel -fi - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "http://download.devel.redhat.com/rhel-7/nightly/RHEL-7/latest-RHEL-7.8/compose/Server/x86_64/os/" $SUBSCRIPTION_PATH diff --git a/bots/images/scripts/rhel-7-8.install b/bots/images/scripts/rhel-7-8.install deleted file mode 100755 index cc9b92b06..000000000 --- a/bots/images/scripts/rhel-7-8.install +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/bash - -set -e - -# remove cockpit distro packages, testing with upstream master -rpm --erase --verbose cockpit cockpit-ws cockpit-bridge cockpit-system - -/var/lib/testvm/fedora.install --rhel "$@" diff --git a/bots/images/scripts/rhel-7-8.setup b/bots/images/scripts/rhel-7-8.setup deleted file mode 120000 index 4fbdfa18d..000000000 --- a/bots/images/scripts/rhel-7-8.setup +++ /dev/null @@ -1 +0,0 @@ -rhel.setup \ No newline at end of file diff --git a/bots/images/scripts/rhel-8-0.bootstrap b/bots/images/scripts/rhel-8-0.bootstrap deleted file mode 100755 index 2ab0f6792..000000000 --- a/bots/images/scripts/rhel-8-0.bootstrap +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -ex - -if [ -z "$SUBSCRIPTION_PATH" ] && [ -e ~/.rhel/login ]; then - SUBSCRIPTION_PATH=~/.rhel -fi - -BASE=$(dirname $0) -# last URL for 8.0.0, later nightlies are for z-stream and have no images -$BASE/virt-install-fedora "$1" x86_64 "http://download.devel.redhat.com/rhel-8/rel-eng/RHEL-8/latest-RHEL-8.0/compose/BaseOS/x86_64/os/" $SUBSCRIPTION_PATH diff --git a/bots/images/scripts/rhel-8-0.install b/bots/images/scripts/rhel-8-0.install deleted file mode 100755 index 3325ec5ae..000000000 --- a/bots/images/scripts/rhel-8-0.install +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -set -e - -# remove cockpit distro packages, testing with upstream master -# subscription-manager-cockpit needs these, thus --nodeps -rpm --erase --nodeps --verbose cockpit cockpit-ws cockpit-bridge cockpit-system - -/var/lib/testvm/fedora.install --rhel "$@" diff --git a/bots/images/scripts/rhel-8-0.setup b/bots/images/scripts/rhel-8-0.setup deleted file mode 120000 index 4fbdfa18d..000000000 --- a/bots/images/scripts/rhel-8-0.setup +++ /dev/null @@ -1 +0,0 @@ -rhel.setup \ No newline at end of file diff --git a/bots/images/scripts/rhel-8-1-distropkg.install b/bots/images/scripts/rhel-8-1-distropkg.install deleted file mode 100755 index cad8786c6..000000000 --- a/bots/images/scripts/rhel-8-1-distropkg.install +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/bash - -set -e - -/var/lib/testvm/fedora.install --rhel "$@" diff --git a/bots/images/scripts/rhel-8-1.bootstrap b/bots/images/scripts/rhel-8-1.bootstrap deleted file mode 100755 index 3b653b300..000000000 --- a/bots/images/scripts/rhel-8-1.bootstrap +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -ex - -if [ -z "$SUBSCRIPTION_PATH" ] && [ -e ~/.rhel/login ]; then - SUBSCRIPTION_PATH=~/.rhel -fi - -BASE=$(dirname $0) -$BASE/virt-install-fedora "$1" x86_64 "http://download.devel.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.1/compose/BaseOS/x86_64/os/" $SUBSCRIPTION_PATH diff --git a/bots/images/scripts/rhel-8-1.install b/bots/images/scripts/rhel-8-1.install deleted file mode 100755 index 3325ec5ae..000000000 --- a/bots/images/scripts/rhel-8-1.install +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -set -e - -# remove cockpit distro packages, testing with upstream master -# subscription-manager-cockpit needs these, thus --nodeps -rpm --erase --nodeps --verbose cockpit cockpit-ws cockpit-bridge cockpit-system - -/var/lib/testvm/fedora.install --rhel "$@" diff --git a/bots/images/scripts/rhel-8-1.setup b/bots/images/scripts/rhel-8-1.setup deleted file mode 120000 index 4fbdfa18d..000000000 --- a/bots/images/scripts/rhel-8-1.setup +++ /dev/null @@ -1 +0,0 @@ -rhel.setup \ No newline at end of file diff --git a/bots/images/scripts/rhel-atomic.bootstrap b/bots/images/scripts/rhel-atomic.bootstrap deleted file mode 100755 index 1bb779c9f..000000000 --- a/bots/images/scripts/rhel-atomic.bootstrap +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/bash - -set -e - -url="http://cdn.stage.redhat.com/content/dist/rhel/atomic/7/7Server/x86_64/images/" - -BASE=$(dirname $0) -$BASE/atomic.bootstrap "$1" "$url" sort 1 "rhel-atomic-cloud-([0-9\.-]+).x86_64.qcow2" diff --git a/bots/images/scripts/rhel-atomic.install b/bots/images/scripts/rhel-atomic.install deleted file mode 100755 index a6b2896be..000000000 --- a/bots/images/scripts/rhel-atomic.install +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/bash - -set -e - -/var/lib/testvm/atomic.install --skip cockpit-sosreport "$@" diff --git a/bots/images/scripts/rhel-atomic.setup b/bots/images/scripts/rhel-atomic.setup deleted file mode 100755 index 9a8e66f5e..000000000 --- a/bots/images/scripts/rhel-atomic.setup +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e - -# subscribe -subscription-manager register --auto-attach --username=`cat ~/.rhel/login` --password=`cat ~/.rhel/pass` -rm -rf ~/.rhel -trap "subscription-manager unregister" EXIT - -# HACK: docker falls over regularly, print its log if it does -systemctl start docker || journalctl -u docker - -docker pull rhel7/support-tools -docker pull registry.access.redhat.com/rhel7/pod-infrastructure:latest -docker pull registry.access.redhat.com/rhel7/cockpit-ws -docker tag registry.access.redhat.com/rhel7/cockpit-ws cockpit/ws -/var/lib/testvm/atomic.setup diff --git a/bots/images/scripts/rhel.setup b/bots/images/scripts/rhel.setup deleted file mode 100755 index 6fd2724bc..000000000 --- a/bots/images/scripts/rhel.setup +++ /dev/null @@ -1,410 +0,0 @@ -#!/bin/bash - -set -e -IMAGE="$1" - -YUM_INSTALL="yum --setopt=skip_missing_names_on_install=False -y install" - -# HACK - virt-resize might not be able to resize our xfs rootfs, -# depending on how it was compiled and which plugins are installed, -# and will just silently not do it. So we do it here. -# -df --output=source,fstype / | tail -n1 | while read source fstype; do - case $fstype in - ext*) - resize2fs $source - ;; - xfs*) - xfs_growfs / - ;; - esac -done - -df -Th / - -# If the file /root/.skip_repos is present on the machine, -# all actions regarding the repositories will be skipped: -# subscriptions, adding repos, deleting existing entries -SKIP_REPO_FLAG="/root/.skip_repos" - -# Only start logging here. Otherwise the subscription credentials -# appear in the output above. -# -set -x - -if [ ! -f "$SKIP_REPO_FLAG" ]; then - # Configure repositories. - - if [ "$IMAGE" = "rhel-7-7" ]; then - # disable all default repos - rm -f --verbose /etc/yum.repos.d/*.repo -cat < /etc/yum.repos.d/internal.repo -[RHEL-7.7] -name=base-rhel -baseurl=http://download.devel.redhat.com/released/RHEL-7/7.7/Server/x86_64/os/ -enabled=1 -gpgcheck=0 - -[EXTRAS-7.7-LATEST] -name=rhel-extras-compose -baseurl=http://download.devel.redhat.com/rhel-7/rel-eng/latest-EXTRAS-7.7-RHEL-7/compose/Server/x86_64/os/ -enabled=1 -gpgcheck=0 - -[RHEL-7.7-DEBUG] -name=base-rhel-debug -baseurl=http://download.devel.redhat.com/released/RHEL-7/7.7/Server/x86_64/debug/tree/ -enabled=0 -gpgcheck=0 - -[EXTRAS-7.7-DEBUG] -name=rhel-extras-compose-debug -baseurl=http://download.devel.redhat.com/rhel-7/rel-eng/latest-EXTRAS-7.7-RHEL-7/compose/Server/x86_64/debug/tree/ -enabled=0 -gpgcheck=0 -EOF - $YUM_INSTALL yum-utils - - elif [ "$IMAGE" = "rhel-7-8" ]; then - # disable all default repos - rm -f --verbose /etc/yum.repos.d/*.repo -cat < /etc/yum.repos.d/nightly.repo -[RHEL-7.8] -name=base-rhel -baseurl=http://download.devel.redhat.com/rhel-7/nightly/RHEL-7/latest-RHEL-7.8/compose/Server/x86_64/os -enabled=1 -gpgcheck=0 - -[EXTRAS-7.8] -name=rhel-extras-compose -baseurl=http://download.devel.redhat.com/rhel-7/nightly/EXTRAS-7/latest-EXTRAS-7.8-RHEL-7/compose/Server/x86_64/os -enabled=1 -gpgcheck=0 - -[RHEL-7.8-DEBUG] -name=base-rhel-debug -baseurl=http://download.devel.redhat.com/nightly/latest-RHEL-7/compose/Server/x86_64/debug/tree -enabled=0 -gpgcheck=0 - -[EXTRAS-7.8-DEBUG] -name=rhel-extras-compose-debug -baseurl=http://download.devel.redhat.com/rhel-7/nightly/EXTRAS-7/latest-EXTRAS-7.8-RHEL-7/compose/Server/x86_64/debug/tree -enabled=0 -gpgcheck=0 -EOF - $YUM_INSTALL yum-utils - - elif [ "${IMAGE#rhel-8*}" != "$IMAGE" ]; then - case "$IMAGE" in - rhel-8-0) REPO="latest-RHEL-8.0.1" ;; - rhel-8-1) REPO="latest-RHEL-8.1" ;; - *) echo "Unknown image $IMAGE"; exit 1 - esac -cat < /etc/yum.repos.d/nightly.repo -[RHEL-8-NIGHTLY-BaseOS] -name=baseos -baseurl=http://download.devel.redhat.com/rhel-8/nightly/RHEL-8/$REPO/compose/BaseOS/x86_64/os/ -enabled=1 -gpgcheck=0 - -[RHEL-8-NIGHTLY-AppStream] -name=appstream -baseurl=http://download.devel.redhat.com/rhel-8/nightly/RHEL-8/$REPO/compose/AppStream/x86_64/os/ -enabled=1 -gpgcheck=0 - -[RHEL-8-NIGHTLY-BaseOS-Debug] -name=baseos-debug -baseurl=http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/$REPO/compose/BaseOS/x86_64/debug/tree/ -enabled=0 -gpgcheck=0 - -[RHEL-8-NIGHTLY-AppStream-Debug] -name=appstream-debug -baseurl=http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/$REPO/compose/AppStream/x86_64/debug/tree/ -enabled=0 -gpgcheck=0 -EOF - # make ipa-client available - dnf module enable -y idm:client - fi - - if [ "${IMAGE#rhel-7*}" != "$IMAGE" ]; then - # the following don't necessarily need to work - yum-config-manager --disable rhel-sjis-for-rhel-7-server-rpms || true - yum-config-manager --disable rhel-7-server-htb-rpms || true - yum-config-manager --disable rhel-7-server-rt-beta-rpms || true - fi -fi - -yum --nogpgcheck -y update - -echo foobar | passwd --stdin root - -# We install all dependencies of the cockpit packages since we want -# them to not spontaneously change from one test run to the next when -# the distribution repository is updated. -COCKPIT_DEPS="\ -atomic \ -device-mapper-multipath \ -docker \ -glib-networking \ -json-glib \ -kexec-tools \ -libssh \ -libvirt-client \ -libvirt-daemon-kvm \ -NetworkManager-team \ -openssl \ -PackageKit \ -pcp-libs \ -pcp \ -realmd \ -redhat-logos \ -selinux-policy-targeted \ -setroubleshoot-server \ -subscription-manager \ -sos \ -tuned \ -udisks2 \ -udisks2-lvm2 \ -udisks2-iscsi \ -" - -# We also install the packages necessary to join a FreeIPA domain so -# that we don't have to go to the network during a test run. -# on epel/rhel we have ipa-client instead of freeipa-client -IPA_CLIENT_PACKAGES="\ -ipa-client \ -oddjob \ -oddjob-mkhomedir \ -sssd \ -sssd-dbus \ -" - -TEST_PACKAGES="\ -valgrind \ -gdb \ -nmap-ncat \ -targetcli \ -yum-utils \ -virt-install \ -libvirt-daemon-config-network \ -cryptsetup \ -qemu-kvm \ -socat \ -vdo \ -kmod-kvdo \ -dracut-fips \ -clevis-luks \ -tang \ -boom-boot \ -" - -if [ "$IMAGE" = "centos-7" ]; then - COCKPIT_DEPS="${COCKPIT_DEPS/redhat-logos/}" -fi -if [ "${IMAGE#rhel-7}" != "$IMAGE" ] || [ "$IMAGE" == "centos-7" ]; then - COCKPIT_DEPS="$COCKPIT_DEPS kubernetes-client" -fi -if [ "$IMAGE" = "rhel-7-7" ]; then - COCKPIT_DEPS="$COCKPIT_DEPS libvirt-dbus" -fi -if [ "$IMAGE" = "rhel-7-8" ]; then - TEST_PACKAGES="${TEST_PACKAGES} insights-client" -fi -if [ "${IMAGE#rhel-7}" != "$IMAGE" ]; then - # needed for composer testing - TEST_PACKAGES="${TEST_PACKAGES} gcc-c++ lorax-composer" -fi -if [ "${IMAGE#rhel-8*}" != "$IMAGE" ]; then - TEST_PACKAGES="${TEST_PACKAGES/yum-utils/dnf-utils}" - TEST_PACKAGES="${TEST_PACKAGES} dnf-automatic" - # Atomic/docker are not on RHEL 8 - COCKPIT_DEPS="${COCKPIT_DEPS/atomic /}" - COCKPIT_DEPS="${COCKPIT_DEPS/docker /}" - COCKPIT_DEPS="${COCKPIT_DEPS} podman" - COCKPIT_DEPS="${COCKPIT_DEPS} libvirt-dbus" - TEST_PACKAGES="${TEST_PACKAGES} libvirt-daemon-config-network" - # Install node for external Composer tests, they use our rhel-* images - TEST_PACKAGES="${TEST_PACKAGES} nodejs" - TEST_PACKAGES="${TEST_PACKAGES} subscription-manager-cockpit" - # Install insights-client for external subscription-manager tests - TEST_PACKAGES="${TEST_PACKAGES} insights-client" -fi - -# in RHEL/CentOS 7, boom is shipped in a different package -if [ "${IMAGE#rhel-7}" != "$IMAGE" ] || [ "${IMAGE#centos-7}" != "$IMAGE" ] ; then - TEST_PACKAGES="${TEST_PACKAGES/boom-boot/lvm2-python-boom}" -fi - -pkgs="$TEST_PACKAGES $COCKPIT_DEPS $IPA_CLIENT_PACKAGES" -$YUM_INSTALL $pkgs - -# Pre-install cockpit packages from base preinstalled, to check for API breakages -# and more convenient interactive debugging -if [ "${IMAGE#rhel-7}" != "$IMAGE" ] || [ "${IMAGE#centos-7}" != "$IMAGE" ] ; then - $YUM_INSTALL cockpit -else - # >= 8 supports weak dependencies - sudo dnf --setopt=install_weak_deps=False install -y cockpit -fi - -# For debugging udisks/storaged crashes -debuginfo-install -y udisks2 - -# Prepare for building - -# only install mock and build if DO_BUILD is 1 -if [ "$DO_BUILD" -eq 1 ]; then - if [ "${IMAGE#rhel-8*}" != "$IMAGE" ]; then - # install mock from EPEL - dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm - dnf install -y rpm-build mock - dnf config-manager --set-disabled epel - - case "$IMAGE" in - rhel-8-0) REPO="rhel-8.0.0-build" ;; - rhel-8-1) REPO="rhel-8.1.0-build" ;; - *) echo "Unknown image $IMAGE"; exit 1 - esac - - cat < /etc/mock/default.cfg -config_opts['chroothome'] = '/builddir' -config_opts['use_host_resolv'] = False -config_opts['basedir'] = '/var/lib/mock' -config_opts['rpmbuild_timeout'] = 86400 -config_opts['yum.conf'] = '[main]\\ncachedir=/var/cache/yum\\ndebuglevel=1\\nlogfile=/var/log/yum.log\\nreposdir=/dev/null\\nretries=20\\nobsoletes=1\\ngpgcheck=0\\nassumeyes=1\\nkeepcache=1\\ninstall_weak_deps=0\\nstrict=1\\n\\n# repos\\n\\n[build]\\nname=build\\nbaseurl=http://download.devel.redhat.com/brewroot/repos/$REPO/latest/x86_64/\\n' -config_opts['chroot_setup_cmd'] = 'groupinstall build' -config_opts['target_arch'] = 'x86_64' -config_opts['root'] = u'rhel-8-candidate-x86_64' - -config_opts['macros']['%_topdir'] = '/builddir/build' -config_opts['macros']['%_rpmfilename'] = '%%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' -EOF - else - # enable epel for mock - if [ ! -f "$SKIP_REPO_FLAG" ]; then - mkdir /tmp/dep - cd /tmp/dep - $YUM_INSTALL wget - wget -T 15 -t 4 http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm - yum -y remove wget - rpm -Uvh epel-release-*.rpm - cd - rm -rf /tmp/dep - fi - - $YUM_INSTALL rpm-build mock - - # disable epel again - yum-config-manager --disable 'epel*' - fi - - useradd -c Builder -G mock builder - opsys=$(cut -d '-' -f 1 <<< "$IMAGE") - version=$(cut -d '-' -f 2 <<< "$IMAGE") - su builder -c "/usr/bin/mock --verbose -i $(/var/lib/testvm/build-deps.sh "$opsys $version")" - su builder -c "/usr/bin/mock --install --verbose rpmlint" -fi - -yum clean all || true - -# For the D-Bus test server -if type "firewall-cmd" >/dev/null 2>&1; then - FIREWALL_STATE=$(firewall-cmd --state || true) - if [ "$FIREWALL_STATE" == "running" ]; then - firewall-cmd --permanent --add-port 8765/tcp - fi -fi - -echo 'NETWORKING=yes' > /etc/sysconfig/network - -useradd -c Administrator -G wheel admin -echo foobar | passwd --stdin admin - -# To enable persistent logging -mkdir -p /var/log/journal - -if type "docker" >/dev/null 2>&1; then - # HACK: docker falls over regularly, print its log if it does - systemctl start docker || journalctl -u docker - - # docker images that we need for integration testing - /var/lib/testvm/docker-images.setup -fi - -/var/lib/testvm/zero-disk.setup --keep-mock-cache - -# HACK - kdump.service interferes with our storage tests, by loading -# the system for some time after boot and thereby causing a race -# between parted and udevd to turn out for the worse. Disabling -# kdump.service helps somewhat, but the race is still there, and -# parted still fails occasionally. -# -# https://bugzilla.redhat.com/show_bug.cgi?id=1245144 -# Fixed in parted-3.1-23.el7 -# -systemctl disable kdump.service - -# Install node for external Composer tests, they use our rhel-* images -if [ "${IMAGE#rhel-7}" != "$IMAGE" ]; then - NODE_VERSION="8.12.0" - # key 7E37093B: public key "Christopher Dickinson " imported - # key DBE9B9C5: public key "Colin Ihrig " imported - # key D2306D93: public key "keybase.io/octetcloud " imported - # key 4EB7990E: public key "Jeremiah Senkpiel " imported - # key 7EDE3FC1: public key "keybase.io/jasnell " imported - # key 7D83545D: public key "Rod Vagg " imported - # key 4C206CA9: public key "Evan Lucas " imported - # key CC11F4C8: public key "Myles Borins " imported - - for key in \ - 9554F04D7259F04124DE6B476D5A82AC7E37093B \ - 94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \ - 0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \ - FD3A5288F042B6850C66B31F09FE44734EB7990E \ - 71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \ - DD8F2338BAE7501E3DD5AC78C273792F7D83545D \ - B9AE9905FFD7803F25714661B63B535A4C206CA9 \ - C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \ - ; do - # this is very flaky from our internal network; retry a few times - retry=0 - until gpg --keyserver pool.sks-keyservers.net --recv-keys "$key"; do - retry=$((retry + 1)) - if [ $retry -eq 10 ]; then - echo "Repeatedly failed to retrieve key, giving up." >&2 - exit 1 - fi - sleep 5 - done - done - - curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" - curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" - gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc - grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - - tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 - rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt -fi - -# Final tweaks - -rm -rf /var/log/journal/* -# RHEL 7 does not enable systemd-coredump by default, later versions do -if ! grep -qr core_pattern /usr/lib/sysctl.d/; then - echo "kernel.core_pattern=|/usr/lib/systemd/systemd-coredump %p %u %g %s %t %e" > /etc/sysctl.d/50-coredump.conf -fi - -# Prevent SSH from hanging for a long time when no external network access -echo 'UseDNS no' >> /etc/ssh/sshd_config - -# Audit events to the journal -if [ ! -f /root/.keep-audit ]; then - rm -f '/etc/systemd/system/multi-user.target.wants/auditd.service' - rm -rf /var/log/audit/ -else - echo "Keeping audit enabled as /root/.keep-audit exists" -fi diff --git a/bots/images/scripts/selenium.bootstrap b/bots/images/scripts/selenium.bootstrap deleted file mode 100755 index 99ac411a7..000000000 --- a/bots/images/scripts/selenium.bootstrap +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2015 Red Hat Inc. -# Author: Dominik Perpeet -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA. - -set -ex - -BASE=$(dirname $0) - -$BASE/virt-install-fedora "$1" x86_64 "http://mirror.centos.org/centos/7/os/x86_64/" diff --git a/bots/images/scripts/selenium.setup b/bots/images/scripts/selenium.setup deleted file mode 100755 index ba3a0dbfc..000000000 --- a/bots/images/scripts/selenium.setup +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2015 Red Hat Inc. -# Author: Dominik Perpeet -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 USA. - -set -ex - -SELENIUM_DEPS="\ -docker \ -" - -yum -y upgrade -yum -y install docker - -systemctl disable firewalld - -# HACK: docker falls over regularly, print its log if it does -systemctl start docker || journalctl -u docker - -systemctl enable docker - -# docker images that we need for integration testing -docker pull selenium/hub:3 -docker pull selenium/node-chrome-debug:3 -docker pull selenium/node-firefox-debug:3 - -# reduce image size -yum clean all -/var/lib/testvm/zero-disk.setup diff --git a/bots/images/scripts/ubuntu-1804.bootstrap b/bots/images/scripts/ubuntu-1804.bootstrap deleted file mode 100755 index 2f51334da..000000000 --- a/bots/images/scripts/ubuntu-1804.bootstrap +++ /dev/null @@ -1,2 +0,0 @@ -#! /bin/sh -ex -exec $(dirname $0)/lib/debian.bootstrap "$1" https://cloud-images.ubuntu.com/daily/server/bionic/current/bionic-server-cloudimg-amd64.img diff --git a/bots/images/scripts/ubuntu-1804.install b/bots/images/scripts/ubuntu-1804.install deleted file mode 100755 index ca020acc9..000000000 --- a/bots/images/scripts/ubuntu-1804.install +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -set -e - -/var/lib/testvm/debian.install "$@" - -# HACK: With our restricted network during tests, systemd-networkd-wait-online -# takes ages and waits for two minutes. This makes waiting for docker.service time out. -systemctl mask systemd-networkd-wait-online.service - -# HACK: broken virt-builder image; locale defaults to en_US (which is ISO-8859-1) -update-locale LANG=C.UTF-8 diff --git a/bots/images/scripts/ubuntu-1804.setup b/bots/images/scripts/ubuntu-1804.setup deleted file mode 120000 index ce158f68b..000000000 --- a/bots/images/scripts/ubuntu-1804.setup +++ /dev/null @@ -1 +0,0 @@ -debian.setup \ No newline at end of file diff --git a/bots/images/scripts/ubuntu-stable.bootstrap b/bots/images/scripts/ubuntu-stable.bootstrap deleted file mode 100755 index 20088276a..000000000 --- a/bots/images/scripts/ubuntu-stable.bootstrap +++ /dev/null @@ -1,18 +0,0 @@ -#! /bin/sh -ex - -# determine latest stable release (see https://launchpad.net/+apidoc) -# in most cases the current series is devel, except for right after a stable release -rel=$(curl --silent https://api.launchpad.net/devel/ubuntu/current_series_link | sed 's/^"//; s/"$//') -if ! curl --silent "$rel" | grep -q '"supported": true'; then - # not supported, go back - rel=$(curl --silent "$rel/previous_series_link" | sed 's/^"//; s/"$//') - - if ! curl --silent "$rel" | grep -q '"supported": true'; then - echo "ERROR: neither of the last two releases are supported!?" >&2 - exit 1 - fi -fi -# release name is the last part of the URL -rel=${rel##*/} - -exec $(dirname $0)/lib/debian.bootstrap "$1" "https://cloud-images.ubuntu.com/daily/server/$rel/current/$rel-server-cloudimg-amd64.img" diff --git a/bots/images/scripts/ubuntu-stable.install b/bots/images/scripts/ubuntu-stable.install deleted file mode 100755 index ca020acc9..000000000 --- a/bots/images/scripts/ubuntu-stable.install +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -set -e - -/var/lib/testvm/debian.install "$@" - -# HACK: With our restricted network during tests, systemd-networkd-wait-online -# takes ages and waits for two minutes. This makes waiting for docker.service time out. -systemctl mask systemd-networkd-wait-online.service - -# HACK: broken virt-builder image; locale defaults to en_US (which is ISO-8859-1) -update-locale LANG=C.UTF-8 diff --git a/bots/images/scripts/ubuntu-stable.setup b/bots/images/scripts/ubuntu-stable.setup deleted file mode 120000 index ce158f68b..000000000 --- a/bots/images/scripts/ubuntu-stable.setup +++ /dev/null @@ -1 +0,0 @@ -debian.setup \ No newline at end of file diff --git a/bots/images/scripts/virt-install-fedora b/bots/images/scripts/virt-install-fedora deleted file mode 100755 index 3068dd9e2..000000000 --- a/bots/images/scripts/virt-install-fedora +++ /dev/null @@ -1,152 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2015 Red Hat Inc. -# Author: -# Further contributions: -# Adapted: -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -# Adapted from create-guest-qcow2. We don't use that script as is -# since it is, to quote Mr Parker, "2% code and 98% config stuff". - -set -ex - -if [ "$#" -lt 3 ]; then - echo >&2 "Usage: virt-install-fedora IMAGE ARCH URL [SUBSCRIPTION_PATH]" - exit 1 -fi - -out=$1 -arch=$2 -url=$3 - -base=$(dirname $0) -ks=fedora.ks - -cat <$ks -install -text -shutdown -lang en_US.UTF-8 -keyboard us -network --bootproto dhcp -rootpw foobar -firewall --enabled --ssh -selinux --enforcing -timezone --utc America/New_York -bootloader --location=mbr --append="console=ttyS0,115200 rd_NO_PLYMOUTH" -zerombr -clearpart --all --initlabel -autopart - -%packages -@core -%end - -%post -mkdir /root/.ssh -chmod 700 /root/.ssh -echo "$(cat $base/../../machine/identity.pub)" > /root/.ssh/authorized_keys -chmod 600 /root/.ssh/authorized_keys -mkdir -p /etc/sysconfig/network-scripts -echo "$(cat $base/network-ifcfg-eth0)" > /etc/sysconfig/network-scripts/ifcfg-eth0 -echo "$(cat $base/network-ifcfg-eth1)" > /etc/sysconfig/network-scripts/ifcfg-eth1 -sed -i 's/GRUB_TIMEOUT.*/GRUB_TIMEOUT=0/; /GRUB_CMDLINE_LINUX=/ s/"$/ net.ifnames=0 biosdevname=0"/' /etc/default/grub -grub2-mkconfig -o /boot/grub2/grub.cfg -systemctl is-enabled sshd.socket || systemctl is-enabled sshd.service || systemctl enable sshd.socket -%end -EOF - -qemu-img create -f qcow2 "$out" 12G - -connect="--connect=qemu:///session" -name=$(basename $1)-builder - -cleanup_builder_vm () { - if virsh $connect list --state-running --name | grep -q $name; then - virsh $connect destroy $name - fi - if virsh $connect list --all --name | grep -q $name; then - virsh $connect undefine $name - fi -} - -# Using virt-install is fundamentally an interactive process. We -# expect the installation to finish unattended, but it might drop into -# an emergency shell upon unexpected errors, for example. Thus, we -# run it under 'expect' and catch a few known scenarios that require -# special handling. -# -# (Also, virt-install works best when it has a tty.) - -supervised_virt_install () { -expect -- - virt-install "$@" <<'EOF' -eval spawn $argv -set timeout -1 -expect { - "emergency mode" { send_user "\n\n\n\nABORT - emergency shell\n" - exit 1 - } - "Please make your choice" { send_user "\n\n\n\nABORT - Anaconda stopped\n" - exit 1 - } - "Please make a selection" { send_user "\n\n\n\nABORT - Anaconda stopped\n" - exit 1 - } -} -EOF -} - -cleanup_builder_vm - -# HACK: Due to #1686464 -ks_path="file:/$ks" -ks_inject="--initrd-inject=$ks" -if [[ "$url" == *30/Server* ]] -then - tmpdir=`mktemp -d` - pushd . - cp $ks $tmpdir - cd $tmpdir - python3 -m http.server & - popd - server_pid=$! - ks_path="http://_gateway:8000/$ks" - ks_inject="" -fi - -supervised_virt_install $connect \ - $ks_inject \ - --extra-args="ks=$ks_path console=ttyS0,115200" \ - --name=$name \ - --disk "path=$out,format=qcow2" \ - --ram 4096 \ - --vcpus=1 \ - --os-type linux \ - --os-variant fedora21 \ - --location="$url" \ - --nographics \ - --noreboot - -if [ -n "$server_pid" ] -then - kill $server_pid - rm -rf $tmpdir -fi - -rm $ks - -cleanup_builder_vm diff --git a/bots/images/scripts/windows-10.bootstrap b/bots/images/scripts/windows-10.bootstrap deleted file mode 100755 index ac350e5d0..000000000 --- a/bots/images/scripts/windows-10.bootstrap +++ /dev/null @@ -1,71 +0,0 @@ -#! /bin/bash -set -ex - -ORIGIN="$1" -VM_NAME=win10 - -# from https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/ -wget --continue https://az792536.vo.msecnd.net/vms/VMBuild_20180425/VMWare/MSEdge/MSEdge.Win10.VMWare.zip -O MSEdge.Win10.VMWare.zip -# from https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ -wget --continue https://download.microsoft.com/download/F/8/A/F8AF50AB-3C3A-4BC4-8773-DC27B32988DD/MicrosoftWebDriver.exe -O MicrosoftWebDriver.exe -wget --continue http://javadl.oracle.com/webapps/download/AutoDL?BundleId=233172_512cd62ec5174c3487ac17c61aaa89e8 -O java-installer.exe -# from https://selenium-release.storage.googleapis.com/ -wget --continue https://selenium-release.storage.googleapis.com/3.13/selenium-server-standalone-3.13.0.jar -O selenium.jar -wget --continue https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.141-1/virtio-win.iso -O virtio-win.iso - -[ -e disk.vmdk ] || unzip -o MSEdge.Win10.VMWare.zip - -cat <<'EOF' > Autorun.inf -[autorun] -open=install.bat -label=Selenium installer -EOF - -cat <<'EOF' > selenium.bat -powershell -Command "while (!(Get-NetIPAddress -AddressFamily ipv4 | Where-Object IPAddress -eq '10.111.112.10')) { sleep 2 }" -timeout 10 -START "hub" java -jar C:\selenium\selenium.jar -role hub -timeout 20 -java -Dwebdriver.edge.driver=c:\selenium\MicrosoftWebDriver.exe -jar C:\selenium\selenium.jar -role node -hub http://localhost:4444/grid/register -browser browserName="MicrosoftEdge",platform=WINDOWS -timeout 50 -EOF - -cat <<'EOF' > install.bat -net file 1>nul 2>nul && goto :run || powershell -ex unrestricted -Command "Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c %~fnx0 %*'" -goto :eof -:run -netsh advfirewall firewall add rule name="Open Port 4444" dir=in action=allow protocol=TCP localport=4444 -netsh advfirewall firewall add rule name="Open Port 5555" dir=in action=allow protocol=TCP localport=5555 -netsh advfirewall set allprofiles state off -pnputil.exe -i -a E:\qxldod\w10\amd64\qxldod.inf -mkdir c:\selenium -echo f | xcopy /f /y d:\selenium.jar c:\selenium\selenium.jar -echo f | xcopy /f /y d:\selenium.bat c:\selenium\selenium.bat -echo f | xcopy /f /y d:\MicrosoftWebDriver.exe c:\selenium\MicrosoftWebDriver.exe -REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Selenium Hub" /t REG_SZ /F /D "C:\selenium\selenium.bat" -d:\java-installer.exe /s -timeout 30 -shutdown -s -t 0 -EOF - -CR=$(printf '\r') -sed -i "s/\$/$CR/" *.bat Autorun.inf -genisoimage -output wininit.iso -volid cidata -joliet *.bat *.exe selenium.jar Autorun.inf -echo "Converting vmdk to qcow2..." -qemu-img convert -f vmdk -O qcow2 disk.vmdk "$ORIGIN" -virsh destroy "$VM_NAME" || true -virsh undefine --nvram "$VM_NAME" || true -virt-install -n "$VM_NAME" -r 4000 --vcpus=4 --os-variant=win10 \ - --disk "$ORIGIN",device=disk --network user --boot uefi \ - --noautoconsole --wait=-1 --noreboot \ - --disk wininit.iso,device=cdrom \ - --disk virtio-win.iso,device=cdrom -virsh start "$VM_NAME" - -echo "Manual part of installation, open graphics console and run D:\install.bat" -set +x -while [[ $(virsh domstate "$VM_NAME") =~ 'running' ]]; do - sleep 2 -done -set -x -echo "Installation finished" diff --git a/bots/images/selenium b/bots/images/selenium deleted file mode 120000 index d6d011a37..000000000 --- a/bots/images/selenium +++ /dev/null @@ -1 +0,0 @@ -selenium-51bd1a8db6923e2bfd4eba0e36c53ca978f0a4109b31a9c1ffb6ff2ca23bf7d4.qcow2 \ No newline at end of file diff --git a/bots/images/ubuntu-1804 b/bots/images/ubuntu-1804 deleted file mode 120000 index 3b4255a3d..000000000 --- a/bots/images/ubuntu-1804 +++ /dev/null @@ -1 +0,0 @@ -ubuntu-1804-8f4086635b6afc6848dc329c60bb43e51999c900cc8d6509183c0e0cd77aeff0.qcow2 \ No newline at end of file diff --git a/bots/images/ubuntu-stable b/bots/images/ubuntu-stable deleted file mode 120000 index f85157319..000000000 --- a/bots/images/ubuntu-stable +++ /dev/null @@ -1 +0,0 @@ -ubuntu-stable-a8d26f908dee3428713ec34fda52164d45b15ad0b7081f650aca191698bbc6ce.qcow2 \ No newline at end of file diff --git a/bots/images/windows-10 b/bots/images/windows-10 deleted file mode 120000 index 94f4a0793..000000000 --- a/bots/images/windows-10 +++ /dev/null @@ -1 +0,0 @@ -windows-10-d62c352db36aa2cb37ec4a51d19c2236ad67a216f698dc3784862047aa762240.qcow2 \ No newline at end of file diff --git a/bots/inspect-queue b/bots/inspect-queue deleted file mode 100755 index 57ddfb664..000000000 --- a/bots/inspect-queue +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2018 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -MAX_PRIORITY = 9 - -import argparse -import sys - -from task import distributed_queue - -def main(): - parser = argparse.ArgumentParser(description='Read and print messages from the queue without acknowleding them') - parser.add_argument('--amqp', default='localhost:5671', - help='The host:port of the AMQP server to consume from (default: %(default)s)') - opts = parser.parse_args() - - with distributed_queue.DistributedQueue(opts.amqp, ['public', 'rhel'], passive=True) as q: - def print_queue(queue): - if q.declare_results[queue] is None: - print("queue {} does not exist".format(queue)) - return - message_count = q.declare_results[queue].method.message_count - if message_count == 0: - print("queue {} is empty".format(queue)) - return - for i in range(message_count): - method_frame, header_frame, body = q.channel.basic_get(queue=queue) - if method_frame: - print(body.decode()) - - print('public queue:') - print_queue('public') - print('rhel queue:') - print_queue('rhel') - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/issue-scan b/bots/issue-scan deleted file mode 100755 index 4c2727c0f..000000000 --- a/bots/issue-scan +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -NAMES = [ - "example-task", - "po-refresh", - "image-refresh", - "npm-update", - "naughty-prune", - "learn-tests", - "tests-data", - "flakes-refresh", -] - -KVM_TASKS = [ - "image-refresh" -] - -# RHEL tasks have to be done inside Red Hat network -REDHAT_TASKS = [ - "rhel", - "redhat" -] - -# Windows tasks have to be done by a human -WINDOWS_TASKS = [ - "windows" -] - -KUBERNETES_TASKS = [ - ".svc.cluster.local" -] - -# Credentials for working on above contexts -REDHAT_CREDS = "~/.rhel/login" - -import argparse -import pipes -import os -import sys -import json - -sys.dont_write_bytecode = True - -from task import github, redhat_network, distributed_queue, labels_of_pull - -BOTS = os.path.normpath(os.path.join(os.path.dirname(__file__))) -BASE = os.path.normpath(os.path.join(BOTS, "..")) - -no_amqp = False -try: - import pika -except ImportError: - no_amqp = True - - -def main(): - parser = argparse.ArgumentParser(description="Scan issues for tasks") - parser.add_argument("-v", "--human-readable", "--verbose", action="store_true", default=False, - dest="verbose", help="Print verbose information") - parser.add_argument('--amqp', default=None, - help='The host:port of the AMQP server to publish to (format host:port)') - parser.add_argument('--issues-data', default=None, - help='issues or pull request event GitHub JSON data to evaluate') - opts = parser.parse_args() - - if opts.amqp and no_amqp: - parser.error("AMQP host:port specified but python-amqp not available") - - kvm = os.access("/dev/kvm", os.R_OK | os.W_OK) - if not kvm: - sys.stderr.write("issue-scan: No /dev/kvm access, not creating images here\n") - - # Figure if we're in a Kubernetes namespace. This file will always exist - try: - with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: - namespace = f.read().strip() - except IOError: - namespace = None - - for result in scan(opts.issues_data, opts.verbose): - if opts.amqp: - with distributed_queue.DistributedQueue(opts.amqp, queues=['rhel', 'public']) as q: - queue_task(q.channel, result) - continue - - if not kvm and contains_any(result, KVM_TASKS): - sys.stderr.write("issue-scan: skipping (no kvm): {0}\n".format(result)) - continue - elif not redhat_network() and contains_any(result, REDHAT_TASKS): - sys.stderr.write("issue-scan: skipping (outside redhat): {0}\n".format(result)) - continue - elif contains_any(result, WINDOWS_TASKS): - sys.stderr.write("issue-scan: skipping (windows task): {0}\n".format(result)) - continue - elif contains_any(result, KUBERNETES_TASKS): - if not namespace: - sys.stderr.write("issue-scan: skipping (not in kubernetes): {0}\n".format(result)) - continue - url = namespace + KUBERNETES_TASKS[0] - if url not in result: - sys.stderr.write("issue-scan: skipping (not same namespace): {0}\n".format(result)) - continue - sys.stdout.write(result + "\n") - - return 0 - -def contains_any(string, matches): - for match in matches: - if match in string: - return True - return False - -# Map all checkable work items to fixtures -def tasks_for_issues(issues_data): - results = [ ] - issues = [ ] - - if issues_data: - event = json.loads(issues_data) - repo = event["repository"]["full_name"] - issue = event.get("issue") or event.get("pull_request") - labels = labels_of_pull(issue) - if 'bot' in labels: - issues.append(issue) - api = github.GitHub(repo=repo) - else: - api = github.GitHub() - issues = api.issues(state="open") - whitelist = api.whitelist() - - for issue in issues: - if issue["title"].strip().startswith("WIP"): - continue - login = issue.get("user", { }).get("login", { }) - if login not in whitelist: - continue - - # - # We only consider the first unchecked item per issue - # - # The bots think the list needs to be done in order. - # If the first item in the checklist is not something - # the bots can do, then the bots will ignore this issue - # (below in output_task) - # - checklist = github.Checklist(issue["body"]) - for item, checked in checklist.items.items(): - if not checked: - results.append((item, issue, api.repo)) - break - return results - -def output_task(command, issue, repo, verbose): - name, unused, context = command.partition(" ") - if name not in NAMES: - return None - number = issue.get("number", None) - if number is None: - return None - - context = context.strip() - checkout = "PRIORITY={priority:04d} " - cmd = "bots/{name} --verbose --issue='{issue}' {context}" - - # `--issues-data` should also be able to receive pull_request events, in that - # case pull_request won't be present in the object, but commits will be - if "pull_request" in issue or "commits" in issue: - checkout += "bots/make-checkout --verbose --repo {repo} pull/{issue}/head && " - else: - checkout += "bots/make-checkout --verbose --repo {repo} master && " - - if verbose: - return "issue-{issue} {name} {context} {priority}".format( - issue=int(number), - priority=distributed_queue.MAX_PRIORITY, - name=name, - context=context - ) - else: - if context: - context = pipes.quote(context) - return (checkout + "cd bots/make-checkout-workdir && " + cmd + " ; cd ../..").format( - issue=int(number), - priority=distributed_queue.MAX_PRIORITY, - name=name, - context=context, - repo=repo, - ) - -def queue_task(channel, result): - body = { - "command": result, - "type": "issue", - } - queue = 'rhel' if contains_any(result, REDHAT_TASKS) else 'public' - channel.basic_publish('', queue, json.dumps(body), properties=pika.BasicProperties(priority=distributed_queue.MAX_PRIORITY)) - -# Default scan behavior run for each task -def scan(issues_data, verbose): - global issues - - results = [ ] - - # Now go through each fixture - for (command, issue, repo) in tasks_for_issues(issues_data): - result = output_task(command, issue, repo, verbose) - if result is not None: - results.append(result) - - return results - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/issues-review b/bots/issues-review deleted file mode 100755 index fa15e7409..000000000 --- a/bots/issues-review +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import time - -from task import github - -def issues_review(api, opts): - now = time.time() - treshold = opts.age * 86400 - count = 100 - page = 1 - while count == 100: - issues = api.get("issues?filter=all&page=%i&per_page=%i" % (page, count)) - page += 1 - count = len(issues) - for issue in issues: - age = now - time.mktime(time.strptime(issue["updated_at"], "%Y-%m-%dT%H:%M:%SZ")) - if age >= treshold: - print("Labelling #%i last updated at %s" % (issue["number"], issue["updated_at"])) - api.post("issues/%i/labels" % issue["number"], [opts.label]) - - -def main(): - parser = argparse.ArgumentParser(description='Add review label to stale issues') - parser.add_argument('-a', '--age', metavar='DAYS', default=90, - help='Label issues whose last update is older than given number of days (default: %(default)s)') - parser.add_argument('-l', '--label', default=time.strftime('review-%Y-%m'), - help='Label name (default: %(default)s)') - parser.add_argument('--repo', help='Work on this GitHub repository (owner/name)') - opts = parser.parse_args() - - api = github.GitHub(repo=opts.repo) - issues_review(api, opts) - - -if __name__ == '__main__': - main() diff --git a/bots/learn-tests b/bots/learn-tests deleted file mode 100755 index 07542192a..000000000 --- a/bots/learn-tests +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Slavek Kabrda -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# The name and version of the training data -TRAINING_DATA = "tests-train-1.jsonl.gz" - -# The number of days in history to learn from. This is different from -# the amount of data we gather in tests-data, and can be adjusted -# independently. -SINCE = 21 - -import os -import socket -import ssl -import subprocess -import sys -import time -import urllib - -sys.dont_write_bytecode = True - -import task - -from machine import testvm - -BOTS = os.path.dirname(os.path.realpath(__file__)) - -def run(url_or_file, verbose=False, dry=False, **kwargs): - # Default set of training data, retrieve it and use from data directory - if not url_or_file: - url_or_file = TRAINING_DATA - - # A URL was provided directly, just use it - if url_or_file.startswith("http"): - filename = os.path.basename(url_or_file) - url = url_or_file - - else: - host = os.environ.get("COCKPIT_LEARN_SERVICE_HOST", "learn-cockpit.apps.ci.centos.org") - port = os.environ.get("COCKPIT_LEARN_SERVICE_PORT", "443") - url = "{0}://{1}:{2}/train/{3}".format("https" if port == "443" else "http", host, port, os.path.basename(url_or_file)) - filename = url_or_file - - if "/" not in filename and not os.path.exists(filename): - if not dry: - subprocess.check_call([ os.path.join(BOTS, "image-download"), "--state", filename ]) - filename = os.path.join(testvm.get_images_data_dir(), filename) - train(filename, url, verbose) - -# Does 'tail -F' on an HTTP URL -def tail(url, until, verbose=False): - stop = False - at = 0 - - while True: - time.sleep(10) - - try: - req = urllib.request.Request(url, headers={ "Range": "bytes={0}-".format(at) }) - cafile = os.path.join(BOTS, "images", "files", "ca.pem") - context = ssl.create_default_context(cafile=cafile) - with urllib.request.urlopen(req, context=context) as f: - while True: - data = f.read(2048) - if not data: - break - at += len(data) - if verbose: - sys.stderr.buffer.write(data) - except urllib.error.HTTPError as ex: - if ex.code != 404 and ex.code != 416: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - except (ConnectionResetError, urllib.error.URLError, socket.gaierror) as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - - if stop: - break - - # Note that we do one more loop after we stop, to make sure to get all of url - stop = until() - -def train(filename, url, verbose=False): - if verbose: - sys.stderr.write(" ^ {0}\n".format(url)) - - cmd = [ os.path.join(BOTS, "image-upload"), "--state", filename, "--store", url ] - - # Passing through a non terminal stdout is necessary to make progress work - subprocess.check_call(cmd) - - # We run until the file disappears, which means training has taken place - def until(): - try: - req = urllib.request.Request(url, method="HEAD") - with urllib.request.urlopen(req, cafile=os.path.join(BOTS, "images", "files", "ca.pem")) as f: - f.read() - except urllib.error.HTTPError as ex: - if ex.code == 404: - return True - sys.stderr.write("{0}: {1}\n".format(url, ex)) - except (ConnectionResetError, urllib.error.URLError, socket.gaierror) as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - return False - - # Now tail the logs until above happens - log = urllib.parse.urljoin(url, "../log") - tail(log, until, verbose) - -if __name__ == '__main__': - task.main(function=run, title="Learn from testing data", verbose=True) diff --git a/bots/learn-trigger b/bots/learn-trigger deleted file mode 100755 index fbe29d2fc..000000000 --- a/bots/learn-trigger +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# Number of days between learning attempts -DAYS = 7 - -import argparse -import sys -import time - -sys.dont_write_bytecode = True - -import task - -# The name and version of the training data -TRAINING_DATA = "tests-train-1.jsonl.gz" - -def main(): - parser = argparse.ArgumentParser(description='Ensure necessary issue learning from tests') - parser.add_argument('-v', '--verbose', action="store_true", default=False, - help="Print verbose information") - parser.parse_args() - - title = "Update machine learning from test logs" - body = "Perform machine learning tasks such as retrieving new test logs and updating" - - since = time.time() - (DAYS * 86400) - items = [ - "tests-data " + TRAINING_DATA, - # We want this to run in both namespaces - "learn-tests https://learn-cockpit.apps.ci.centos.org/train/" + TRAINING_DATA, - "flakes-refresh", - ] - issue = task.issue(title, body, items[1], items=items, state="all", since=since) - - if issue: - sys.stderr.write("#{0}: learn-tests\n".format(issue["number"])) - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/machine/cloud-init.iso b/bots/machine/cloud-init.iso deleted file mode 100644 index 3ffbb0828..000000000 Binary files a/bots/machine/cloud-init.iso and /dev/null differ diff --git a/bots/machine/host_key b/bots/machine/host_key deleted file mode 100644 index 14f588b45..000000000 --- a/bots/machine/host_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAr+bCynyw7hAG03Bwt3joCTPjrexdO+ynsA+HtZRs38N9NCaO -MZ7j7KCRFUgkezo7GEAp7lRparZWrzAixcyATZNOokwYP55flvsWtwhTSE2wI4gY -n+0nmNFy+l3qs29VWFzVX8CkCqXBiGw53uo8qLuMEWVdXmstNxR00pHdvlyjOhjl -BpZBFKD8gMGDx6qClGIosgcSbNtJf6Xl1ceo7BoLNknOoJdyiT9EwdhO53A9aVhx -kbYjbIRRVWq8P2Cq/kbPioYlUtwgAH2A4aQTVlzsEyssdnriYwIbERddG8eqZ7mn -UhKU/FH6Of2BSFQA9Rh6bVC0s1Y1KCZupLaBwQIDAQABAoIBAQCbjHLA4NcNDjsb -CxmCBXcbfDlged5QuYvoEzOtDN3iWlsDnPytQJbJj4v8x9kK54mOfl8WFKtL5IZv -UR/OznK/Jv6oYqYmzAQ33T5PCRusmpaiNR2hfvQ/HSiR4i9EEbXk9+LwU8g8aivk -WeArEfQmOgM49uxELH7FcF+GPdtbE9TsHNdkVf1CzCMcGdIeNjeCqEDQrgRSdAq8 -YpCrlvQj76Gv8g6IOUiMZYS/fqbuvMR/XryXSQkEUX/4I5QojZOD1XrzxGA94jJ9 -dOv3Yr1y2+fhPAy5dDIFoqWSuDMib2yGV47jFo+Mqu6ovLVPAt74UWucHKImXgo1 -qvl0wFkJAoGBANwmWqJZ8dxJTU5gcK2KOq3u2JUYSYek3HMkEsPjEezGtht1Okg5 -TjxFEiw+yc4yeUtj0lIOyNU976FA0+5ItiW18/Byw6zWUi2BmrLGsCM0/CL/xwKM -hVo8DrMXcGrZY6ZSqNiLtAYLmgAUKkJEP+pw8r1Qr0pO5yfVHNeK0v/3AoGBAMyL -xWIhETGKkmyCuqFSFPELxbmMwjqWagNrzFK23/cqgbFv0aCz6wXhcwQ5JiszFq7B -Hvz8Wezl9Ur2FdFz3wGz46q+Cdqnw7uQTTGd5WbDWHN/tCS67bKn998BqENpPiWK -OIgNFXnNcucFtte9o7QDmjSaDd4u0xwveRYwHg4HAoGBAJWPbOV864X3OpCzjfkn -vmOprvPjUxjW1HlYmXMA0Y2lFdSjmFu2qsLhPc5XPaxat/KStzDOIHxWHnTTYOcx -+KS37yh8HxlNZPjLYrhvqPvSJDT2xVGi+3lo8aeTlejRFRTKdTDgAAZXXWEOUgNA -8Jcp8o7QwLVf00RJUNXR1zTTAoGAChy+3WMVHoXjR0oPP/p23pPeapXy5EKbax/h -MhWobOfFEaidjHxYminTLdpFcM1NycXyaj9vkq6rudEAsyIvXD4wezh59D1nB9bS -eil8NeBidxNRLJ+xMKvtLTE/yFVjpSd4NAGxlhv6GkHGEFRny3aCISecl+douHQA -YIBwe/ECgYALzEEkESm8d5Zq2fuFtUhRqFGcOtr/IYR8OgtUIZe2NRImsR+r5ycN -w4mw7RAnxKqOoXeAtWBwi5MykItiaof2MD3MIe4kxlZQt0NPpyE4dkzsUkYf89kE -ndu5mUalV7s7KBttm9gn8e+btzERna2VPRfDQh8nHw/zLXtE7lFSUg== ------END RSA PRIVATE KEY----- diff --git a/bots/machine/host_key.pub b/bots/machine/host_key.pub deleted file mode 100644 index a05bca32c..000000000 --- a/bots/machine/host_key.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCv5sLKfLDuEAbTcHC3eOgJM+Ot7F077KewD4e1lGzfw300Jo4xnuPsoJEVSCR7OjsYQCnuVGlqtlavMCLFzIBNk06iTBg/nl+W+xa3CFNITbAjiBif7SeY0XL6Xeqzb1VYXNVfwKQKpcGIbDne6jyou4wRZV1eay03FHTSkd2+XKM6GOUGlkEUoPyAwYPHqoKUYiiyBxJs20l/peXVx6jsGgs2Sc6gl3KJP0TB2E7ncD1pWHGRtiNshFFVarw/YKr+Rs+KhiVS3CAAfYDhpBNWXOwTKyx2euJjAhsRF10bx6pnuadSEpT8Ufo5/YFIVAD1GHptULSzVjUoJm6ktoHB diff --git a/bots/machine/identity b/bots/machine/identity deleted file mode 100644 index d02b8c671..000000000 --- a/bots/machine/identity +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA1DrTSXQRF8isQQfPfK3U+eFC4zBrjur+Iy15kbHUYUeSHf5S -jXPYbHYqD1lHj4GJajC9okle9rykKFYZMmJKXLI6987wZ8vfucXo9/kwS6BDAJto -ZpZSj5sWCQ1PI0Ce8CbkazlTp5NIkjRfhXGP8mkNKMEhdNjaYceO49ilnNCIxhpb -eH5dH5hybmQQNmnzf+CGCCLBFmc4g3sFbWhI1ldyJzES5ZX3ahjJZYRUfnndoUM/ -TzdkHGqZhL1EeFAsv5iV65HuYbchch4vBAn8jDMmHh8G1ixUCL3uAlosfarZLLyo -3HrZ8U/llq7rXa93PXHyI/3NL/2YP3OMxE8baQIDAQABAoIBAQCxuOUwkKqzsQ9W -kdTWArfj3RhnKigYEX9qM+2m7TT9lbKtvUiiPc2R3k4QdmIvsXlCXLigyzJkCsqp -IJiPEbJV98bbuAan1Rlv92TFK36fBgC15G5D4kQXD/ce828/BSFT2C3WALamEPdn -v8Xx+Ixjokcrxrdeoy4VTcjB0q21J4C2wKP1wEPeMJnuTcySiWQBdAECCbeZ4Vsj -cmRdcvL6z8fedRPtDW7oec+IPkYoyXPktVt8WsQPYkwEVN4hZVBneJPCcuhikYkp -T3WGmPV0MxhUvCZ6hSG8D2mscZXRq3itXVlKJsUWfIHaAIgGomWrPuqC23rOYCdT -5oSZmTvFAoGBAPs1FbbxDDd1fx1hisfXHFasV/sycT6ggP/eUXpBYCqVdxPQvqcA -ktplm5j04dnaQJdHZ8TPlwtL+xlWhmhFhlCFPtVpU1HzIBkp6DkSmmu0gvA/i07Z -pzo5Z+HRZFzruTQx6NjDtvWwiXVLwmZn2oiLeM9xSqPu55OpITifEWNjAoGBANhH -XwV6IvnbUWojs7uiSGsXuJOdB1YCJ+UF6xu8CqdbimaVakemVO02+cgbE6jzpUpo -krbDKOle4fIbUYHPeyB0NMidpDxTAPCGmiJz7BCS1fCxkzRgC+TICjmk5zpaD2md -HCrtzIeHNVpTE26BAjOIbo4QqOHBXk/WPen1iC3DAoGBALsD3DSj46puCMJA2ebI -2EoWaDGUbgZny2GxiwrvHL7XIx1XbHg7zxhUSLBorrNW7nsxJ6m3ugUo/bjxV4LN -L59Gc27ByMvbqmvRbRcAKIJCkrB1Pirnkr2f+xx8nLEotGqNNYIawlzKnqr6SbGf -Y2wAGWKmPyEoPLMLWLYkhfdtAoGANsFa/Tf+wuMTqZuAVXCwhOxsfnKy+MNy9jiZ -XVwuFlDGqVIKpjkmJyhT9KVmRM/qePwgqMSgBvVOnszrxcGRmpXRBzlh6yPYiQyK -2U4f5dJG97j9W7U1TaaXcCCfqdZDMKnmB7hMn8NLbqK5uLBQrltMIgt1tjIOfofv -BNx0raECgYEApAvjwDJ75otKz/mvL3rUf/SNpieODBOLHFQqJmF+4hrSOniHC5jf -f5GS5IuYtBQ1gudBYlSs9fX6T39d2avPsZjfvvSbULXi3OlzWD8sbTtvQPuCaZGI -Df9PUWMYZ3HRwwdsYovSOkT53fG6guy+vElUEDkrpZYczROZ6GUcx70= ------END RSA PRIVATE KEY----- diff --git a/bots/machine/identity.pub b/bots/machine/identity.pub deleted file mode 100644 index d60db7e4c..000000000 --- a/bots/machine/identity.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUOtNJdBEXyKxBB898rdT54ULjMGuO6v4jLXmRsdRhR5Id/lKNc9hsdioPWUePgYlqML2iSV72vKQoVhkyYkpcsjr3zvBny9+5xej3+TBLoEMAm2hmllKPmxYJDU8jQJ7wJuRrOVOnk0iSNF+FcY/yaQ0owSF02Nphx47j2KWc0IjGGlt4fl0fmHJuZBA2afN/4IYIIsEWZziDewVtaEjWV3InMRLllfdqGMllhFR+ed2hQz9PN2QcapmEvUR4UCy/mJXrke5htyFyHi8ECfyMMyYeHwbWLFQIve4CWix9qtksvKjcetnxT+WWrutdr3c9cfIj/c0v/Zg/c4zETxtp cockpit-test diff --git a/bots/machine/machine_core/__init__.py b/bots/machine/machine_core/__init__.py deleted file mode 100644 index a2ee36f93..000000000 --- a/bots/machine/machine_core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Place holder for python module diff --git a/bots/machine/machine_core/cli.py b/bots/machine/machine_core/cli.py deleted file mode 100644 index 21184cfb1..000000000 --- a/bots/machine/machine_core/cli.py +++ /dev/null @@ -1,49 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import signal -import argparse -from . import machine_virtual - -def cmd_cli(): - parser = argparse.ArgumentParser(description="Run a VM image until SIGTERM or SIGINT") - parser.add_argument("--memory", type=int, default=1024, - help="Memory in MiB to allocate to the VM (default: %(default)s)") - parser.add_argument("image", help="Image name") - args = parser.parse_args() - - network = machine_virtual.VirtNetwork(0, image=args.image) - machine = machine_virtual.VirtMachine(image=args.image, networking=network.host(), memory_mb=args.memory) - machine.start() - machine.wait_boot() - - # run a command to force starting the SSH master - machine.execute('uptime') - - # print ssh command - print("ssh -o ControlPath=%s -p %s %s@%s" % - (machine.ssh_master, machine.ssh_port, machine.ssh_user, machine.ssh_address)) - # print Cockpit web address - print("http://%s:%s" % (machine.web_address, machine.web_port)) - # print marker that the VM is ready; tests can poll for this to wait for the VM - print("RUNNING") - - signal.signal(signal.SIGTERM, lambda sig, frame: machine.stop()) - try: - signal.pause() - except KeyboardInterrupt: - machine.stop() diff --git a/bots/machine/machine_core/constants.py b/bots/machine/machine_core/constants.py deleted file mode 100644 index 88cf9a0b1..000000000 --- a/bots/machine/machine_core/constants.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import os - -# Images which are Atomic based -ATOMIC_IMAGES = ["rhel-atomic", "fedora-atomic", "continuous-atomic"] - -MACHINE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -BOTS_DIR = os.path.dirname(MACHINE_DIR) -BASE_DIR = os.path.dirname(BOTS_DIR) -TEST_DIR = os.path.join(BASE_DIR, "test") -GIT_DIR = os.path.join(BASE_DIR, ".git") - -IMAGES_DIR = os.path.join(BOTS_DIR, "images") -SCRIPTS_DIR = os.path.join(IMAGES_DIR, "scripts") - -DEFAULT_IDENTITY_FILE = os.path.join(MACHINE_DIR, "identity") - -TEST_OS_DEFAULT = "fedora-30" -DEFAULT_IMAGE = os.environ.get("TEST_OS", TEST_OS_DEFAULT) diff --git a/bots/machine/machine_core/directories.py b/bots/machine/machine_core/directories.py deleted file mode 100644 index 741eb978c..000000000 --- a/bots/machine/machine_core/directories.py +++ /dev/null @@ -1,55 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2019 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import os -import subprocess - -from .constants import BOTS_DIR, BASE_DIR, GIT_DIR - -def get_git_config(variable): - if not os.path.exists(GIT_DIR): - return None - - try: - myenv = os.environ.copy() - myenv["GIT_DIR"] = GIT_DIR - return subprocess.check_output(["git", "config", variable], universal_newlines=True, env=myenv).strip() - - except (OSError, subprocess.CalledProcessError): # 'git' not in PATH, or cmd fails - return None - -_images_data_dir = None -def get_images_data_dir(): - global _images_data_dir - - if _images_data_dir is None: - _images_data_dir = get_git_config('cockpit.bots.images-data-dir') - - if _images_data_dir is None: - _images_data_dir = os.path.join(os.environ.get("TEST_DATA", BOTS_DIR), "images") - - return _images_data_dir - -_temp_dir = None -def get_temp_dir(): - global _temp_dir - - if _temp_dir is None: - _temp_dir = os.path.join(os.environ.get("TEST_DATA", BASE_DIR), "tmp") - os.makedirs(_temp_dir, exist_ok=True) - - return _temp_dir diff --git a/bots/machine/machine_core/exceptions.py b/bots/machine/machine_core/exceptions.py deleted file mode 100644 index e7daa8431..000000000 --- a/bots/machine/machine_core/exceptions.py +++ /dev/null @@ -1,27 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -class Failure(Exception): - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return self.msg - - -class RepeatableFailure(Failure): - pass diff --git a/bots/machine/machine_core/machine.py b/bots/machine/machine_core/machine.py deleted file mode 100644 index 4e6f2f1ef..000000000 --- a/bots/machine/machine_core/machine.py +++ /dev/null @@ -1,280 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import os - -from . import ssh_connection -from . import timeout -from .constants import DEFAULT_IDENTITY_FILE, ATOMIC_IMAGES, TEST_DIR - -LOGIN_MESSAGE = """ -TTY LOGIN - User: {ssh_user}/admin Password: foobar - To quit use Ctrl+], Ctrl+5 (depending on locale) - -SSH ACCESS - $ ssh -p {ssh_port} {ssh_user}@{ssh_address} - Password: foobar - -COCKPIT - http://{web_address}:{web_port} -""" - -RESOLV_SCRIPT = """ -set -e -# HACK: Racing with operating systems reading/updating resolv.conf and -# the fact that resolv.conf can be a symbolic link. Avoid failures like: -# chattr: Operation not supported while reading flags on /etc/resolv.conf -mkdir -p /etc/NetworkManager/conf.d -printf '[main]\ndns=none\n' > /etc/NetworkManager/conf.d/dns.conf -systemctl reload-or-restart NetworkManager -printf 'domain {domain}\nsearch {domain}\nnameserver {nameserver}\n' >/etc/resolv2.conf -chcon -v unconfined_u:object_r:net_conf_t:s0 /etc/resolv2.conf 2> /dev/null || true -mv /etc/resolv2.conf /etc/resolv.conf -""" - - -class Machine(ssh_connection.SSHConnection): - def __init__(self, address="127.0.0.1", image="unknown", verbose=False, label=None, browser=None, - user="root", identity_file=None, arch="x86_64", ssh_port=22, web_port=9090): - - identity_file_old = identity_file - identity_file = identity_file or DEFAULT_IDENTITY_FILE - - if identity_file_old is None: - os.chmod(identity_file, 0o600) - if ":" in address: - (ssh_address, unused, ssh_port) = address.rpartition(":") - else: - ssh_address = address - ssh_port = ssh_port - if not browser: - browser = address - - super(Machine, self).__init__(user, ssh_address, ssh_port, identity_file, verbose=verbose) - - self.arch = arch - self.image = image - self.atomic_image = self.image in ATOMIC_IMAGES - if ":" in browser: - (self.web_address, unused, self.web_port) = browser.rpartition(":") - else: - self.web_address = browser - self.web_port = web_port - if label: - self.label = label - elif self.image is not "unknown": - self.label = "{}-{}-{}".format(self.image, self.ssh_address, self.ssh_port) - else: - self.label = "{}@{}:{}".format(self.ssh_user, self.ssh_address, self.ssh_port) - - # The Linux kernel boot_id - self.boot_id = None - - def diagnose(self): - keys = { - "ssh_user": self.ssh_user, - "ssh_address": self.ssh_address, - "ssh_port": self.ssh_port, - "web_address": self.web_address, - "web_port": self.web_port, - } - return LOGIN_MESSAGE.format(**keys) - - def start(self): - """Overridden by machine classes to start the machine""" - self.message("Assuming machine is already running") - - def stop(self): - """Overridden by machine classes to stop the machine""" - self.message("Not shutting down already running machine") - - def wait_poweroff(self): - """Overridden by machine classes to wait for a machine to stop""" - assert False, "Cannot wait for a machine we didn't start" - - def kill(self): - """Overridden by machine classes to unconditionally kill the running machine""" - assert False, "Cannot kill a machine we didn't start" - - def shutdown(self): - """Overridden by machine classes to gracefully shutdown the running machine""" - assert False, "Cannot shutdown a machine we didn't start" - - def upload(self, sources, dest, relative_dir=TEST_DIR): - """Upload a file into the test machine - - Arguments: - sources: the array of paths of the file to upload - dest: the file path in the machine to upload to - """ - super(Machine, self).upload(sources, dest, relative_dir) - - def download(self, source, dest, relative_dir=TEST_DIR): - """Download a file from the test machine. - """ - super(Machine, self).download(source, dest, relative_dir) - - def download_dir(self, source, dest, relative_dir=TEST_DIR): - """Download a directory from the test machine, recursively. - """ - super(Machine, self).download_dir(source, dest, relative_dir) - - def journal_cursor(self): - """Return current journal cursor - - This can be passed to journal_messages() or audit_messages(). - """ - return self.execute("journalctl --show-cursor -n0 -o cat | sed 's/^.*cursor: *//'") - - def journal_messages(self, syslog_ids, log_level, cursor=None): - """Return interesting journal messages""" - - # give the OS some time to write pending log messages, to make - # unexpected message detection more reliable; RHEL/CentOS 7 does not - # yet know about --sync, so ignore failures - self.execute("journalctl --sync 2>/dev/null || true; sleep 3; journalctl --sync 2>/dev/null || true") - - # Journald does not always set trusted fields like - # _SYSTEMD_UNIT or _EXE correctly for the last few messages of - # a dying process, so we filter by the untrusted but reliable - # SYSLOG_IDENTIFIER instead - - matches = " ".join(map(lambda id: "SYSLOG_IDENTIFIER=" + id, syslog_ids)) - - # Some versions of journalctl terminate unsuccessfully when - # the output is empty. We work around this by ignoring the - # exit status and including error messages from journalctl - # itself in the returned messages. - - if cursor: - cursor_arg = "--cursor '%s'" % cursor - else: - cursor_arg = "" - - cmd = "journalctl 2>&1 %s -o cat -p %d %s || true" % (cursor_arg, log_level, matches) - messages = self.execute(cmd).splitlines() - if len(messages) == 1 and ("Cannot assign requested address" in messages[0] - or "-- No entries --" in messages[0]): - # No messages - return [ ] - else: - return messages - - def audit_messages(self, type_pref, cursor=None): - if cursor: - cursor_arg = "--cursor '%s'" % cursor - else: - cursor_arg = "" - - cmd = "journalctl %s -o cat SYSLOG_IDENTIFIER=kernel 2>&1 | grep 'type=%s.*audit' || true" % (cursor_arg, type_pref, ) - messages = self.execute(cmd).splitlines() - if len(messages) == 1 and "Cannot assign requested address" in messages[0]: - messages = [ ] - return messages - - def get_admin_group(self): - if "debian" in self.image or "ubuntu" in self.image: - return "sudo" - else: - return "wheel" - - def start_cockpit(self, atomic_wait_for_host=None, tls=False): - """Start Cockpit. - - Cockpit is not running when the test virtual machine starts up, to - allow you to make modifications before it starts. - """ - - if self.atomic_image: - # HACK: https://bugzilla.redhat.com/show_bug.cgi?id=1228776 - # we want to run: - # self.execute("atomic run cockpit/ws --no-tls") - # but atomic doesn't forward the parameter, so we use the resulting command - # also we need to wait for cockpit to be up and running - cmd = """#!/bin/sh - systemctl start docker && - """ - if tls: - cmd += "/usr/bin/docker run -d --privileged --pid=host -v /:/host cockpit/ws /container/atomic-run --local-ssh\n" - else: - cmd += "/usr/bin/docker run -d --privileged --pid=host -v /:/host cockpit/ws /container/atomic-run --local-ssh --no-tls\n" - with timeout.Timeout(seconds=90, error_message="Timeout while waiting for cockpit/ws to start"): - self.execute(script=cmd) - self.wait_for_cockpit_running(atomic_wait_for_host or "localhost") - elif tls: - self.execute(script="""#!/bin/sh - rm -f /etc/systemd/system/cockpit.service.d/notls.conf && - systemctl daemon-reload && - systemctl stop cockpit.service && - systemctl start cockpit.socket - """) - else: - self.execute(script="""#!/bin/sh - mkdir -p /etc/systemd/system/cockpit.service.d/ && - rm -f /etc/systemd/system/cockpit.service.d/notls.conf && - printf \"[Service]\nExecStartPre=-/bin/sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'\nExecStart=\n%s --no-tls\n\" `systemctl cat cockpit.service | grep ExecStart=` > /etc/systemd/system/cockpit.service.d/notls.conf && - systemctl daemon-reload && - systemctl stop cockpit.service && - systemctl start cockpit.socket - """) - - def restart_cockpit(self): - """Restart Cockpit. - """ - if self.atomic_image: - with timeout.Timeout(seconds=90, error_message="timeoutlib.Timeout while waiting for cockpit/ws to restart"): - self.execute("docker restart `docker ps | grep cockpit/ws | awk '{print $1;}'`") - self.wait_for_cockpit_running() - else: - self.execute("systemctl restart cockpit") - - def stop_cockpit(self): - """Stop Cockpit. - """ - if self.atomic_image: - with timeout.Timeout(seconds=60, error_message="Timeout while waiting for cockpit/ws to stop"): - self.execute("docker kill `docker ps | grep cockpit/ws | awk '{print $1;}'`") - else: - self.execute("systemctl stop cockpit.socket cockpit.service") - - def set_address(self, address, mac='52:54:01'): - """Set IP address for the network interface with given mac prefix""" - cmd = "nmcli con add type ethernet autoconnect yes con-name static-{mac} ifname \"$(grep -l '{mac}' /sys/class/net/*/address | cut -d / -f 5)\" ip4 {address} && ( nmcli conn up static-{mac} || true )" - self.execute(cmd.format(mac=mac, address=address)) - - def set_dns(self, nameserver=None, domain=None): - self.execute(RESOLV_SCRIPT.format(nameserver=nameserver or "127.0.0.1", domain=domain or "cockpit.lan")) - - def dhcp_server(self, mac='52:54:01', range=['10.111.112.2', '10.111.127.254']): - """Sets up a DHCP server on the interface""" - cmd = "dnsmasq --domain=cockpit.lan --interface=\"$(grep -l '{mac}' /sys/class/net/*/address | cut -d / -f 5)\" --bind-dynamic --dhcp-range=" + ','.join(range) + " && firewall-cmd --add-service=dhcp" - self.execute(cmd.format(mac=mac)) - - def dns_server(self, mac='52:54:01'): - """Sets up a DNS server on the interface""" - cmd = "dnsmasq --domain=cockpit.lan --interface=\"$(grep -l '{mac}' /sys/class/net/*/address | cut -d / -f 5)\" --bind-dynamic" - self.execute(cmd.format(mac=mac)) - - def wait_for_cockpit_running(self, address="localhost", port=9090, seconds=30, tls=False): - WAIT_COCKPIT_RUNNING = """#!/bin/sh - until curl --insecure --silent --connect-timeout 2 --max-time 3 %s://%s:%s >/dev/null; do - sleep 0.5; - done; - """ % (tls and "https" or "http", address, port) - with timeout.Timeout(seconds=seconds, error_message="Timeout while waiting for cockpit to start"): - self.execute(script=WAIT_COCKPIT_RUNNING) diff --git a/bots/machine/machine_core/machine_virtual.py b/bots/machine/machine_core/machine_virtual.py deleted file mode 100644 index 804525f3d..000000000 --- a/bots/machine/machine_core/machine_virtual.py +++ /dev/null @@ -1,686 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import contextlib -import errno -import fcntl -import libvirt -import libvirt_qemu -import os -import string -import socket -import subprocess -import tempfile -import sys -import time - -from .exceptions import Failure, RepeatableFailure -from .machine import Machine -from .constants import TEST_DIR, BOTS_DIR -from .directories import get_temp_dir - -MEMORY_MB = 1024 - - -# The Atomic variants can't build their own packages, so we build in -# their non-Atomic siblings. For example, fedora-atomic is built -# in fedora-29 -def get_build_image(image): - (test_os, unused) = os.path.splitext(os.path.basename(image)) - if test_os == "fedora-atomic": - image = "fedora-29" - elif test_os == "rhel-atomic": - image = "rhel-7-7" - elif test_os == "continuous-atomic": - image = "centos-7" - return image - - -# some tests have suffixes that run the same image in different modes; map a -# test context image to an actual physical image name -def get_test_image(image): - return image.replace("-distropkg", "") - - -# based on http://stackoverflow.com/a/17753573 -# we use this to quieten down calls -@contextlib.contextmanager -def stdchannel_redirected(stdchannel, dest_filename): - """ - A context manager to temporarily redirect stdout or stderr - e.g.: - with stdchannel_redirected(sys.stderr, os.devnull): - noisy_function() - """ - try: - stdchannel.flush() - oldstdchannel = os.dup(stdchannel.fileno()) - dest_file = open(dest_filename, 'w') - os.dup2(dest_file.fileno(), stdchannel.fileno()) - yield - finally: - if oldstdchannel is not None: - os.dup2(oldstdchannel, stdchannel.fileno()) - if dest_file is not None: - dest_file.close() - - -TEST_CONSOLE_XML=""" - - - -""" - -TEST_GRAPHICS_XML=""" -
- - - - -""" - -TEST_DOMAIN_XML=""" - - {label} - {cpu} - - hvm - - {loader} - - {memory_in_mib} - {memory_in_mib} - - - - - - - - - ROOT - - - {console} - - - - - - - /dev/urandom - - {bridgedev} - - - {ethernet} - {redir} - - -""" - -TEST_DISK_XML=""" - - - - %(serial)s -
- - -""" - -TEST_KVM_XML=""" - - {cpus} -""" - -# The main network interface which we use to communicate between VMs -TEST_MCAST_XML=""" - - - - -""" - -TEST_BRIDGE_XML=""" - - - - - -""" - -# Used to access SSH from the main host into the virtual machines -TEST_REDIR_XML=""" - - - - -""" - -class VirtNetwork: - def __init__(self, network=None, bridge=None, image="generic"): - self.locked = [ ] - self.bridge = bridge - self.image = image - - if network is None: - offset = 0 - force = False - else: - offset = network * 100 - force = True - - # This is a shared port used as the identifier for the socket mcast network - self.network = self._lock(5500 + offset, step=100, force=force) - - # An offset for other ports allocated later - self.offset = (self.network - 5500) - - # The last machine we allocated - self.last = 0 - - # Unique hostnet identifiers - self.hostnet = 8 - - def _lock(self, start, step=1, force=False): - resources = os.path.join(tempfile.gettempdir(), ".cockpit-test-resources") - try: - os.mkdir(resources, 0o755) - except FileExistsError: - pass - for port in range(start, start + (100 * step), step): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - lockpath = os.path.join(resources, "network-{0}".format(port)) - try: - lockf = os.open(lockpath, os.O_WRONLY | os.O_CREAT) - fcntl.flock(lockf, fcntl.LOCK_NB | fcntl.LOCK_EX) - sock.bind(("127.0.0.1", port)) - self.locked.append(lockf) - except IOError: - if force: - return port - os.close(lockf) - continue - else: - return port - finally: - sock.close() - raise Failure("Couldn't find unique network port number") - - # Create resources for an interface, returns address and XML - def interface(self, number=None): - if number is None: - number = self.last + 1 - if number > self.last: - self.last = number - mac = self._lock(10000 + self.offset + number) - (10000 + self.offset) - hostnet = self.hostnet - self.hostnet += 1 - result = { - "number": self.offset + number, - "mac": '52:54:01:{:02x}:{:02x}:{:02x}'.format((mac >> 16) & 0xff, (mac >> 8) & 0xff, mac & 0xff), - "name": "m{0}.cockpit.lan".format(mac), - "mcast": self.network, - "hostnet": "hostnet{0}".format(hostnet) - } - return result - - # Create resources for a host, returns address and XML - def host(self, number=None, restrict=False, isolate=False, forward={ }): - result = self.interface(number) - result["mcast"] = self.network - result["restrict"] = restrict and "on" or "off" - result["forward"] = { "22": 2200, "9090": 9090 } - result["forward"].update(forward) - result["netdriver"] = ("windows" in self.image) and "rtl8139" or "virtio-net-pci" - forwards = [] - for remote, local in result["forward"].items(): - local = self._lock(int(local) + result["number"]) - result["forward"][remote] = "127.0.0.2:{}".format(local) - forwards.append("hostfwd=tcp:{}-:{}".format(result["forward"][remote], remote)) - if remote == "22": - result["control"] = result["forward"][remote] - elif remote == "9090": - result["browser"] = result["forward"][remote] - - if isolate: - result["bridge"] = "" - result["bridgedev"] = "" - result["ethernet"] = "" - elif self.bridge: - result["bridge"] = self.bridge - result["bridgedev"] = TEST_BRIDGE_XML.format(**result) - result["ethernet"] = "" - else: - result["bridge"] = "" - result["bridgedev"] = "" - result["ethernet"] = TEST_MCAST_XML.format(**result) - result["forwards"] = ",".join(forwards) - result["redir"] = TEST_REDIR_XML.format(**result) - return result - - def kill(self): - locked = self.locked - self.locked = [ ] - for x in locked: - os.close(x) - -class VirtMachine(Machine): - network = None - memory_mb = None - cpus = None - - def __init__(self, image, networking=None, maintain=False, memory_mb=None, cpus=None, graphics=False, **args): - self.maintain = maintain - - # Currently all images are run on x86_64. When that changes we will have - # an override file for those images that are not - self.arch = "x86_64" - - self.memory_mb = memory_mb or VirtMachine.memory_mb or MEMORY_MB - self.cpus = cpus or VirtMachine.cpus or 1 - self.graphics = graphics or "windows" in image - - # Set up some temporary networking info if necessary - if networking is None: - networking = VirtNetwork(image=image).host() - - # Allocate network information about this machine - self.networking = networking - args["address"] = networking["control"] - args["browser"] = networking["browser"] - self.forward = networking["forward"] - - # The path to the image file to load, and parse an image name - if "/" in image: - self.image_file = image = os.path.abspath(image) - else: - self.image_file = os.path.join(TEST_DIR, "images", image) - if not os.path.lexists(self.image_file): - self.image_file = os.path.join(BOTS_DIR, "images", image) - (image, extension) = os.path.splitext(os.path.basename(image)) - - Machine.__init__(self, image=image, **args) - - base_dir = os.path.dirname(BOTS_DIR) - self.run_dir = os.path.join(get_temp_dir(), "run") - - self.virt_connection = self._libvirt_connection(hypervisor = "qemu:///session") - - self._disks = [ ] - self._domain = None - - # init variables needed for running a vm - self._cleanup() - - def _libvirt_connection(self, hypervisor, read_only = False): - tries_left = 5 - connection = None - if read_only: - open_function = libvirt.openReadOnly - else: - open_function = libvirt.open - while not connection and (tries_left > 0): - try: - connection = open_function(hypervisor) - except: - # wait a bit - time.sleep(1) - pass - tries_left -= 1 - if not connection: - # try again, but if an error occurs, don't catch it - connection = open_function(hypervisor) - return connection - - def _start_qemu(self): - self._cleanup() - - os.makedirs(self.run_dir, 0o750, exist_ok=True) - - def execute(*args): - self.message(*args) - return subprocess.check_call(args) - - image_to_use = self.image_file - if not self.maintain: - (unused, self._transient_image) = tempfile.mkstemp(suffix='.qcow2', prefix="", dir=self.run_dir) - execute("qemu-img", "create", "-q", "-f", "qcow2", - "-o", "backing_file=%s" % self.image_file, self._transient_image) - image_to_use = self._transient_image - - keys = { - "label": self.label, - "image": self.image, - "type": "qemu", - "arch": self.arch, - "cpu": "", - "cpus": self.cpus, - "memory_in_mib": self.memory_mb, - "drive": image_to_use, - "iso": os.path.join(BOTS_DIR, "machine", "cloud-init.iso"), - } - - if os.path.exists("/dev/kvm"): - keys["type"] = "kvm" - keys["cpu"] = TEST_KVM_XML.format(**keys) - else: - sys.stderr.write("WARNING: Starting virtual machine with emulation due to missing KVM\n") - sys.stderr.write("WARNING: Machine will run about 10-20 times slower\n") - - keys.update(self.networking) - keys["name"] = "{image}-{control}".format(**keys) - - # No need or use for redir network on windows - if "windows" in self.image: - keys["disk"] = "ide" - keys["redir"] = "" - else: - keys["disk"] = "virtio" - if self.graphics: - keys["console"] = TEST_GRAPHICS_XML.format(**keys) - else: - keys["console"] = TEST_CONSOLE_XML.format(**keys) - if "windows-10" in self.image: - keys["loader"] = "/usr/share/edk2/ovmf/OVMF_CODE.fd" - else: - keys["loader"] = "" - test_domain_desc = TEST_DOMAIN_XML.format(**keys) - - # add the virtual machine - try: - # print >> sys.stderr, test_domain_desc - self._domain = self.virt_connection.createXML(test_domain_desc, libvirt.VIR_DOMAIN_START_AUTODESTROY) - except libvirt.libvirtError as le: - if 'already exists with uuid' in str(le): - raise RepeatableFailure("libvirt domain already exists: " + str(le)) - else: - raise - - # start virsh console - def qemu_console(self, extra_message=""): - self.message("Started machine {0}".format(self.label)) - if self.maintain: - message = "\nWARNING: Uncontrolled shutdown can lead to a corrupted image\n" - else: - message = "\nWARNING: All changes are discarded, the image file won't be changed\n" - message += self.diagnose() + extra_message + "\nlogin: " - message = message.replace("\n", "\r\n") - - try: - proc = subprocess.Popen("virsh -c qemu:///session console %s" % str(self._domain.ID()), shell=True) - - # Fill in information into /etc/issue about login access - pid = 0 - while pid == 0: - if message: - try: - with stdchannel_redirected(sys.stderr, os.devnull): - Machine.wait_boot(self) - sys.stderr.write(message) - except (Failure, subprocess.CalledProcessError): - pass - message = None - (pid, ret) = os.waitpid(proc.pid, message and os.WNOHANG or 0) - - try: - if self.maintain: - self.shutdown() - else: - self.kill() - except libvirt.libvirtError as le: - # the domain may have already been freed (shutdown) while the console was running - self.message("libvirt error during shutdown: %s" % (le.get_error_message())) - - except OSError as ex: - raise Failure("Failed to launch virsh command: {0}".format(ex.strerror)) - finally: - self._cleanup() - - def graphics_console(self): - self.message("Started machine {0}".format(self.label)) - if self.maintain: - message = "\nWARNING: Uncontrolled shutdown can lead to a corrupted image\n" - else: - message = "\nWARNING: All changes are discarded, the image file won't be changed\n" - if "bridge" in self.networking: - message += "\nIn the machine a web browser can access Cockpit on parent host:\n\n" - message += " https://10.111.112.1:9090\n" - message = message.replace("\n", "\r\n") - - try: - proc = subprocess.Popen(["virt-viewer", str(self._domain.ID())]) - sys.stderr.write(message) - proc.wait() - except OSError as ex: - raise Failure("Failed to launch virt-viewer command: {0}".format(ex.strerror)) - finally: - self._cleanup() - - def pull(self, image): - if "/" in image: - image_file = os.path.abspath(image) - else: - image_file = os.path.join(BOTS_DIR, "images", image) - if not os.path.exists(image_file): - try: - subprocess.check_call([ os.path.join(BOTS_DIR, "image-download"), image_file ]) - except OSError as ex: - if ex.errno != errno.ENOENT: - raise - return image_file - - def start(self): - tries = 0 - while True: - try: - self._start_qemu() - if not self._domain.isActive(): - self._domain.start() - except RepeatableFailure: - self.kill() - if tries < 10: - tries += 1 - time.sleep(tries) - continue - else: - raise - except: - self.kill() - raise - - # Normally only one pass - break - - def _diagnose_no_address(self): - SCRIPT = """ - spawn virsh -c qemu:///session console $argv - set timeout 300 - expect "Escape character" - send "\r" - expect " login: " - send "root\r" - expect "Password: " - send "foobar\r" - expect " ~]# " - send "ip addr\r\n" - expect " ~]# " - exit 0 - """ - expect = subprocess.Popen(["expect", "--", "-", str(self._domain.ID())], stdin=subprocess.PIPE, - universal_newlines=True) - expect.communicate(SCRIPT) - - def wait_boot(self, timeout_sec=120): - """Wait for a machine to boot""" - try: - Machine.wait_boot(self, timeout_sec) - except Failure: - self._diagnose_no_address() - raise - - def stop(self, timeout_sec=120): - if self.maintain: - self.shutdown(timeout_sec=timeout_sec) - else: - self.kill() - - def _cleanup(self, quick=False): - self.disconnect() - try: - for disk in self._disks: - self.rem_disk(disk, quick) - - self._domain = None - if hasattr(self, '_transient_image') and self._transient_image and os.path.exists(self._transient_image): - os.unlink(self._transient_image) - except: - (type, value, traceback) = sys.exc_info() - sys.stderr.write("WARNING: Cleanup failed:%s\n" % value) - - def kill(self): - # stop system immediately, with potential data loss - # to shutdown gracefully, use shutdown() - try: - self.disconnect() - except Exception: - pass - if self._domain: - try: - # not graceful - with stdchannel_redirected(sys.stderr, os.devnull): - self._domain.destroyFlags(libvirt.VIR_DOMAIN_DESTROY_DEFAULT) - except: - pass - self._cleanup(quick=True) - - def wait_poweroff(self, timeout_sec=120): - # shutdown must have already been triggered - if self._domain: - start_time = time.time() - while (time.time() - start_time) < timeout_sec: - try: - with stdchannel_redirected(sys.stderr, os.devnull): - if not self._domain.isActive(): - break - except libvirt.libvirtError as le: - if 'no domain' in str(le) or 'not found' in str(le): - break - raise - time.sleep(1) - else: - raise Failure("Waiting for machine poweroff timed out") - try: - with stdchannel_redirected(sys.stderr, os.devnull): - self._domain.destroyFlags(libvirt.VIR_DOMAIN_DESTROY_DEFAULT) - except libvirt.libvirtError as le: - if 'not found' not in str(le): - raise - self._cleanup(quick=True) - - def shutdown(self, timeout_sec=120): - # shutdown the system gracefully - # to stop it immediately, use kill() - self.disconnect() - try: - if self._domain: - self._domain.shutdown() - self.wait_poweroff(timeout_sec=timeout_sec) - finally: - self._cleanup() - - def add_disk(self, size=None, serial=None, path=None, type='raw'): - index = len(self._disks) - - os.makedirs(self.run_dir, 0o750, exist_ok=True) - - if path: - (unused, image) = tempfile.mkstemp(suffix='.qcow2', prefix=os.path.basename(path), dir=self.run_dir) - subprocess.check_call([ "qemu-img", "create", "-q", "-f", "qcow2", - "-o", "backing_file=" + os.path.realpath(path), image ]) - - else: - assert size is not None - name = "disk-{0}".format(self._domain.name()) - (unused, image) = tempfile.mkstemp(suffix='qcow2', prefix=name, dir=self.run_dir) - subprocess.check_call(["qemu-img", "create", "-q", "-f", "raw", image, str(size)]) - - if not serial: - serial = "DISK{0}".format(index) - dev = 'sd' + string.ascii_lowercase[index] - disk_desc = TEST_DISK_XML % { - 'file': image, - 'serial': serial, - 'unit': index, - 'dev': dev, - 'type': type, - } - - if self._domain.attachDeviceFlags(disk_desc, libvirt.VIR_DOMAIN_AFFECT_LIVE) != 0: - raise Failure("Unable to add disk to vm") - - disk = { - "path": image, - "serial": serial, - "filename": image, - "dev": dev, - "index": index, - "type": type, - } - - self._disks.append(disk) - return disk - - def rem_disk(self, disk, quick=False): - if not quick: - disk_desc = TEST_DISK_XML % { - 'file': disk["filename"], - 'serial': disk["serial"], - 'unit': disk["index"], - 'dev': disk["dev"], - 'type': disk["type"] - } - - if self._domain: - if self._domain.detachDeviceFlags(disk_desc, libvirt.VIR_DOMAIN_AFFECT_LIVE ) != 0: - raise Failure("Unable to remove disk from vm") - - def _qemu_monitor(self, command): - self.message("& " + command) - # you can run commands manually using virsh: - # virsh -c qemu:///session qemu-monitor-command [domain name/id] --hmp [command] - output = libvirt_qemu.qemuMonitorCommand(self._domain, command, libvirt_qemu.VIR_DOMAIN_QEMU_MONITOR_COMMAND_HMP) - self.message(output.strip()) - return output - - def add_netiface(self, networking=None): - if not networking: - networking = VirtNetwork(image=self.image).interface() - self._qemu_monitor("netdev_add socket,mcast=230.0.0.1:{mcast},id={id}".format(mcast=networking["mcast"], id=networking["hostnet"])) - cmd = "device_add virtio-net-pci,mac={0},netdev={1}".format(networking["mac"], networking["hostnet"]) - self._qemu_monitor("device_add virtio-net-pci,mac={0},netdev={1}".format(networking["mac"], networking["hostnet"])) - return networking["mac"] - - def needs_writable_usr(self): - # On atomic systems, we need a hack to change files in /usr/lib/systemd - if self.atomic_image: - self.execute(command="mount -o remount,rw /usr") diff --git a/bots/machine/machine_core/ssh_connection.py b/bots/machine/machine_core/ssh_connection.py deleted file mode 100644 index f20f69572..000000000 --- a/bots/machine/machine_core/ssh_connection.py +++ /dev/null @@ -1,462 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - - -import os -import time -import socket -import subprocess -import tempfile -import select -import errno -import sys - -from . import exceptions -from . import timeout as timeoutlib -from .directories import get_temp_dir - - -class SSHConnection(object): - def __init__(self, user, address, ssh_port, identity_file, verbose=False): - self.verbose = verbose - - # Currently all images are x86_64. When that changes we will have - # an override file for those images that are not - self.ssh_user = user - self.identity_file = identity_file - self.ssh_address = address - self.ssh_port = ssh_port - self.ssh_master = None - self.ssh_process = None - self.ssh_reachable = False - self.label = "{}@{}:{}".format(self.ssh_user, self.ssh_address, self.ssh_port) - - def disconnect(self): - self.ssh_reachable = False - self._kill_ssh_master() - - def message(self, *args): - """Prints args if in verbose mode""" - if not self.verbose: - return - print(" ".join(args)) - - # wait until we can execute something on the machine. ie: wait for ssh - def wait_execute(self, timeout_sec=120): - """Try to connect to self.address on ssh port""" - - # If connected to machine, kill master connection - self._kill_ssh_master() - - start_time = time.time() - while (time.time() - start_time) < timeout_sec: - addrinfo = socket.getaddrinfo(self.ssh_address, self.ssh_port, 0, socket.SOCK_STREAM) - (family, socktype, proto, canonname, sockaddr) = addrinfo[0] - sock = socket.socket(family, socktype, proto) - sock.settimeout(5) - try: - sock.connect(sockaddr) - data = sock.recv(10) - if len(data): - self.ssh_reachable = True - return True - except IOError: - pass - finally: - sock.close() - time.sleep(0.5) - return False - - def wait_user_login(self): - """Wait until logging in as non-root works. - - Most tests run as the "admin" user, so we make sure that - user sessions are allowed (and cockit-ws will let "admin" - in) before declaring a test machine as "booted". - - Returns the boot id of the system, or None if ssh timed out. - """ - tries_left = 60 - while (tries_left > 0): - try: - with timeoutlib.Timeout(seconds=30): - return self.execute("! test -f /run/nologin && cat /proc/sys/kernel/random/boot_id", direct=True) - except subprocess.CalledProcessError: - pass - except RuntimeError: - # timeout; assume that ssh just went down during reboot, go back to wait_boot() - return None - tries_left = tries_left - 1 - time.sleep(1) - raise exceptions.Failure("Timed out waiting for /run/nologin to disappear") - - def wait_boot(self, timeout_sec=120): - """Wait for a machine to boot""" - start_time = time.time() - boot_id = None - while (time.time() - start_time) < timeout_sec: - if self.wait_execute(timeout_sec=15): - boot_id = self.wait_user_login() - if boot_id: - break - if not boot_id: - raise exceptions.Failure("Unable to reach machine {0} via ssh: {1}:{2}".format(self.label, self.ssh_address, self.ssh_port)) - self.boot_id = boot_id - - def wait_reboot(self, timeout_sec=180): - self.disconnect() - assert self.boot_id, "Before using wait_reboot() use wait_boot() successfully" - boot_id = self.boot_id - start_time = time.time() - while (time.time() - start_time) < timeout_sec: - try: - self.wait_boot(timeout_sec=timeout_sec) - if self.boot_id != boot_id: - break - except exceptions.Failure: - pass - else: - raise exceptions.Failure("Timeout waiting for system to reboot properly") - - - def _start_ssh_master(self): - self._kill_ssh_master() - - control = os.path.join(get_temp_dir(), "ssh-%h-%p-%r-" + str(os.getpid())) - - cmd = [ - "ssh", - "-p", str(self.ssh_port), - "-i", self.identity_file, - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "BatchMode=yes", - "-M", # ControlMaster, no stdin - "-o", "ControlPath=" + control, - "-o", "LogLevel=ERROR", - "-l", self.ssh_user, - self.ssh_address, - "/bin/bash -c 'echo READY; read a'" - ] - - # Connection might be refused, so try this 10 times - tries_left = 10 - while tries_left > 0: - tries_left = tries_left - 1 - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) - stdout_fd = proc.stdout.fileno() - output = "" - while stdout_fd > -1 and "READY" not in output: - ret = select.select([stdout_fd], [], [], 10) - for fd in ret[0]: - if fd == stdout_fd: - data = os.read(fd, 1024) - if not data: - stdout_fd = -1 - proc.stdout.close() - output += data.decode('utf-8', 'replace') - - if stdout_fd > -1: - break - - # try again if the connection was refused, unless we've used up our tries - proc.wait() - if proc.returncode == 255 and tries_left > 0: - self.message("ssh: connection refused, trying again") - time.sleep(1) - continue - else: - raise exceptions.Failure("SSH master process exited with code: {0}".format(proc.returncode)) - - self.ssh_master = control - self.ssh_process = proc - - if not self._check_ssh_master(): - raise exceptions.Failure("Couldn't launch an SSH master process") - - def _kill_ssh_master(self): - if self.ssh_master: - try: - os.unlink(self.ssh_master) - except OSError as e: - if e.errno != errno.ENOENT: - raise - self.ssh_master = None - if self.ssh_process: - self.message("killing ssh master process", str(self.ssh_process.pid)) - self.ssh_process.stdin.close() - self.ssh_process.terminate() - self.ssh_process.stdout.close() - with timeoutlib.Timeout(seconds=90, error_message="Timeout while waiting for ssh master to shut down"): - self.ssh_process.wait() - self.ssh_process = None - - def _check_ssh_master(self): - if not self.ssh_master: - return False - cmd = [ - "ssh", - "-q", - "-p", str(self.ssh_port), - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "BatchMode=yes", - "-S", self.ssh_master, - "-O", "check", - "-l", self.ssh_user, - self.ssh_address - ] - with open(os.devnull, 'w') as devnull: - code = subprocess.call(cmd, stdin=devnull, stdout=devnull, stderr=devnull) - if code == 0: - self.ssh_reachable = True - return True - return False - - def _ensure_ssh_master(self): - if not self._check_ssh_master(): - self._start_ssh_master() - - def execute(self, command=None, script=None, input=None, environment={}, - stdout=None, quiet=False, direct=False, timeout=120, - ssh_env=["env", "-u", "LANGUAGE", "LC_ALL=C"]): - """Execute a shell command in the test machine and return its output. - - Either specify @command or @script - - Arguments: - command: The string to execute by /bin/sh. - script: A multi-line script to execute in /bin/sh - input: Input to send to the command - environment: Additional environment variables - timeout: Applies if not already wrapped in a #Timeout context - Returns: - The command/script output as a string. - """ - assert command or script - assert self.ssh_address - - if not direct: - self._ensure_ssh_master() - - env_script = "" - env_command = [] - if environment and isinstance(environment, dict): - for name, value in environment.items(): - env_script += "%s='%s'\n" % (name, value) - env_script += "export %s\n" % name - env_command.append("{}={}".format(name, value)) - elif environment == {}: - pass - else: - raise Exception("enviroment support dict or list items given: ".format(environment)) - default_ssh_params = [ - "ssh", - "-p", str(self.ssh_port), - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "LogLevel=ERROR", - "-o", "BatchMode=yes", - "-l", self.ssh_user, - self.ssh_address - ] - additional_ssh_params = [] - cmd = [] - - if direct: - additional_ssh_params += ["-i", self.identity_file] - else: - additional_ssh_params += ["-o", "ControlPath=" + self.ssh_master] - - if command: - if getattr(command, "strip", None): # Is this a string? - cmd += [command] - if not quiet: - self.message("+", command) - else: - cmd += command - if not quiet: - self.message("+", *command) - else: - assert not input, "input not supported to script" - cmd += ["sh", "-s"] - if self.verbose: - cmd += ["-x"] - input = env_script - input += script - command = " - - - - - - - - - - - - - - - -

Logs

-

- Result directory
- Raw log -

- -
-
-
- - diff --git a/bots/task/sink.py b/bots/task/sink.py deleted file mode 100644 index 360a67695..000000000 --- a/bots/task/sink.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import json -import os -import shutil -import subprocess -import sys -import tarfile -import tempfile - -__all__ = ( - 'Sink', -) - -BOTS = os.path.join(os.path.dirname(__file__), "..") - -class Sink(object): - def __init__(self, host, identifier, status=None): - self.attachments = tempfile.mkdtemp(prefix="attachments.", dir="/var/tmp") - os.environ["TEST_ATTACHMENTS"] = self.attachments - self.status = status - - # Start a gzip and cat processes - self.ssh = subprocess.Popen([ - "ssh", "-o", "ServerAliveInterval=30", host, "--", - "python", "sink", identifier - ], stdin=subprocess.PIPE) - - # Send the status line - self.ssh.stdin.write(json.dumps(status).encode('utf-8') + b"\n") - self.ssh.stdin.flush() - - # Now dup our own output and errors into the pipeline - sys.stdout.flush() - self.fout = os.dup(1) - os.dup2(self.ssh.stdin.fileno(), 1) - sys.stderr.flush() - self.ferr = os.dup(2) - os.dup2(self.ssh.stdin.fileno(), 2) - - def attach(self, filename): - shutil.copy(filename, self.attachments) - - def flush(self, status=None): - assert self.ssh is not None - - # Reset stdout back - sys.stdout.flush() - os.dup2(self.fout, 1) - os.close(self.fout) - self.fout = -1 - - # Reset stderr back - sys.stderr.flush() - os.dup2(self.ferr, 2) - os.close(self.ferr) - self.ferr = -1 - - # Splice in the github status - if status is None: - status = self.status - if status is not None: - self.ssh.stdin.write(b"\n" + json.dumps(status).encode('utf-8')) - - # Send a zero character and send the attachments - files = os.listdir(self.attachments) - if len(files): - self.ssh.stdin.write(b'\x00') - self.ssh.stdin.flush() - with tarfile.open(name="attachments.tgz", mode="w:gz", fileobj=self.ssh.stdin) as tar: - for filename in files: - tar.add(os.path.join(self.attachments, filename), arcname=filename, recursive=True) - shutil.rmtree(self.attachments) - - # All done sending output - self.ssh.stdin.close() - - # SSH should terminate by itself - ret = self.ssh.wait() - if ret != 0: - raise subprocess.CalledProcessError(ret, "ssh") - self.ssh = None diff --git a/bots/task/test-cache b/bots/task/test-cache deleted file mode 100755 index 80912a4fc..000000000 --- a/bots/task/test-cache +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import os -import tempfile -import time -import unittest - -import cache - -class TestCache(unittest.TestCase): - def setUp(self): - self.directory = tempfile.mkdtemp() - - def tearDown(self): - for name in os.listdir(self.directory): - os.unlink(os.path.join(self.directory, name)) - os.rmdir(self.directory) - - def testReadWrite(self): - value = { "blah": 1 } - - c = cache.Cache(self.directory) - result = c.read("pa+t\%h") - self.assertIsNone(result) - - c.write("pa+t\%h", value) - result = c.read("pa+t\%h") - self.assertEqual(result, value) - - other = "other" - c.write("pa+t\%h", other) - result = c.read("pa+t\%h") - self.assertEqual(result, other) - - c.write("second", value) - result = c.read("pa+t\%h") - self.assertEqual(result, other) - - def testCurrent(self): - c = cache.Cache(self.directory, lag=3) - - c.write("resource2", { "value": 2 }) - self.assertTrue(c.current("resource2")) - - time.sleep(2) - self.assertTrue(c.current("resource2")) - - time.sleep(2) - self.assertFalse(c.current("resource2")) - - def testCurrentMark(self): - c = cache.Cache(self.directory, lag=3) - - self.assertFalse(c.current("resource")) - - c.write("resource", { "value": 1 }) - self.assertTrue(c.current("resource")) - - time.sleep(2) - self.assertTrue(c.current("resource")) - - c.mark() - self.assertFalse(c.current("resource")) - - def testCurrentZero(self): - c = cache.Cache(self.directory, lag=0) - c.write("resource", { "value": 1 }) - self.assertFalse(c.current("resource")) - -if __name__ == '__main__': - unittest.main() diff --git a/bots/task/test-checklist b/bots/task/test-checklist deleted file mode 100755 index d250766ce..000000000 --- a/bots/task/test-checklist +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import os -import sys -import unittest - -BASE = os.path.dirname(__file__) -sys.path.insert(1, os.path.join(BASE, "..")) - -from task import github - -class TestChecklist(unittest.TestCase): - def testParse(self): - parse_line = github.Checklist.parse_line - self.assertEqual(parse_line("blah"), (None, None)) - self.assertEqual(parse_line(""), (None, None)) - self.assertEqual(parse_line(""), (None, None)) - self.assertEqual(parse_line("* [ ] test two"), ("test two", False)) - self.assertEqual(parse_line("- [ ] test two"), ("test two", False)) - self.assertEqual(parse_line(" * [ ] test two "), ("test two", False)) - self.assertEqual(parse_line(" - [ ] test two "), ("test two", False)) - self.assertEqual(parse_line(" - [x] test two "), ("test two", True)) - self.assertEqual(parse_line(" * [x] test two"), ("test two", True)) - self.assertEqual(parse_line(" * [x] FAIL: test two"), ("test two", "FAIL")) - self.assertEqual(parse_line(" * [x] FAIL: test FAIL: two"), ("test FAIL: two", "FAIL")) - self.assertEqual(parse_line(" * [x]test three"), (None, None)) - self.assertEqual(parse_line(" - [X] test two "), ("test two", True)) - self.assertEqual(parse_line(" * [X] test four"), ("test four", True)) - self.assertEqual(parse_line(" * [X] FAIL: test four"), ("test four", "FAIL")) - self.assertEqual(parse_line(" * [X] FAIL: test FAIL: four"), ("test FAIL: four", "FAIL")) - self.assertEqual(parse_line(" * [X]test five"), (None, None)) - - def testFormat(self): - format_line = github.Checklist.format_line - self.assertEqual(format_line("blah", True), " * [x] blah") - self.assertEqual(format_line("blah", False), " * [ ] blah") - self.assertEqual(format_line("blah", "FAIL"), " * [ ] FAIL: blah") - - def testProcess(self): - body = "This is a description\n- [ ] item1\n * [x] Item two\n * [X] Item three\n\nMore lines" - checklist = github.Checklist(body) - self.assertEqual(checklist.body, body) - self.assertEqual(checklist.items, { "item1": False, "Item two": True, "Item three": True }) - - def testCheck(self): - body = "This is a description\n- [ ] item1\n * [x] Item two\n * [X] Item three\n\nMore lines" - checklist = github.Checklist(body) - checklist.check("item1", True) - checklist.check("Item three", False) - self.assertEqual(checklist.body, "This is a description\n * [x] item1\n * [x] Item two\n * [ ] Item three\n\nMore lines") - self.assertEqual(checklist.items, { "item1": True, "Item two": True, "Item three": False }) - - def testDisable(self): - body = "This is a description\n- [ ] item1\n * [x] Item two\n\nMore lines" - checklist = github.Checklist(body) - checklist.check("item1", "Status") - self.assertEqual(checklist.body, "This is a description\n * [ ] Status: item1\n * [x] Item two\n\nMore lines") - self.assertEqual(checklist.items, { "item1": "Status", "Item two": True }) - - def testAdd(self): - body = "This is a description\n- [ ] item1\n * [x] Item two\n\nMore lines" - checklist = github.Checklist(body) - checklist.add("Item three") - self.assertEqual(checklist.body, "This is a description\n- [ ] item1\n * [x] Item two\n\nMore lines\n * [ ] Item three") - self.assertEqual(checklist.items, { "item1": False, "Item two": True, "Item three": False }) - - def testChecked(self): - body = "This is a description\n- [ ] item1\n * [x] Item two\n * [X] Item three\n\nMore lines" - checklist = github.Checklist(body) - checklist.check("item1", True) - checklist.check("Item three", False) - checked = checklist.checked() - self.assertEqual(checklist.items, { "item1": True, "Item two": True, "Item three": False }) - self.assertEqual(checked, { "item1": True, "Item two": True }) - -if __name__ == '__main__': - unittest.main() diff --git a/bots/task/test-github b/bots/task/test-github deleted file mode 100755 index ff46a4b9a..000000000 --- a/bots/task/test-github +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -ADDRESS = ("127.0.0.8", 9898) - -import ctypes -import fnmatch -import json -import os -import signal -import shutil -import sys -import tempfile -import time -import unittest -import urllib.parse - -BASE = os.path.dirname(__file__) -sys.path.insert(1, os.path.join(BASE, "..")) - -from task import cache -from task import github - -def mockServer(): - # Data used by the above handler - data = { - "count": 0, - } - - issues =[{ "number": "5", "state": "open", "created_at": "2011-04-22T13:33:48Z" }, - { "number": "6", "state": "closed", "closed_at": "2011-04-21T13:33:48Z" }, - { "number": "7", "state": "open" }] - - import http.server - class Handler(http.server.BaseHTTPRequestHandler): - - def replyData(self, value, headers={ }, status=200): - self.send_response(status) - for name, content in headers.items(): - self.send_header(name, content) - self.end_headers() - self.wfile.write(value.encode('utf-8')) - self.wfile.flush() - - def replyJson(self, value, headers={ }, status=200): - headers["Content-type"] = "application/json" - self.server.data["count"] += 1 - self.replyData(json.dumps(value), headers=headers, status=status) - - def do_GET(self): - parsed = urllib.parse.urlparse(self.path) - if parsed.path == "/count": - self.replyJson(self.server.data["count"]) - elif parsed.path == "/issues": - self.replyJson(issues) - elif parsed.path == "/test/user": - if self.headers.get("If-None-Match") == "blah": - self.replyData("", status=304) - else: - self.replyJson({ "user": "blah" }, headers={ "ETag": "blah" }) - elif parsed.path == "/test/user/modified": - if self.headers.get("If-Modified-Since") == "Thu, 05 Jul 2012 15:31:30 GMT": - self.replyData("", status=304) - else: - self.replyJson({ "user": "blah" }, headers={ "Last-Modified": "Thu, 05 Jul 2012 15:31:30 GMT" }) - elif parsed.path == "/orgs/cockpit-project/teams": - self.replyJson([ - { "name": github.TEAM_CONTRIBUTORS, "id": 1 }, - { "name": "Other team", "id": 2 } - ]) - elif parsed.path == "/teams/1/members": - self.replyJson([ - { "login": "one" }, - { "login": "two" }, - { "login": "three" } - ]) - else: - self.send_error(404, 'Mock Not Found: ' + parsed.path) - - def do_DELETE(self): - parsed = urllib.parse.urlparse(self.path) - if parsed.path == '/issues/7': - del issues[-1] - self.replyJson({}) - else: - self.send_error(404, 'Mock Not Found: ' + parsed.path) - - httpd = http.server.HTTPServer(ADDRESS, Handler) - httpd.data = data - - child = os.fork() - if child != 0: - httpd.server_close() - return child - - # prctl(PR_SET_PDEATHSIG, SIGTERM) - try: - libc = ctypes.CDLL('libc.so.6') - libc.prctl(1, 15) - except OSError: - pass - - httpd.serve_forever() - os._exit(1) - -def mockKill(child): - os.kill(child, signal.SIGTERM) - os.waitpid(child, 0) - -class TestLogger(object): - def __init__(self): - self.data = "" - - # Yes, we open the file each time - def write(self, value): - self.data = self.data + value - -class TestGitHub(unittest.TestCase): - def setUp(self): - self.child = mockServer() - self.temp = tempfile.mkdtemp() - - def tearDown(self): - mockKill(self.child) - shutil.rmtree(self.temp) - - def testCache(self): - api = github.GitHub("http://127.0.0.8:9898", cacher=cache.Cache(self.temp)) - - values = api.get("/test/user") - cached = api.get("/test/user") - self.assertEqual(json.dumps(values), json.dumps(cached)) - - count = api.get("/count") - self.assertEqual(count, 1) - - def testLog(self): - api = github.GitHub("http://127.0.0.8:9898", cacher=cache.Cache(self.temp)) - api.log = TestLogger() - - api.get("/test/user") - api.cache.mark(time.time() + 1) - api.get("/test/user") - - expect = '127.0.0.8:9898 - - * "GET /test/user HTTP/1.1" 200 -\n' + \ - '127.0.0.8:9898 - - * "GET /test/user HTTP/1.1" 304 -\n' - - match = fnmatch.fnmatch(api.log.data, expect) - if not match: - self.fail("'{0}' did not match '{1}'".format(api.log.data, expect)) - - def testIssuesSince(self): - api = github.GitHub("http://127.0.0.8:9898/") - issues = api.issues(since=1499838499) - self.assertEqual(len(issues), 1) - self.assertEqual(issues[0]["number"], "7") - - def testWhitelist(self): - api = github.GitHub("http://127.0.0.8:9898/") - whitelist = api.whitelist() - self.assertTrue(len(whitelist) > 0) - self.assertEqual(whitelist, set(["one", "two", "three"])) - self.assertNotIn("four", whitelist) - self.assertNotIn("", whitelist) - - def testTeamIdFromName(self): - api = github.GitHub("http://127.0.0.8:9898/") - self.assertEqual(api.teamIdFromName(github.TEAM_CONTRIBUTORS), 1) - self.assertEqual(api.teamIdFromName("Other team"), 2) - self.assertRaises(KeyError, api.teamIdFromName, "team that doesn't exist") - - def testLastIssueDelete(self): - api = github.GitHub("http://127.0.0.8:9898/") - self.assertEqual(len(api.issues()), 2) - api.delete("/issues/7") - issues = api.issues() - self.assertEqual(len(issues), 1) - self.assertEqual(issues[0]["number"], "5") - -if __name__ == '__main__': - unittest.main() diff --git a/bots/task/test-policy b/bots/task/test-policy deleted file mode 100755 index c4010cecf..000000000 --- a/bots/task/test-policy +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import os -import subprocess -import sys -import unittest - -BASE = os.path.dirname(__file__) -BOTS = os.path.normpath(os.path.join(BASE, "..")) -sys.path.insert(1, BOTS) - -PCP_CRASH = """ -# ---------------------------------------------------------------------- -# testFrameNavigation (check_multi_machine.TestMultiMachine) -# -Unexpected journal message '/usr/libexec/cockpit-pcp: bridge was killed: 11' -not ok 110 testFrameNavigation (check_multi_machine.TestMultiMachine) -Traceback (most recent call last): - File "test/verify/check-multi-machine", line 202, in tearDown - MachineCase.tearDown(self) - File "/home/martin/upstream/cockpit/test/common/testlib.py", line 533, in tearDown - self.check_journal_messages() - File "/home/martin/upstream/cockpit/test/common/testlib.py", line 689, in check_journal_messages - raise Error(first) -Error: /usr/libexec/cockpit-pcp: bridge was killed: 11 -Wrote TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.png -Wrote TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.html -Wrote TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.js.log -Journal extracted to TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.log -Journal extracted to TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2503-FAIL.log -Journal extracted to TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2502-FAIL.log -""" - -PCP_KNOWN = """ -# ---------------------------------------------------------------------- -# testFrameNavigation (check_multi_machine.TestMultiMachine) -# -Unexpected journal message '/usr/libexec/cockpit-pcp: bridge was killed: 11' -ok 110 testFrameNavigation (check_multi_machine.TestMultiMachine) # SKIP Known issue #9876 -Traceback (most recent call last): - File "test/verify/check-multi-machine", line 202, in tearDown - MachineCase.tearDown(self) - File "/home/martin/upstream/cockpit/test/common/testlib.py", line 533, in tearDown - self.check_journal_messages() - File "/home/martin/upstream/cockpit/test/common/testlib.py", line 689, in check_journal_messages - raise Error(first) -Error: /usr/libexec/cockpit-pcp: bridge was killed: 11 -Wrote TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.png -Wrote TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.html -Wrote TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.js.log -Journal extracted to TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2501-FAIL.log -Journal extracted to TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2503-FAIL.log -Journal extracted to TestMultiMachine-testFrameNavigation-fedora-i386-127.0.0.2-2502-FAIL.log -""" - -class TestPolicy(unittest.TestCase): - def testSimpleNumber(self): - script = os.path.join(BOTS, "tests-policy") - cmd = [ script, "--simple", "--offline", "verify/example" ] - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) - (output, unused) = proc.communicate(PCP_CRASH) - self.assertEqual(output, "9876\n") - - def testScenario(self): - script = os.path.join(BOTS, "tests-policy") - cmd = [ script, "--simple", "--offline", "verify/example/scen-one" ] - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) - (output, unused) = proc.communicate(PCP_CRASH) - self.assertEqual(output, "9876\n") - - def testKnownIssue(self): - script = os.path.join(BOTS, "tests-policy") - cmd = [ script, "--offline", "verify/example" ] - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) - (output, unused) = proc.communicate(PCP_CRASH) - self.assertEqual(output, PCP_KNOWN) - - -if __name__ == '__main__': - unittest.main() - diff --git a/bots/task/test-task b/bots/task/test-task deleted file mode 100755 index d7d44613d..000000000 --- a/bots/task/test-task +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -ADDRESS = ("127.0.0.9", 9898) - -import ctypes -import imp -import json -import os -import signal -import shutil -import tempfile -import unittest -from unittest.mock import patch - -BASE = os.path.dirname(__file__) -os.environ["GITHUB_API"] = "http://127.0.0.9:9898" -os.environ["GITHUB_BASE"] = "project/repo" - -task = imp.load_source("task", os.path.join(BASE, "__init__.py")) - -DATA = { - "/repos/project/repo/issues/3333": { - "title": "The issue title" - }, - "/repos/project/repo/pulls/1234": { - "title": "Task title", - "number": 1234, - "body": "This is the body", - "head": {"sha": "abcdef"}, - }, - "/users/user/repos": [{"full_name": "project/repo"}] -} - -def mockServer(): - # Data used by below handler - data = { } - - import http.server - class Handler(http.server.BaseHTTPRequestHandler): - - def replyData(self, value, headers={ }, status=200): - self.send_response(status) - for name, content in headers.items(): - self.send_header(name, content) - self.end_headers() - self.wfile.write(value) - self.wfile.flush() - - def replyJson(self, value, headers={ }, status=200): - headers["Content-type"] = "application/json" - self.replyData(json.dumps(value).encode('utf-8'), headers=headers, status=status) - - def do_GET(self): - if self.path in DATA: - self.replyJson(DATA[self.path]) - else: - self.send_error(404, 'Mock Not Found: ' + self.path) - - def do_POST(self): - if self.path == "/repos/project/repo/pulls": - content_len = int(self.headers.get('content-length')) - data = json.loads(self.rfile.read(content_len).decode('utf-8')) - assert(data['title'] == "[no-test] Task title") - data["number"] = 1234 - self.replyJson(data) - elif self.path == "/repos/project/repo/pulls/1234": - content_len = int(self.headers.get('content-length')) - data = json.loads(self.rfile.read(content_len).decode('utf-8')) - data["number"] = 1234 - data["body"] = "This is the body" - data["head"] = {"sha": "abcde"} - self.replyJson(data) - elif self.path == "/repos/project/repo/issues/1234/comments": - content_len = int(self.headers.get('content-length')) - data = json.loads(self.rfile.read(content_len).decode('utf-8')) - self.replyJson(data) - elif self.path == "/repos/project/repo/issues/1234/labels": - content_len = int(self.headers.get('content-length')) - data = json.loads(self.rfile.read(content_len).decode('utf-8')) - self.replyJson(data) - else: - self.send_error(405, 'Method not allowed: ' + self.path) - - httpd = http.server.HTTPServer(ADDRESS, Handler) - httpd.data = data - - child = os.fork() - if child != 0: - return child - - # prctl(PR_SET_PDEATHSIG, SIGTERM) - try: - libc = ctypes.CDLL('libc.so.6') - libc.prctl(1, 15) - except OSError: - pass - - httpd.serve_forever() - os._exit(1) - -def mock_execute(*args): - assert(args[0] == 'git') - if args[1] == "show": - return "Task title\n" - elif args[1] == "commit" and args[2] == "--amend": - if args[4] != "Task title\nCloses #1234": - raise Exception("Incorrect commit message") - elif args[1] == "push": - assert(args[2] == "-f") - else: - raise Exception("Mocking unsupported git command") - -def mockKill(child): - os.kill(child, signal.SIGTERM) - os.waitpid(child, 0) - -class TestTask(unittest.TestCase): - def setUp(self): - self.child = mockServer() - self.temp = tempfile.mkdtemp() - - def tearDown(self): - mockKill(self.child) - shutil.rmtree(self.temp) - - def testRunArguments(self): - status = { "ran": False } - - def function(context, **kwargs): - self.assertEqual(context, "my-context") - self.assertEqual(kwargs["title"], "The issue title") - status["ran"] = True - - ret = task.run("my-context", function, name="blah", title="Task title", issue=3333) - self.assertEqual(ret, 0) - self.assertTrue(status["ran"]) - - def testComment(self): - comment = task.comment(1234, "This is the comment") - self.assertEqual(comment["body"], "This is the comment") - - def testLabel(self): - label = task.label(1234, ['xxx']) - self.assertEqual(label, ['xxx']) - - @patch('task.execute', mock_execute) - def testPullBody(self): - args = { "title": "Task title" } - pull = task.pull("user:branch", body="This is the body", **args) - self.assertEqual(pull["title"], "Task title") - self.assertEqual(pull["body"], "This is the body") - -if __name__ == '__main__': - unittest.main() diff --git a/bots/task/testmap.py b/bots/task/testmap.py deleted file mode 100644 index a79096a6d..000000000 --- a/bots/task/testmap.py +++ /dev/null @@ -1,128 +0,0 @@ -# This file is part of Cockpit. -# -# Copyright (C) 2019 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -REPO_BRANCH_CONTEXT = { - 'cockpit-project/cockpit': { - 'master': ['fedora-30/container-bastion', - 'fedora-30/selenium-firefox', 'fedora-30/selenium-chrome', 'fedora-30/selenium-edge', - 'debian-stable', 'debian-testing', - 'ubuntu-1804', 'ubuntu-stable', - 'fedora-30', 'fedora-31', 'fedora-atomic', - 'rhel-8-1-distropkg', 'rhel-8-1', - ], - 'rhel-7.7': ['rhel-7-7', - 'fedora-30/container-bastion', 'fedora-30/selenium-firefox', 'fedora-30/selenium-chrome', - 'rhel-atomic', 'continuous-atomic', - ], - 'rhel-8.0': ['fedora-30/container-bastion', - 'fedora-30/selenium-firefox', 'fedora-30/selenium-chrome', - 'rhel-8-0', - ], - 'rhel-8-appstream': ['fedora-30/container-bastion', - 'fedora-30/selenium-firefox', 'fedora-30/selenium-chrome', 'rhel-8-1-distropkg', 'rhel-8-1', - ], - 'rhel-8.1': ['fedora-30/container-bastion', - 'fedora-30/selenium-firefox', 'fedora-30/selenium-chrome', 'rhel-8-1', - ], - 'rhel-7.8': ['rhel-7-8', - 'fedora-30/container-bastion', 'fedora-30/selenium-firefox', 'fedora-30/selenium-chrome', - ], - # These can be triggered manually with bots/tests-trigger - '_manual': [ - 'fedora-i386', - 'fedora-testing', - ], - }, - 'cockpit-project/starter-kit': { - 'master': [ - 'centos-7', - 'fedora-30', - ], - }, - 'cockpit-project/cockpit-ostree': { - 'master': [ - 'fedora-atomic', - 'continuous-atomic', - 'rhel-atomic', - ], - }, - 'cockpit-project/cockpit-podman': { - 'master': [ - 'fedora-29', - 'fedora-30', - 'fedora-31', - 'rhel-8-1', - ], - }, - 'weldr/lorax': { - 'master': [ - 'fedora-30', - 'fedora-30/tar', - 'fedora-30/live-iso', - 'fedora-30/qcow2', - 'fedora-30/aws', - 'fedora-30/azure', - 'fedora-30/openstack', - 'fedora-30/vmware', - ], - 'rhel8-branch': [ - 'rhel-8-1', - 'rhel-8-1/live-iso', - 'rhel-8-1/qcow2', - 'rhel-8-1/aws', - 'rhel-8-1/azure', - 'rhel-8-1/openstack', - 'rhel-8-1/vmware', - ], - 'rhel7-extras': [ - 'rhel-7-7', - 'rhel-7-7/live-iso', - 'rhel-7-7/qcow2', - 'rhel-7-7/aws', - 'rhel-7-7/azure', - 'rhel-7-7/openstack', - 'rhel-7-7/vmware', - ], - }, - 'weldr/cockpit-composer': { - 'master': [ - 'fedora-30/chrome', - 'fedora-30/firefox', - 'fedora-30/edge', - 'rhel-7-7/firefox', - 'rhel-8-1/chrome', - ], - 'rhel-8.0': [ - 'rhel-8-0/chrome', - 'rhel-8-0/firefox', - 'rhel-8-0/edge', - ], - }, - 'mvollmer/subscription-manager': { - 'master': [ - 'rhel-8-1', - ], - } -} - -def projects(): - """Return all projects for which we run tests.""" - return REPO_BRANCH_CONTEXT.keys() - -def tests_for_project(project): - """Return branch -> contexts map.""" - return REPO_BRANCH_CONTEXT.get(project, {}) diff --git a/bots/test-bots b/bots/test-bots deleted file mode 100755 index 25c22ba5c..000000000 --- a/bots/test-bots +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import glob -import imp -import os -import sys -import unittest - -sys.dont_write_bytecode = True - -BOTS_DIR = os.path.dirname(__file__) - -def main(): - loader = unittest.TestLoader() - suite = unittest.TestSuite() - for filename in glob.glob(os.path.join(BOTS_DIR, "task", "test-*")): - name = os.path.basename(filename) - sys.path[0] = os.path.dirname(filename) - module = imp.load_source(name.replace("-", "_"), filename) - suite.addTest(loader.loadTestsFromModule(module)) - - runner = unittest.TextTestRunner() - result = runner.run(suite) - if result.wasSuccessful(): - return 0 - return 1 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/tests-data b/bots/tests-data deleted file mode 100755 index 23c2bd16a..000000000 --- a/bots/tests-data +++ /dev/null @@ -1,481 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2017 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import gzip -import json -import os -import re -import socket -import ssl -import subprocess -import sys -import tempfile -import time -import urllib.parse -import urllib.request, urllib.error, urllib.parse -import zlib - -import html.parser - -sys.dont_write_bytecode = True - -import task - -from machine import testvm - -# The number of days of previous closed pull requests to learn from -SINCE_DAYS = 120 - -BOTS = os.path.abspath(os.path.dirname(__file__)) -SEEDED = set() -SINKS = { } - -def run(filename, verbose=False, dry=False, **kwargs): - since = time.time() - 60 * 60 * 24 * SINCE_DAYS - pulls = Pulls(since) - - # Seed with our input data - if filename: - if "/" not in filename and not os.path.exists(filename): - if not dry: - subprocess.check_call([ os.path.join(BOTS, "image-download"), "--state", filename ]) - filename = os.path.join(testvm.get_images_data_dir(), filename) - (outfd, outname) = tempfile.mkstemp(prefix=os.path.basename(filename), dir=os.path.dirname(filename)) - os.close(outfd) - output = gzip.open(outname, 'wb') - if os.path.exists(filename): - with gzip.open(filename, 'rb') as fp: - seed(since, fp, pulls, output) - else: - output = sys.stdout.buffer - outname = None - - def write(**kwargs): - line = json.dumps(kwargs).encode('utf-8') + b"\n" - output.write(line) - - # Iterate through all revisions, pull requests on this branch - for (commit, merged, created, pull) in commits("master", pulls, since, verbose): - logged = False - if verbose: - sys.stderr.write("- {0}\n".format(commit)) - for (context, created, url, log) in logs(commit): - if verbose: - sys.stderr.write(" - {0} {1}\n".format(created, context)) - for (status, name, body, tracker) in tap(log): - write(pull=pull, revision=commit, status=status, - context=context, date=created, merged=merged, - test=name, url=url, tracker=tracker, log=body) - logged = True - - # Nothing found for this log - if not logged: - write(pull=pull, revision=commit, status="unknown", date=created, - merged=merged, url=url, log=log) - logged = True - - # Nothing found for this revision - if not logged: - write(pull=pull, revision=commit, status="unknown", date=created, merged=merged) - logged = True - - sys.stdout.flush() - if output: - output.close() - if outname: - os.rename(outname, filename) - - if not dry and outname and filename: - upload = [ os.path.join(BOTS, "image-upload"), "--state", filename ] - subprocess.check_call(upload) - -# An HTML parser that just pulls out all the -# link hrefs in a given page of content. We also qualify these -# hrefs with a base url, in case they're relative -class HrefParser(html.parser.HTMLParser): - def __init__(self, base, hrefs): - html.parser.HTMLParser.__init__(self) - self.hrefs = hrefs - self.base = base - - def handle_starttag(self, tag, attrs): - if tag.lower() == "a": - for (name, value) in attrs: - if name.lower() == "href": - url = urllib.parse.urljoin(self.base, value) - # print 'HREF', url - self.hrefs.append(url) - -# Check if a given pull request was included in its base -# branch via merging or otherwise -class Pulls(): - def __init__(self, since): - self.fetched = { } - self.checked = { } - self.pulls = { } - self.listing = [ ] - self.since = since - - # Get all the pull requests since a given time - def __iter__(self): - if self.listing: - iterate = self.pulls.values() - else: - iterate = task.api.pulls(state="all", since=self.since) - listing = [ ] - for pull in iterate: - self.pulls[pull["number"]] = pull - listing.append(pull) - yield pull - self.listing = listing - - # Turn a stning/int pull number into an pull object - def normalize(self, pull): - if isinstance(pull, int): - pull = str(pull) - if isinstance(pull, str): - if "/" not in pull: - pull = qualify("pulls/{0}".format(pull)) - if pull in self.pulls: - pull = self.pulls[pull] - else: - pull = task.api.get(pull) - self.pulls[pull["url"]] = pull - elif not isinstance(pull, dict): - raise ValueError("Invalid pull request: {0}".format(repr(pull))) - return pull - - def merged(self, pull): - pull = self.normalize(pull) - # if not pull: - # return None - - number = pull["number"] - - if number in self.checked: - return self.checked[number] - - if pull.get("state") != "closed": - return None - - # GitHub is telling us this was merged - if pull.get("merged"): - return True - - # Fetch git data about this branch - cwd = os.path.dirname(__file__) - base = pull["base"]["ref"] - if base not in self.fetched: - try: - subprocess.check_call([ "git", "fetch", "-q", "--", "origin", base ], cwd=cwd) - except subprocess.CalledProcessError: - return None # error already printed by process - self.fetched[base] = base - - # Look for git commits up until a year before the pull request - when = time.mktime(time.strptime(pull["created_at"], "%Y-%m-%dT%H:%M:%SZ")) - when -= 60 * 60 * 24 * 365 - since = time.strftime("%Y-%m-%d", time.gmtime(when)) - - # Check if it's referred to in this branch - match = "(Closes|Fixes|closes|fixes).*{0}".format(number) - cmd = [ - "git", "log", "--extended-regexp", "--grep", match, - "--since=" + since, "origin/" + base - ] - output = subprocess.check_output(cmd, cwd=cwd) - self.checked[number] = output and True or False - return self.checked[number] - -# Retrieves the content of the given URL -def retrieve(url): - ctx = ssl.create_default_context() - ctx.check_hostname = False - ctx.verify_mode = ssl.CERT_NONE - req = urllib.request.urlopen(url, context=ctx) - return req.read().decode('utf-8', 'replace') - -# Returns a list of all results at the given URL -def links(url): - result = [ ] - parser = HrefParser(url, result) - try: - parser.feed(retrieve(url)) - except urllib.error.HTTPError as ex: - if ex.code != 404: - raise - except (ConnectionResetError, urllib.error.URLError, socket.gaierror) as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - return result - -# Parses seed input data and passes it through to output -# all the while preparing the fact that certain URLs have -# already been seen -def seed(since, fp, pulls, output): - seeded = None - known = re.compile("# SKIP Known issue #([0-9]+)", re.IGNORECASE) - - while True: - try: - line = fp.readline() - except (OSError, zlib.error) as ex: - sys.stderr.write("tests-data: {0}\n".format(str(ex))) - break - if not line: - break - try: - item = json.loads(line.decode('utf-8')) - except ValueError as ex: - sys.stderr.write("tests-data: {0}\n".format(str(ex))) - continue - - # Once we see a new pull treat the old one as complete and seeded - # As a failsafe, just to make sure we didn't miss something - # wo don't treat the last pull request as completely seeded - pull = item.get("pull") - if pull and pull != seeded: - SEEDED.add(seeded) - seeded = None - - if pull and item.get("merged") not in [ True, False ]: - item["merged"] = pulls.merged(pull) - - # Note that we've already retrieved this URL - url = item.get("url") - if url and item.get("log") is not None: - SEEDED.add(url) - SEEDED.add(urllib.parse.urljoin(url, "./")) - - # If the pull request had a known merged value it can be seeded - # This forces us to retrieve data about open pull requests again - if item["merged"] in [ True, False ]: - seeded = pull - SEEDED.add(item["revision"]) - - date = item.get("date") - if not date or since > time.mktime(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")): - continue - - # COMPAT: Fix data that wasn't yet valid - if item["status"] == "skip": - match = known.search(item["log"]) - if match: - item["status"] = "failure" - item["tracker"] = qualify("issues/{0}".format(match.group(1))) - - line = json.dumps(item).encode('utf-8') + b"\n" - output.write(line) - -# Generate a list of (revision, merged, url) for the given branch -# This includes pull requests targeting the branch in question -# -# revision: the SHA of a commit -# merged: True/False/None whether merged or not -# url: The URL for the pull request or None -def commits(branch, pulls, since, verbose=False): - if verbose: - sys.stderr.write("{0}\n".format(branch)) - - # Iterate through commits on master - for commit in task.api.commits(branch, since=since): - revision = commit["sha"].lower() - if revision not in SEEDED: - yield revision, True, commit["commit"]["committer"]["date"], None - - # Iterate through pull requests - for pull in pulls: - if pull["number"] in SEEDED: - continue - if pull["base"]["ref"] != branch: - continue - if verbose: - sys.stderr.write("pull-{0}\n".format(pull["number"])) - merged = pulls.merged(pull) - - for revision in revisions(pull): - yield revision, merged, pull["created_at"], pull["url"] - - # The next revisions for the pull request are not the ones - # that got merged. Only the first one produced by revisions - if merged: - merged = False - - -# Get all the revisions in a pull request. GitHub doesn't help -# us here so we have to use silly tricks -def revisions(pull): - head = pull.get("head", { }).get("sha") - if not head: - return - - # First give back the main pull request - head = head.lower() - yield head - - # All the revisions we've seen - seen = set([ head ]) - - # Seed the set of sinks. We use these sinks to figure out additional - # revisions for the pull request. Unfortunately GitHub doesn't help us - # with a list of revisions that this pull request used to reflect. So - # we have to look to our sink for that info. - data = task.api.get("commits/{0}/status?page=1&per_page=100".format(head)) - for status in data.get("statuses", [ ]): - url = status["target_url"] - if url: - SEEDED.add(urllib.parse.urljoin(url, "./")) - sink = urllib.parse.urljoin(url, "../") - if sink not in SINKS: - SINKS[sink] = links(sink) - - # Now ask each sink for its set of urls - name = "pull-{0}".format(pull["number"]) - for sink in SINKS: - for link in SINKS[sink]: - - # We only care about stuff at the sink where pull-XXXX is in - # the URL. This is how we figure out whether things are related - if name not in link: - continue - - # Already retrieved this one - if link in SEEDED: - continue - - # Build a URL for the cockpituous sink /status file and read it - target = urllib.parse.urljoin(link, "status") - try: - data = json.loads(retrieve(target)) - except (ValueError, ConnectionError) as ex: - sys.stderr.write("{0}: {1}\n".format(target, ex)) - except urllib.error.HTTPError as ex: - if ex.code != 404: - raise - except urllib.error.URLError as ex: - sys.stderr.write("{0}: {1}\n".format(target, ex)) - pass - else: - # The status file contains a "revision" field which is the git revision - # of what was tested during that test run. This is what we're after - if "revision" in data: - revision = data["revision"].lower() - if revision not in seen: - seen.add(revision) - yield revision - -# Pull out all status (context, created, log) for a given revision. This includes multiple -# test runs for a given revision, and all the various status contexts -def logs(revision): - page = 1 - count = 100 - while count == 100: - data = task.api.get("commits/{0}/status?page={1}&per_page={2}".format(revision, page, count)) - count = 0 - for status in data.get("statuses", [ ]): - count += 1 - # Make sure to not consider "state": "success" as a success - # here because individual tests may have failed, or been retried. - # - # Always only consider tests individually to have run or failed - # not entire test suite statuses - if status["state"] in [ "pending" ]: - continue - target = status.get("target_url") - if not target: - continue - if target.endswith(".html"): - target = target[:-5] - if target in SEEDED: - continue - log = None - try: - log = retrieve(target) - except urllib.error.HTTPError as ex: - if ex.code != 404: - raise - log = "" - except (ConnectionResetError, urllib.error.URLError, socket.gaierror) as ex: - sys.stderr.write("{0}: {1}\n".format(target, ex)) - if log is not None: - yield (status["context"], status["created_at"], target, log) - - -# Generate (status, name, body, tracker) for each Test Anything Protocol test -# in the content. -# -# status: possible values "success", "failure", "skip" -# name: the name of the test -# body: full log of the test -# tracker: url tracking the failure, or None -def tap(content): - name = status = tracker = None - prefix = None - body = [ ] - blocks = False - for line in content.split('\n'): - # The test intro, everything before here is fluff - if not prefix and line.startswith("1.."): - prefix = line - body = [ ] - name = status = tracker = None - - # A TAP test status line - elif line.startswith("ok ") or line.startswith("not ok "): - body.append(line) - # Parse out the status - if line.startswith("not ok "): - status = "failure" - line = line[7:] - else: - line = line[3:] - if "# SKIP KNOWN ISSUE" in line.upper(): - status = "failure" - (unused, delim, issue) = line.partition("#") - tracker = qualify("issues/{0}".format(issue)) - if "# SKIP" in line.upper(): - status = "skip" - else: - status = "success" - # Parse out the name - while line[0].isspace() or line[0].isdigit(): - line = line[1:] - (name, delim, directive) = line.partition("#") - (name, delim, directive) = name.partition("duration") - name = name.strip() - # Old Cockpit tests had strange blocks - if not blocks: - yield (status, name, "\n".join(body), tracker) - status = name = tracker = None - body = [ ] - else: - # Old Cockpit tests didn't separate bound their stuff properly - if line.startswith("# --------------------"): - blocks = True - if status: - yield (status, name, "\n".join(body), tracker) - name = status = tracker = None - body = [ ] - body.append(line) - -# Qualify a URL into the GitHub repository -def qualify(path): - return "https://api.github.com" + task.api.qualify(path) - -if __name__ == '__main__': - task.main(function=run, title="Pull out test data for pull requests", verbose=True) diff --git a/bots/tests-invoke b/bots/tests-invoke deleted file mode 100755 index dff8ccb3f..000000000 --- a/bots/tests-invoke +++ /dev/null @@ -1,317 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2016 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import argparse -import os -import socket -import subprocess -import sys -import traceback - -sys.dont_write_bytecode = True - -from task import github -from task import sink - -HOSTNAME = socket.gethostname().split(".")[0] -BOTS = os.path.abspath(os.path.dirname(__file__)) -BASE = os.path.normpath(os.path.join(BOTS, "..")) -DEVNULL = open("/dev/null", "r+") - -def main(): - parser = argparse.ArgumentParser(description='Run integration tests') - parser.add_argument('--rebase', help="Rebase onto the specific branch before testing") - parser.add_argument('-o', "--offline", action='store_true', - help="Work offline, don''t fetch new data from origin for rebase") - parser.add_argument('--publish', dest='publish', default=os.environ.get("TEST_PUBLISH", ""), - action='store', help='Publish results centrally to a sink') - parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') - parser.add_argument('--pull-number', help="The number of the pull request to test") - parser.add_argument('--html-logs', action='store_true', help="Use log.html to prettify the raw logs") - parser.add_argument('context', help="The context or type of integration tests to run") - opts = parser.parse_args() - - name = os.environ.get("TEST_NAME", "tests") - revision = os.environ.get("TEST_REVISION") - test_project = os.environ.get("TEST_PROJECT") - # branch name when explicitly given in the test status - # e.g. PR opened against master for test `rhel-8-0@cockpit-project/cockpit/rhel-8.0` - # TEST_BRANCH would be `rhel-8.0` - test_branch = os.environ.get("TEST_BRANCH") - - try: - task = PullTask(name, revision, opts.context, opts.rebase, - test_project=test_project, test_branch=test_branch, - html_logs=opts.html_logs) - ret = task.run(opts) - except RuntimeError as ex: - ret = str(ex) - - if ret: - sys.stderr.write("tests-invoke: {0}\n".format(ret)) - return 1 - return 0 - -class PullTask(object): - def __init__(self, name, revision, context, base, test_project, test_branch, html_logs): - self.name = name - self.revision = revision - self.context = context - self.base = base - self.test_project = test_project - self.test_branch = test_branch - self.html_logs = html_logs - - self.sink = None - self.github_status_data = None - - def detect_collisions(self, opts): - api = github.GitHub() - - if opts.pull_number: - pull = api.get("pulls/{0}".format(opts.pull_number)) - if pull: - if pull["head"]["sha"] != self.revision: - return "Newer revision available on GitHub for this pull request" - if pull["state"] != "open": - return "Pull request isn't open" - - if not self.revision: - return None - - statuses = api.get("commits/{0}/statuses".format(self.revision)) - for status in statuses: - if status.get("context") == self.context: - if status.get("state") == "pending" and status.get("description") not in [github.NOT_TESTED, github.NOT_TESTED_DIRECT]: - return "Status of context isn't pending or description is not in [{0}, {1}]".format(github.NOT_TESTED, github.NOT_TESTED_DIRECT) - else: # only check the newest status of the supplied context - return None - - def start_publishing(self, host): - api = github.GitHub() - - # build a unique file name for this test run - id_context = "-".join([self.test_project, self.test_branch or "", self.context]) - identifier = "-".join([ - self.name.replace("/", "-"), - self.revision[0:8], - id_context.replace("/", "-") - ]) - - description = "{0} [{1}]".format(github.TESTING, HOSTNAME) - - # build a globally unique test context for GitHub statuses - github_context = self.context - if self.test_branch: - github_context += "@" + self.test_project + "/" + self.test_branch - elif self.test_project != api.repo : # disambiguate test name for foreign project tests - github_context += "@" + self.test_project - - self.github_status_data = { - "state": "pending", - "context": github_context, - "description": description, - "target_url": ":link" - } - - status = { - "github": { - "token": api.token, - "requests": [ - # Set status to pending - { "method": "POST", - "resource": api.qualify("commits/" + self.revision + "/statuses"), - "data": self.github_status_data - } - ], - "watches": [{ - "resource": api.qualify("commits/" + self.revision + "/status?per_page=100"), - "result": { - "statuses": [ - { - "context": github_context, - "description": description, - "target_url": ":link" - } - ] - } - }] - }, - "revision": self.revision, - "onaborted": { - "github": { - "token": api.token, - "requests": [ - # Set status to error - { "method": "POST", - "resource": api.qualify("statuses/" + self.revision), - "data": { - "state": "error", - "context": self.context, - "description": "Aborted without status", - "target_url": ":link" - } - } - ] - }, - } - } - - if self.html_logs: - # explicit request for html logs - status["link"] = "log.html" - status["extras"] = [ "https://raw.githubusercontent.com/cockpit-project/cockpit/master/bots/task/log.html" ] - elif self.test_project != "cockpit-project/cockpit": - # third-party project, link directly to text log - status["link"] = "log" - else: - # Testing cockpit itself, use HTML log from current - # revision. Use log.html from code under test, but only - # if we are on master. Other branches don't have a bots/ - # in their repo. - status["link"] = "log.html" - status["extras"] = [ "https://raw.githubusercontent.com/cockpit-project/cockpit/{0}/bots/task/log.html".format(self.revision if not self.base else "master") ] - - - # Include information about which base we're testing against - if self.base: - subprocess.check_call([ "git", "fetch", "origin", self.base ]) - commit = subprocess.check_output([ "git", "rev-parse", "origin/" + self.base ], - universal_newlines=True).strip() - status["base"] = commit - - if not self.base: - status['irc'] = { } # Only send to IRC when master - - # For other scripts to use - os.environ["TEST_DESCRIPTION"] = description - self.sink = sink.Sink(host, identifier, status) - - def rebase(self): - remote_base = "origin" + "/" + self.base - - # Rebase this branch onto the base, but only if it's not already an ancestor - try: - if subprocess.call([ "git", "merge-base", "--is-ancestor", remote_base, "HEAD" ]) != 0: - sha = subprocess.check_output([ "git", "rev-parse", remote_base ], universal_newlines=True).strip() - sys.stderr.write("Rebasing onto {0} ({1}) ...\n".format(remote_base, sha)) - subprocess.check_call([ "git", "reset", "HEAD" ]) - subprocess.check_call([ "git", "rebase", remote_base ]) - except subprocess.CalledProcessError: - subprocess.call([ "git", "rebase", "--abort" ]) - traceback.print_exc() - return "Rebase failed" - - return None - - def stop_publishing(self, ret): - sink = self.sink - def mark_failed(): - if "github" in sink.status: - self.github_status_data["state"] = "failure" - if "irc" in sink.status: # Never send success messages to IRC - sink.status["irc"]["channel"] = "#cockpit" - def mark_passed(): - if "github" in sink.status: - self.github_status_data["state"] = "success" - if isinstance(ret, str): - message = ret - mark_failed() - ret = 0 - elif ret == 0: - message = "Tests passed" - mark_passed() - else: - message = "Tests failed with code {0}".format(ret) - mark_failed() - ret = 0 # A failure, but not for this script - sink.status["message"] = message - if "github" in sink.status: - self.github_status_data["description"] = message - try: - del sink.status["extras"] - except KeyError: - pass - sink.flush() - - return ret - - def run(self, opts): - # Collision detection - ret = self.detect_collisions(opts) - if ret: - sys.stderr.write('Collision detected: {0}\n'.format(ret)) - return None - - if opts.publish: - self.start_publishing(opts.publish) - os.environ["TEST_ATTACHMENTS"] = self.sink.attachments - - head = subprocess.check_output([ "git", "rev-parse", "HEAD" ], universal_newlines=True).strip() - if not self.revision: - self.revision = head - - # Retrieve information about our base branch and master (for bots/) - if self.base and not opts.offline: - subprocess.check_call([ "git", "fetch", "origin", self.base, "master" ]) - - # Clean out the test directory - subprocess.check_call([ "git", "clean", "-d", "--force", "--quiet", "-x", "--", "test/" ]) - - os.environ["TEST_NAME"] = self.name - os.environ["TEST_REVISION"] = self.revision - - # Split OS and potential scenario - (image, _, scenario) = self.context.partition("/") - if scenario: - os.environ["TEST_SCENARIO"] = scenario - os.environ["TEST_OS"] = image - - msg = "Testing {0} for {1} with {2} on {3}...\n".format(self.revision, self.name, - self.context, HOSTNAME) - sys.stderr.write(msg) - - ret = None - - if self.base: - ret = self.rebase() - - # Flush our own output before running command - sys.stdout.flush() - sys.stderr.flush() - - # Actually run the tests - if not ret: - entry_point = os.path.join(BOTS, "..", ".cockpit-ci", "run") - alt_entry_point = os.path.join(BOTS, "..", "test", "run") - if not os.path.exists(entry_point) and os.path.exists(alt_entry_point): - entry_point = alt_entry_point - cmd = [ "timeout", "120m", entry_point ] - if opts.verbose: - cmd.append("--verbose") - ret = subprocess.call(cmd) - - # All done - if self.sink: - ret = self.stop_publishing(ret) - - return ret - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/tests-policy b/bots/tests-policy deleted file mode 100755 index 5e4346d41..000000000 --- a/bots/tests-policy +++ /dev/null @@ -1,416 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import argparse -import datetime -import fnmatch -import json -import re -import os -import socket -import ssl -import sys -import time -import traceback -import urllib - -sys.dont_write_bytecode = True - -FLAKE_THRESHOLD = 0.4 - -from task import github -try: - from machine.testvm import get_test_image -except ImportError: - # fallback for unit tests where libvirt is not available - get_test_image = lambda i: i - -BOTS = os.path.dirname(os.path.realpath(__file__)) - -def main(): - script = os.path.basename(__file__) - parser = argparse.ArgumentParser(description='Check a traceback for a known issue') - parser.add_argument('-o', "--offline", action='store_true', - help="Work offline, don't fetch new data or contact servers") - parser.add_argument("--simple", action="store_true", - help="Just print out a known issue number") - parser.add_argument("-v", "--verbose", action="store_true", - help="Print out more verbose logging info") - parser.add_argument('context', help="The image to check against") - opts = parser.parse_args() - - base = None - if os.environ.get("GITHUB_KNOWN_ISSUE_BASE"): - netloc = os.environ.get("GITHUB_API", "https://api.github.com") - base = "{0}/repos/{1}/".format(netloc, os.environ.get("GITHUB_KNOWN_ISSUE_BASE")) - - api = None if opts.offline else github.GitHub(base=base) - - context = opts.context - if "/" not in context: - context = "verify/{0}".format(context) - prefix, _, image = context.partition("/") - image, _, scenario = image.partition("/") - - try: - output = sys.stdin.read() - number = None - - if image and output: - number = check_known_issue(api, output, image) - - if number and api and api.available: - try: - post_github(api, number, output, image) - except (socket.error, RuntimeError): - traceback.print_exc() - sys.stderr.write("{0}: posting update to GitHub failed\n".format(script)) - # Fall through - - # Simple single number output - if opts.simple: - if number: - sys.stdout.write("{0}\n".format(number)) - return 0 - - # Otherwise we filter the output and write it back - if number: - output = filterSkip(output, "# SKIP Known issue #{0}".format(number)) - elif checkRetry(output): - output = filterRetry(output, "# RETRY due to failure of test harness or framework") - elif not opts.offline: - output = guessFlake(output, context, opts.verbose) - - sys.stdout.write(output) - return 0 - - except RuntimeError as ex: - sys.stderr.write("{0}: {1}\n".format(script, ex)) - return 1 - -# ----------------------------------------------------------------------------- -# TAP Parsing - -# Update TAP output failure and put a skip message -# in the appropriate place -def filterSkip(output, skip): - lines = output.split("\n") - for i, line in enumerate(lines): - if line.startswith("not ok "): - lines[i] = line[4:] + " " + skip - return "\n".join(lines) - -# Update TAP output failure and put a retry message -# in the appropriate place -def filterRetry(output, retry): - lines = output.split("\n") - for i, line in enumerate(lines): - if line.startswith("not ok "): - lines[i] = line + " " + retry - return "\n".join(lines) - -# Figure out the name from a failed test -def parseName(output): - for line in output.split("\n"): - if line.startswith("not ok "): - line = line[7:] - while line[0].isspace() or line[0].isdigit(): - line = line[1:] - (name, delim, directive) = line.partition("#") - (name, delim, directive) = name.partition("duration") - name = name.strip() - return name - return "" - -# ----------------------------------------------------------------------------- -# Flakiness Checks - -def guessFlake(output, context, verbose=False): - # Build up an item just like in tests-data - item = { - "pull": None, - "revision": os.environ.get("TEST_REVISION"), - "status": "failure", - "context": context, - "date": time.strftime("%Y-%m-%dT%H:%M:%SZ"), - "merged": None, - "test": parseName(output), - "url": None, - "log": output - } - - host = os.environ.get("COCKPIT_LEARN_SERVICE_HOST", "learn-cockpit.apps.ci.centos.org") - port = os.environ.get("COCKPIT_LEARN_SERVICE_PORT", "443") - response = { } - flake = False - - url = "{0}://{1}:{2}/predict".format("https" if port == "443" else "http", host, port) - cafile = os.path.join(BOTS, "images", "files", "ca.pem") - context = ssl.create_default_context(cafile=cafile) - jsonl = json.dumps(item) + "\n" - try: - data = urllib.request.urlopen(url, data=jsonl.encode('utf-8'), context=context).read() - except (ConnectionResetError, urllib.error.URLError, socket.gaierror) as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - data = b"{ }" - try: - response = json.loads(data.decode('utf-8')) - except ValueError as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - - if verbose and response: - sys.stderr.write(json.dumps(response, indent=4) + "\n") - - def count(record, field, only): - values = record.get(field, []) - for value, count in values: - if value != only: - continue - return count - return 0 - - merged = count(response, "merged", True) - not_merged = count(response, "merged", False) - null_merged = count(response, "merged", None) - - score = 0 - total = merged + not_merged + null_merged - if total: - score = (merged / total) - if score > FLAKE_THRESHOLD: - output += "\n# Flake likely: {0:.3f} (clustering). Please file issue if this not a flake\n".format(score) - flake = True - elif merged > 0: - output += "\n# Flake probability: {0:.3f} (clustering)\n".format(score) - if "trackers" in response: - tracker = response["trackers"][0] - output += "# Flake tracked {0} likelihood {1:.3f}\n".format(tracker[0], tracker[1]) - - if flake: - output = filterRetry(output, "# RETRY due to flakiness") - return output - -# ----------------------------------------------------------------------------- -# Retry policy - -def checkRetry(trace): - # We check for persistent but test harness or framework specific - # failures that otherwise cause flakiness and false positives. - # - # The things we check here must: - # * have no impact on users of Cockpit in the real world - # * be things we tried to resolve in other ways. This is a last resort - # - - trace = normalize_traceback(trace) - - # HACK: Interacting with sshd during boot is not always predictable - # We're using an implementation detail of the server as our "way in" for testing. - # This often has to do with sshd being restarted for some reason - if "SSH master process exited with code: 255" in trace: - return True - - # HACK: Intermittently the new libvirt machine won't get an IP address - # or SSH will completely fail to start. We've tried various approaches - # to minimize this, but it happens every 100,000 tests or so - if "Failure: Unable to reach machine " in trace: - return True - - # HACK: For when the verify machine runs out of available processes - # We should retry this test process - if "self.pid = os.fork()\nOSError: [Errno 11] Resource temporarily unavailable" in trace: - return True - - return False - - -# ----------------------------------------------------------------------------- -# Known Issue Matching and Filing - -def normalize_traceback(trace): - # All file paths converted to basename - trace = re.sub(r'File "[^"]*/([^/"]+)"', 'File "\\1"', trace.strip()) - - # replace noise in SELinux violations - trace = re.sub('audit\([0-9.:]+\)', 'audit(0)', trace) - trace = re.sub(r'\b(pid|ino)=[0-9]+ ', r'\1=0 ', trace) - - # in Python 3, testlib.Error is shown with namespace - trace = re.sub('testlib\.Error', 'Error', trace) - return trace - -def check_known_issue(api, trace, image): - image_naughty = os.path.join(BOTS, "naughty", get_test_image(image)) - if not os.path.exists(image_naughty): - return None - - trace = normalize_traceback(trace) - number = None - for naughty in os.listdir(image_naughty): - (prefix, unused, name) = naughty.partition("-") - n = int(prefix) - with open(os.path.join(image_naughty, naughty), "r") as fp: - match = "*" + normalize_traceback(fp.read()) + "*" - # Match as in a file name glob, albeit multi line, and account for literal pastes with '[]' - if fnmatch.fnmatchcase(trace, match) or fnmatch.fnmatchcase(trace, match.replace("[", "?")): - number = n - return number - - -# Update a known issue thread on GitHub -# -# The idea is to combine repeated errors into fewer comments by -# editing them and keeping all relevant information. -# -# For this we keep one comment per context (e.g. 'verify/fedora-atomic') -# and divide that into sections, one each per error description / trace. -# In each section, we keep the error description / trace as well as -# the number of recorded events, the first occurrence and the last 10 -# occurrences. -# For each (listed) occurrence we display the timestamp and some details -# provided by the caller, such as a revision or link to log files. -# The details can't contain newline characters and should be brief -def update_known_issue(api, number, err, details, context, timestamp=None): - timestamp = timestamp or datetime.datetime.now().isoformat() - - link = timestamp - if details: - link = "{0} | {1}".format(timestamp, details) - - comments = issue_comments(api, number) - - # try to find an existing comment to update; extract the traceback from the - # whole output; also ensure to remove the "# duration: XXs" trailer - err_key = normalize_traceback(err).strip() - m = re.search("^(Traceback.*^not ok[^#\\n]*)", err_key, re.S | re.M) - if m: - err_key = m.group(1) - comment_key = "{0}\n".format(context) - latest_occurrences = "Latest occurrences:\n\n" - for comment in reversed(comments): - if 'body' in comment and comment['body'].startswith(comment_key): - parts = comment['body'].split("
") - updated = False - for part_idx, part in enumerate(parts): - part = normalize_traceback(part).strip() - if err_key in part: - latest = part.split(latest_occurrences) - if len(latest) < 2: - sys.stderr.write("Error while parsing latest occurrences\n") - else: - # number of times this error was recorded - header = latest[0].split("\n") - for header_idx, entry in enumerate(header): - if entry.startswith("Times recorded: "): - rec_entries = entry.split(" ") - rec_entries[-1] = str(int(rec_entries[-1]) + 1) - header[header_idx] = " ".join(rec_entries) - latest[0] = "\n".join(header) - # list of recent occurrences - occurrences = [_f for _f in latest[1].split("\n") if _f] - occurrences.append("- {0}\n".format(link)) - # only keep the last 10 - if len(occurrences) > 10: - occurrences.pop(0) - parts[part_idx] = "{0}{1}{2}".format(latest[0], latest_occurrences, "\n".join(occurrences)) - updated = True - break - - if updated: - # shuffle the updated part to the end - assert len(parts) > part_idx - parts.append(parts[part_idx]) - del parts[part_idx] - - else: - # add a new part - while len(parts) > 10: # maximum 10 traces - parts.pop() - - parts.append(""" -``` -{0} -``` -First occurrence: {1} -Times recorded: 1 -{2}- {1} -""".format(err.strip(), link, latest_occurrences)) - updated = True - - # update comment, no need to check others - body = "
\n".join(parts) - - # ensure that the body is not longer than 64k. - # drop earlier parts if we need to. - while len(body) >= 65536: - parts.pop(1) # parts[0] is the header - - body = "
\n".join(parts) - - return api.patch("issues/comments/{0}".format(comment['id']), { "body": body }) - - # create a new comment, since we didn't find one to update - - data = { "body": """{0}\nOoops, it happened again
-``` -{1} -``` -First occurrence: {2} -Times recorded: 1 -{3}- {2} -""".format(context, err.strip(), link, latest_occurrences) } - return api.post("issues/{0}/comments".format(number), data) - -def issue_comments(api, number): - result = [ ] - page = 1 - count = 100 - while count == 100: - comments = api.get("issues/{0}/comments?page={1}&per_page={2}".format(number, page, count)) - count = 0 - page += 1 - if comments: - result += comments - count = len(comments) - return result - - -def post_github(api, number, output, image): - - # Ignore this if we were not given a token - if not api or not api.available: - return - - context = "verify/{0}".format(image) - - # Lookup the link being logged to - link = None - revision = os.environ.get("TEST_REVISION", None) - if revision: - link = "revision {0}".format(revision) - statuses = api.get("commits/{0}/statuses".format(revision)) - if statuses: - for status in statuses: - if status["context"] == context: - link = "revision {0}, [logs]({1})".format(revision, status["target_url"]) - break - update_known_issue(api, number, output, link, context) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/tests-scan b/bots/tests-scan deleted file mode 100755 index 51b8ba612..000000000 --- a/bots/tests-scan +++ /dev/null @@ -1,445 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2015 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -# Random extra options for tests-invoke -REPO_EXTRA_INVOKE_OPTIONS = { - 'mvollmer/subscription-manager': [ "--html-logs" ] -} - -# Label: should a PR trigger external tests -LABEL_TEST_EXTERNAL = "test-external" - -import argparse -import os -import json -import pipes -import sys -import time -import logging -import itertools -import urllib.request - -sys.dont_write_bytecode = True -logging.basicConfig(level=logging.INFO) - -from task import github, label, redhat_network, labels_of_pull, distributed_queue, testmap - -no_amqp = False -try: - import pika -except ImportError: - no_amqp = True - -def main(): - parser = argparse.ArgumentParser(description='Bot: scan and update status of pull requests on GitHub') - parser.add_argument('-v', '--human-readable', action="store_true", default=False, - help='Display human readable output rather than tasks') - parser.add_argument('-d', '--dry', action="store_true", default=False, - help='Don''t actually change anything on GitHub') - parser.add_argument('--repo', default=None, - help='Repository to scan and checkout.') - parser.add_argument('-c', '--context', action="append", default=[ ], - help='Test contexts to use.') - parser.add_argument('-p', '--pull-number', default=None, - help='Single pull request to scan for tasks') - parser.add_argument('--pull-data', default=None, - help='pull_request event GitHub JSON data to evaluate; mutualy exclusive with -p and -s') - parser.add_argument('-s', '--sha', default=None, - help='SHA beloging to pull request to scan for tasks') - parser.add_argument('--amqp', default=None, - help='The host:port of the AMQP server to publish to (format host:port)') - - opts = parser.parse_args() - if opts.amqp and no_amqp: - logging.error("AMQP host:port specified but python-amqp not available") - return 1 - if opts.pull_data and (opts.pull_number or opts.sha): - parser.error("--pull-data and --pull-number/--sha are mutually exclusive") - - api = github.GitHub(repo=opts.repo) - - # HACK: The `repo` option is used throughout the code, for example repo from - # opts is needed in `tests_invoke`, `tests_human`, `queue_test` etc. - # Would be better to use api.repo everywhere - opts.repo = api.repo - - try: - policy = testmap.tests_for_project(opts.repo) - if opts.context: - short_contexts = [] - for context in opts.context: - short_contexts.append(context.split("@")[0]) - policy = {} - for (branch, contexts) in testmap.tests_for_project(opts.repo).items(): - branch_context = [] - for context in short_contexts: - if context in contexts: - branch_context.append(context) - if branch_context: - policy[branch] = branch_context - results = scan_for_pull_tasks(api, policy, opts, opts.repo) - - # When run from c-p/cockpit checkout without PR number or PR data we want to scan also all - # external projects. E.g. `bots/tests-scan` in c-p/c checkout will scan all projects - if opts.repo == "cockpit-project/cockpit" and not opts.pull_data and not opts.pull_number: - results += scan_external_projects(opts) - except RuntimeError as ex: - logging.error("tests-scan: " + str(ex)) - return 1 - - for result in results: - if result: - sys.stdout.write(result + "\n") - - return 0 - -# Prepare a human readable output -def tests_human(priority, name, number, revision, ref, context, base, original_base, repo, bots_ref): - if not priority: - return - try: - priority = int(priority) - except (ValueError, TypeError): - pass - return "{name:11} {context:25} {revision:10} {priority:2}{repo}{bots_ref}{branches}".format( - priority=priority, - revision=revision[0:7], - context=context, - name=name, - repo=repo and " (%s)" % repo or "", - bots_ref=bots_ref and (" [bots@%s]" % bots_ref) or "", - branches=base != original_base and (" {%s...%s}" % (original_base, base)) or "" - ) - -def is_internal_context(context): - for pattern in ["rhel", "edge", "vmware", "openstack"]: - if pattern in context: - return True - return False - -# Prepare a test invocation command -def tests_invoke(priority, name, number, revision, ref, context, base, original_base, repo, bots_ref, options): - if not options.amqp and not redhat_network() and is_internal_context(context): - return '' - - try: - priority = int(priority) - except (ValueError, TypeError): - priority = 0 - if priority <= 0: - return - current = time.strftime('%Y%m%d-%H%M%M') - - checkout = "PRIORITY={priority:04d} bots/make-checkout --verbose" - cmd = "TEST_PROJECT={repo} TEST_NAME={name}-{current} TEST_REVISION={revision} bots/tests-invoke --pull-number={pull_number} " - if base: - if base != ref: - cmd += " --rebase={base}" - checkout += " --base={base}" - - checkout += " --repo={repo}" - - if bots_ref: - checkout += " --bots-ref={bots_ref}" - - # The repo of this test differs from the PR's repo - if options.repo != repo: - cmd = "GITHUB_BASE={github_base} " + cmd - - if repo in REPO_EXTRA_INVOKE_OPTIONS: - cmd += " " + " ".join(REPO_EXTRA_INVOKE_OPTIONS[repo]) - - # Let tests-invoke know that we are triggering different branch - it needs to post correct status - if base != original_base: - cmd = "TEST_BRANCH={base} " + cmd - - cmd += " {context}" - if bots_ref: - # we are checking the external repo on a cockpit PR, so stay on the project's master - checkout += " {ref} && " - else: - # we are testing the repo itself, checkout revision from the PR - checkout += " {ref} {revision} && " - - return (checkout + "cd bots/make-checkout-workdir && " + cmd + " ; cd ../..").format( - priority=priority, - name=pipes.quote(name), - revision=pipes.quote(revision), - base=pipes.quote(str(base)), - ref=pipes.quote(ref), - bots_ref=pipes.quote(bots_ref), - context=pipes.quote(context), - current=current, - pull_number=number, - repo=pipes.quote(repo), - github_base=pipes.quote(options.repo), - ) - -def queue_test(priority, name, number, revision, ref, context, base, original_base, repo, bots_ref, channel, options): - command = tests_invoke(priority, name, number, revision, ref, context, base, original_base, repo, bots_ref, options) - if command: - if priority > distributed_queue.MAX_PRIORITY: - priority = distributed_queue.MAX_PRIORITY - - body = { - "command": command, - "type": "test", - "sha": revision, - "ref": ref, - "name": name, - } - queue = 'rhel' if is_internal_context(context) else 'public' - channel.basic_publish('', queue, json.dumps(body), properties=pika.BasicProperties(priority=priority)) - logging.info("Published '{0}' on '{1}' with command: '{2}'".format(name, revision, command)) - -def prioritize(status, title, labels, priority, context, number): - state = status.get("state", None) - update = { "state": "pending" } - - # This commit definitively succeeded or failed - if state in [ "success", "failure" ]: - logging.info("Skipping '{0}' on #{1} because it has already finished".format(context, number)) - priority = 0 - update = None - - # This test errored, we try again but low priority - elif state in [ "error" ]: - priority -= 2 - - elif state in [ "pending" ]: - logging.info("Not updating status for '{0}' on #{1} because it is pending".format(context, number)) - update = None - - # Ignore context when the PR has [no-test] in the title or as label, unless - # the context was directly triggered - if (('no-test' in labels or '[no-test]' in title) and - status.get("description", "") != github.NOT_TESTED_DIRECT): - logging.info("Skipping '{0}' on #{1} because it is no-test".format(context, number)) - priority = 0 - update = None - - if priority > 0: - if "priority" in labels: - priority += 2 - if "blocked" in labels: - priority -= 1 - - # Pull requests where the title starts with WIP get penalized - if title.startswith("WIP") or "needswork" in labels: - priority -= 1 - - # Is testing already in progress? - description = status.get("description", "") - if description.startswith(github.TESTING): - logging.info("Skipping '{0}' on #{1} because it is already running".format(context, number)) - priority = description - update = None - - if update: - if priority <= 0: - logging.info("Not updating status for '{0}' on #{1} because of low priority".format(context, number)) - update = None - else: - update["description"] = github.NOT_TESTED - - return [priority, update] - -def dict_is_subset(full, check): - for (key, value) in check.items(): - if not key in full or full[key] != value: - return False - return True - -def update_status(api, revision, context, last, changes): - if changes: - changes["context"] = context - if changes and not dict_is_subset(last, changes): - response = api.post("statuses/" + revision, changes, accept=[ 422 ]) # 422 Unprocessable Entity - errors = response.get("errors", None) - if not errors: - return True - for error in response.get("errors", []): - sys.stderr.write("{0}: {1}\n".format(revision, error.get('message', json.dumps(error)))) - sys.stderr.write(json.dumps(changes)) - return False - return True - -def cockpit_tasks(api, update, branch_contexts, repo, pull_data, pull_number, sha, amqp): - results = [] - pulls = [] - contexts = set(itertools.chain(*branch_contexts.values())) - - if pull_data: - pulls.append(json.loads(pull_data)['pull_request']) - elif pull_number: - pull = api.get("pulls/{0}".format(pull_number)) - if pull: - pulls.append(pull) - else: - logging.error("Can't find pull request {0}".format(pull_number)) - return 1 - else: - pulls = api.pulls() - - whitelist = api.whitelist() - for pull in pulls: - title = pull["title"] - number = pull["number"] - revision = pull["head"]["sha"] - statuses = api.statuses(revision) - login = pull["head"]["user"]["login"] - base = pull["base"]["ref"] # The branch this pull request targets - - logging.info("Processing #{0} titled '{1}' on revision {2}".format(number, title, revision)) - - # If sha is present only scan PR with selected sha - if sha and revision != sha and not revision.startswith(sha): - continue - - labels = labels_of_pull(pull) - - baseline = distributed_queue.BASELINE_PRIORITY - # amqp automatically prioritizes on age - if not amqp: - # modify the baseline slightly to favor older pull requests, so that we don't - # end up with a bunch of half tested pull requests - baseline += 1.0 - (min(100000, float(number)) / 100000) - - def trigger_externals(): - if repo != "cockpit-project/cockpit": # already a non-cockpit project - return False - if base != "master": # bots/ is always taken from master branch - return False - if LABEL_TEST_EXTERNAL in labels: # already checked before? - return True - - if not statuses: - # this is the first time tests-scan looks at a PR, so determine if it changes bots/ - with urllib.request.urlopen(pull["patch_url"]) as f: - # enough to look at the git commit header, it lists all changed files - if b"bots/" in f.read(4000): - if update: - # remember for next run, to avoid downloading the patch multiple times - label(number, [LABEL_TEST_EXTERNAL]) - return True - - return False - - def get_externals(): - result = [] - for proj_repo in testmap.projects(): - if proj_repo == "cockpit-project/cockpit": - continue - for context in testmap.tests_for_project(proj_repo).get("master", []): - result.append(context + "@" + proj_repo) - return result - - def is_valid_context(context): - (os_scenario, _, repo_branch) = context.partition("@") - if repo_branch: - repo_branch = "/".join(repo_branch.split("/")[:2]) - repo_contexts = testmap.tests_for_project(repo_branch).values() - return os_scenario in set(itertools.chain(*repo_contexts)) - else: - return os_scenario in contexts - - # Create list of statuses to process - todos = {} - for status in statuses: # Firstly add all valid contexts that already exist in github - if is_valid_context(status): - todos[status] = statuses[status] - if not statuses: # If none defined in github add basic set of contexts - for context in branch_contexts.get(base, []): - todos[context] = {} - if trigger_externals(): - for context in get_externals(): - if context not in todos: - todos[context] = {} - - for context in todos: - # Get correct project and branch. Ones from test name have priority - project = repo - branch = base - (os_scenario, _, repo_branch) = context.partition("@") - repo_branch = repo_branch.split("/") - if len(repo_branch) == 2: - project = "/".join(repo_branch) - branch = "master" - elif len(repo_branch) == 3: - project = "/".join(repo_branch[:2]) - branch = repo_branch[2] - - ref = "pull/%d/head" % number - - # For unmarked and untested status, user must be in whitelist - # Not this only applies to this specific commit. A new status - # will apply if the user pushes a new commit. - status = todos[context] - if login not in whitelist and status.get("description", github.NO_TESTING) == github.NO_TESTING: - priority = github.NO_TESTING - changes = { "description": github.NO_TESTING, "context": context, "state": "pending" } - else: - (priority, changes) = prioritize(status, title, labels, baseline, context, number) - if not update or update_status(api, revision, context, status, changes): - checkout_ref = ref - if project != repo: - checkout_ref = "master" - if base != branch: - checkout_ref = branch - results.append((priority, - "pull-%d" % number, - number, - revision, - checkout_ref, - os_scenario, - branch, - base, - project, - ref if project != repo or base != branch else None)) - - return results - -def scan_for_pull_tasks(api, policy, opts, repo): - kvm = os.access("/dev/kvm", os.R_OK | os.W_OK) - if not kvm: - logging.error("tests-scan: No /dev/kvm access, not running tests here") - return [] - - results = cockpit_tasks(api, not opts.dry, policy, repo, opts.pull_data, opts.pull_number, opts.sha, opts.amqp) - - if opts.human_readable: - func = lambda x: tests_human(*x) - results.sort(reverse=True, key=lambda x: str(x)) - return list(map(func, results)) - if not opts.amqp: - func = lambda x: tests_invoke(*x, options=opts) - return list(map(func, results)) - with distributed_queue.DistributedQueue(opts.amqp, ['rhel', 'public']) as q: - func = lambda x: queue_test(*x, channel=q.channel, options=opts) - return list(map(func, results)) - -def scan_external_projects(opts): - tests = [] - for repo in testmap.projects(): - if repo != "cockpit-project/cockpit": - tests += scan_for_pull_tasks(github.GitHub(repo=repo), testmap.tests_for_project(repo), opts, repo) - return tests - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/tests-score b/bots/tests-score deleted file mode 100755 index d008fa023..000000000 --- a/bots/tests-score +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2018 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import json -import os -import socket -import ssl -import sys -import urllib - -sys.dont_write_bytecode = True - -import task - -BOTS = os.path.dirname(os.path.realpath(__file__)) - -# This parses the output JSONL format discussed here, where various -# values are grouped: -# -# https://github.com/cockpit-project/cockpituous/blob/master/learn/README.md - -# Here we're looking for a field in a record that only has one value -def value(record, field): - values = record.get(field, []) - if len(values) == 1: - return values[0][0] or "" - return None - -# Here we're looking for the count of a specific field/value in the record -def count(record, field, only): - values = record.get(field, []) - for value, count in values: - if value != only: - continue - return count - return 0 - -def run(url, verbose=False, dry=False, **kwargs): - cafile = None - if not url: - host = os.environ.get("COCKPIT_LEARN_SERVICE_HOST", "learn-cockpit.apps.ci.centos.org") - port = os.environ.get("COCKPIT_LEARN_SERVICE_PORT", "443") - url = "{0}://{1}:{2}/active/statistics.jsonl".format("https" if port == "443" else "http", host, port) - cafile = os.path.join(BOTS, "images", "files", "ca.pem") - context = ssl.create_default_context(cafile=cafile) - - statistics = [ ] - - # Retrieve the URL - try: - req = urllib.request.Request(url) - with urllib.request.urlopen(req, context=context) as f: - for line in f.readlines(): - try: - record = json.loads(line.decode('utf-8')) - statistics.append(record) - except ValueError as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - except (ConnectionResetError, urllib.error.URLError, socket.gaierror) as ex: - sys.stderr.write("{0}: {1}\n".format(url, ex)) - return False - - # Now go through and look for tests that have never flaked - score = [] - tests = { } - failure = set() - - for record in statistics: - test = value(record, "test") - context = value(record, "context") - status = value(record, "status") - tracker = value(record, "tracker") - - # There should only be one of all of these values - if test is None or status is None: - continue - - # Make note of all the tests we've seen - contexts = tests.get(test) - if not contexts: - tests[test] = contexts = [ ] - if context: - contexts.append(context) - - # Flaky tests only score on those that fail and are not tracked - if status == "failure" and not tracker: - merged = count(record, "merged", True) - not_merged = count(record, "merged", False) - null_merged = count(record, "merged", None) - total = merged + not_merged + null_merged - - # And the key is that they were merged anyway - if total > 0: - score.append((context, merged / total, test)) - failure.add(test) - - # Tests that never failed (wow) get a zero score - for test, contexts in tests.items(): - if test not in failure: - for context in contexts: - score.append((context, 0, test)) - - score.sort() - - for context, fraction, test in score: - sys.stdout.write("{:>5} {:>20} {}\n".format(int(fraction * 100), context, test)) - -if __name__ == '__main__': - task.main(function=run, title="Get flakiness score for tests", verbose=True) diff --git a/bots/tests-trigger b/bots/tests-trigger deleted file mode 100755 index e29eed5c3..000000000 --- a/bots/tests-trigger +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import sys - -sys.dont_write_bytecode = True - -from task import github - -def trigger_pull(api, opts): - pull = api.get("pulls/" + opts.pull) - - if not pull: - sys.stderr.write("{0} is not a pull request.\n".format(opts.pull)) - return 1 - - # triggering is manual, so don't prevent triggering a user that isn't on the whitelist - # but issue a warning in case of an oversight - login = pull["head"]["user"]["login"] - if not opts.allow and not login in api.whitelist(): - sys.stderr.write("Pull request author '{0}' isn't whitelisted. Override with --allow.\n".format(login)) - return 1 - - revision = pull['head']['sha'] - statuses = api.statuses(revision) - if opts.context: - contexts = [cntx for cntx in opts.context if github.known_context(cntx)] - all = False - else: - contexts = set(statuses.keys()) - all = True - - ret = 0 - for context in contexts: - status = statuses.get(context, { }) - current_status = status.get("state", all and "unknown" or "empty") - - if current_status not in ["empty", "error", "failure"]: - # allow changing if manual testing required, otherwise "pending" state indicates that testing is in progress - manual_testing = current_status == "pending" and status.get("description", None) == github.NO_TESTING - queued = current_status == "pending" and status.get("description", None) == github.NOT_TESTED_DIRECT - # also allow override with --force or --requeue - if not (manual_testing or opts.force) and not (queued and opts.requeue): - if not all: - sys.stderr.write("{0}: isn't in triggerable state (is: {1})\n".format(context, status["state"])) - ret = 1 - continue - sys.stderr.write("{0}: triggering on pull request {1}\n".format(context, opts.pull)) - changes = { "state": "pending", "description": github.NOT_TESTED_DIRECT, "context": context } - - # Keep the old link for reference, until testing starts again - link = status.get("target_url", None) - if link: - changes["target_url"] = link - - api.post("statuses/" + revision, changes) - - return ret - -def main(): - parser = argparse.ArgumentParser(description='Manually trigger CI Robots') - parser.add_argument('-f', '--force', action="store_true", - help='Force setting the status even if the program logic thinks it shouldn''t be done') - parser.add_argument('-a', '--allow', action='store_true', dest='allow', - help="Allow triggering for users that aren't whitelisted") - parser.add_argument('--requeue', action="store_true", - help='Re-queue pending test requests (workaround for occasionally ignored webhook events)') - parser.add_argument('--repo', help="The repository to trigger the robots in", default=None) - parser.add_argument('pull', help='The pull request to trigger') - parser.add_argument('context', nargs='*', help='The github task context(s) to trigger') - opts = parser.parse_args() - - api = github.GitHub(repo=opts.repo) - return trigger_pull(api, opts) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/bots/vm-reset b/bots/vm-reset deleted file mode 100755 index c5ad5dc87..000000000 --- a/bots/vm-reset +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -SELF=$(basename $0) -BASE=$(dirname $(dirname $0)) - -usage() -{ - echo >&2 "Usage: $SELF" -} - -case ${1:-} in ---help|-h) - usage - exit 0 - ;; -esac - -rm -rf $BASE/tmp/run/* $BASE/tmp/run/.*?? $BASE/test/images/* diff --git a/bots/vm-run b/bots/vm-run deleted file mode 100755 index 18eae7045..000000000 --- a/bots/vm-run +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/python3 -# This file is part of Cockpit. -# -# Copyright (C) 2013 Red Hat, Inc. -# -# Cockpit is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# Cockpit is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Cockpit; If not, see . - -import argparse -import errno -import os -import subprocess -import sys -import re - -from machine import testvm - -BOTS = os.path.abspath(os.path.dirname(__file__)) - -NETWORK_SCRIPT=b""" - set -ex - virsh net-destroy cockpit1 || true - virsh net-undefine cockpit1 || true - virsh net-define /dev/stdin < - cockpit1 - f3605fa4-0763-41ea-8143-49da3bf73263 - - - - - - - - - - - - - - -EOF - - if [ ! -u /usr/libexec/qemu-bridge-helper ]; then - chmod -v u+s /usr/libexec/qemu-bridge-helper - fi - - for qemu_config in /etc/qemu-kvm/bridge.conf /etc/qemu/bridge.conf; do - if [ -e "$qemu_config" ] && ! grep -qF cockpit1 "$qemu_config"; then - echo "allow cockpit1" >> "$qemu_config" - fi - done - - virsh net-autostart cockpit1 - virsh net-start cockpit1 -""" - -parser = argparse.ArgumentParser(description='Run a test machine') -parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose details') -parser.add_argument('-m', '--maintain', action='store_true', help='Changes are permanent') -parser.add_argument('-M', '--memory', default=None, type=int, help='Memory (in MiB) of the target machine') -parser.add_argument('-G', '--graphics', action='store_true', help='Display a graphics console') -parser.add_argument('-C', '--cpus', default=None, type=int, help='Number of cpus in the target machine') -parser.add_argument('-S', '--storage', default=None, type=str, help='Add a second qcow2 disk at this path') -parser.add_argument('--network', action='store_true', help='Setup a bridged network for running machines') -parser.add_argument('--no-network', action='store_true', help='Do not connect the machine to the Internet') - -parser.add_argument('image', help='The image to run') -args = parser.parse_args() - -try: - if args.network: - proc = subprocess.Popen(["sudo", "/bin/sh"], stdin=subprocess.PIPE) - proc.communicate(NETWORK_SCRIPT) - if proc.returncode != 0: - sys.stderr.write("vm-run: failed to create cockpit1 network\n") - sys.exit(1) - - bridge=None - with open(os.devnull, 'w') as fp: - if subprocess.call(["ip", "address", "show", "dev", "cockpit1"], stdout=fp, stderr=fp) == 0: - bridge = "cockpit1" - - # Lets make sure Windows has enough memory to be productive - memory = args.memory - if "windows" in args.image and memory is None: - memory = 4096 - - graphics = args.graphics or 'windows' in args.image - network = testvm.VirtNetwork(0, bridge=bridge, image=args.image) - - machine = testvm.VirtMachine(verbose=args.verbose, image=args.image, maintain=args.maintain, - networking=network.host(restrict=args.no_network), - memory_mb=memory, cpus=args.cpus, graphics=graphics) - - # Hack to make things easier for users who don't know about kubeconfig - if 'openshift' in args.image: - kubeconfig = os.path.join(os.path.expanduser("~"), ".kube", "config") - if not os.path.lexists(kubeconfig): - d = os.path.dirname(kubeconfig) - src = os.path.abspath(os.path.join(BOTS, "images", "files", "openshift.kubeconfig")) - os.makedirs(d, exist_ok=True) - sys.stderr.write("image-run: linking kubeconfig into ~/.kube/config\n") - os.symlink(src, kubeconfig) - - # Check that image is downloaded - if not os.path.exists(machine.image_file): - try: - ret = subprocess.call([ os.path.join(BOTS, "image-download"), args.image]) - except OSError as ex: - if ex.errno != errno.ENOENT: - raise - else: - if ret != 0: - sys.exit(ret) - - machine.start() - - if args.storage: - machine.add_disk(path=args.storage, type='qcow2') - - # for a bridged network, up its interface with DHCP and show it in the console message - message = "" - if args.network and bridge and 'windows' not in args.image: - machine.execute("nmcli connection modify 'System eth1' ipv4.method auto && nmcli connection up 'System eth1'") - output = machine.execute("ip -4 a show dev eth1") - ip = re.search("inet ([^/]+)", output).group(1) - message = "\nBRIDGE IP FROM HOST\n %s\n" % ip - - # Graphics console necessary - if graphics: - machine.graphics_console() - - # No graphics console necessary - else: - machine.qemu_console(message) - -except testvm.Failure as ex: - sys.stderr.write("vm-run: %s\n" % ex) - sys.exit(1)