cockpit/bots/images/scripts/lib/atomic.install

304 lines
12 KiB
Python
Executable File

#!/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 <http://www.gnu.org/licenses/>.
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