#!/usr/bin/python # -*- coding: utf-8 -*- # 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 import urllib import argparse import datetime import json import re 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-networkmanager", "cockpit-ostree", "cockpit-sosreport" ] def __init__(self, rpms=None, verbose=False): self.verbose = verbose self.rpms = rpms self.branch = json.load(open("/usr/share/rpm-ostree/treefile.json"))["ref"] 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 "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")]) now = datetime.datetime.utcnow() 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-ws and cockpit-dashboard in our container""" ws = None dashboard = None for package in self.rpms: if 'cockpit-ws' in package: ws = package if 'cockpit-dashboard' in package: dashboard = package if ws or dashboard: subprocess.check_call(["docker", "run", "--name", "build-cockpit", "-d", "--privileged", "-v", "/:/host", "cockpit/ws", "sleep", "1d"]) rpm_args = [] if ws: rpm_args.append("/host" + ws) if dashboard: rpm_args.append("/host" + dashboard) if rpm_args: rpm_args = ["docker", "exec", "build-cockpit", "rpm", "-U", "--force"] + rpm_args if self.verbose: print "updating cockpit-ws container" subprocess.check_call(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 filter(lambda s: not s is None, map(lambda s: self.package_basename(s), package_names)) def get_installed_cockpit_packages(self): """ get list installed cockpit packages """ packages = subprocess.check_output("rpm -qa | grep cockpit", shell=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("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 = 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 names downloader = urllib.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) 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('--skip', action='append', default=[], help='Packes to skip during installation') args = parser.parse_args() if args.build: print >> sys.stderr, "Can't build on Atomic" 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 = map(os.path.abspath, filter(lambda f: (f.endswith(".rpm") and not f.endswith(".src.rpm") and not any(f.startswith(s) for s in args.skip)), os.listdir("."))) cockpit_installer = AtomicCockpitInstaller(rpms=rpms, verbose=args.verbose) cockpit_installer.run()