304 lines
12 KiB
Python
Executable File
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
|