#!/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