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

import parent
from testlib import *
import os
import subprocess

INSTALL_RPMS = [
    "empty-1-0.noarch",
    "cockpit-ostree-wip-0.1.noarch",
    "cockpit-machines-99999-2.noarch",
]

REPO_LOCATION = "/var/local-repo"
CHECKOUT_LOCATION = "/var/local-tree"
RPM_LOCATION = "/usr/share/rpm"
KEY_ID = "95A8BA1754D0E95E2B3A98A7EE15015654780CBD"

def get_os_name(m):
    if m.image == "rhel-atomic":
        return "rhel-atomic-host"
    elif 'continuous' in m.image:
        return 'centos-atomic-host'
    else:
        return "fedora-atomic"

def ensure_remote_http_port (m, remote="local"):
    remote_spec = m.execute(["ostree", "remote", "show-url", remote])
    if remote_spec.startswith("http"):
        parts = remote_spec.strip().split(":")
        port = parts[-1]
    else:
        m.execute(["ostree", "remote", "delete", remote])
        m.execute(["ostree", "remote", "add", remote,
                   "http://127.0.0.1:12345", "--no-gpg-verify"])
        m.execute("printf '\nmin-free-space-percent=0\n' >> /var/local-repo/config")
        try:
            m.execute(["rpm-ostree reload"])
        except subprocess.CalledProcessError:
            m.execute("systemctl restart rpm-ostreed")
        port = 12345

    return port

def start_trivial_httpd(m, remote="local", location=REPO_LOCATION):
    port = ensure_remote_http_port (m, remote)
    script = "export PYTHONUNBUFFERED=1\ncd {0}\nexec python -m SimpleHTTPServer {1}".format(location, port)
    m.execute("echo '{}' > /tmp/trivial-httpd".format(script))
    m.execute("chmod +x /tmp/trivial-httpd")
    pid = m.spawn("/tmp/trivial-httpd", "trivial-httpd")

    m.execute(["ostree", "summary", "--repo={}".format(location), "-u"])
    m.wait_for_cockpit_running(port=port)
    return pid

def stop_trivial_httpd(m, pid):
    if pid:
        m.execute(["kill", str(pid)])

def generate_new_commit(m, pkg_to_remove):
    # Make one change of each type to a new rpm tree
    branch = m.execute("ostree refs --repo={0}".format(REPO_LOCATION))

    m.upload(["verify/files/{0}.rpm".format(k) for k in INSTALL_RPMS],
              "/home/admin/")

    # move /usr/etc to /etc, makes rpm installs easier
    rpm_etc = os.path.join(CHECKOUT_LOCATION, "etc")
    usr_etc = os.path.join(CHECKOUT_LOCATION, "usr", "etc")
    m.execute("mv {0} {1}".format(usr_etc, rpm_etc))

    # Remove a package
    rpm_args = [CHECKOUT_LOCATION, RPM_LOCATION, pkg_to_remove]
    m.execute("rpm -e --root {0} --dbpath {1} {2}".format(*rpm_args))

    # Install our dummy packages, dbonly
    rpm_args[-1] = ' '.join(["{0}.rpm".format(os.path.join("/home/admin", x)) \
                                for x in INSTALL_RPMS])
    m.execute("rpm -U --oldpackage --root {0} --dbpath {1} --justdb {2}".format(*rpm_args))

    # move /etc back to /usr/etc to
    m.execute("mv {0} {1}".format(rpm_etc, usr_etc))

    # Upload a signing key
    m.upload(["verify/files/secring.gpg",
              "verify/files/pubring.gpg"], "/root/")

    commit_args = [REPO_LOCATION, branch.strip(), CHECKOUT_LOCATION, KEY_ID]
    command = "ostree commit -s cockpit-tree2 --repo {0} -b {1} --add-metadata-string version=cockpit-base.2 --tree=dir={2} --gpg-sign={3} --gpg-homedir=/root/"
    m.execute(command.format(*commit_args), timeout=600)
    m.execute(["ostree", "summary", "--repo={}".format(REPO_LOCATION), "-u"])

def rhsmcertd_hack(m):
    # HACK: https://github.com/candlepin/subscription-manager/issues/1404
    m.execute("systemctl disable rhsmcertd || true")
    m.execute("systemctl stop rhsmcertd || true")


@skipImage("No OSTree available", "centos-7", "debian-stable", "debian-testing", "fedora-26", "fedora-27", "fedora-testing", "fedora-i386", "rhel-7", "rhel-7-4", "rhel-7-5", "ubuntu-1604", "ubuntu-stable")
class OstreeRestartCase(MachineCase):
    provision = {
        "machine1": { "address": "10.111.113.2/20", "dns": "10.111.113.2" }
    }

    def switch_to_packages(self, b, sel, required_classes):
        b.wait_not_visible("{0} div.packages".format(sel))
        b.wait_present('{0} ul'.format(sel))
        b.click('{0} ul li a:contains("Packages")'.format(sel))
        b.wait_visible("{0} div.packages".format(sel))
        for c in required_classes:
            b.wait_present("{0} div.packages dl.{1}".format(sel, c))

    def check_change_counts(self, b, sel):
        for k in ['adds', 'removes', 'updates', 'downgrades']:
            b.wait_present("{0} dd.{1}".format(sel, k))
            b.wait_text("{0} dd.{1}".format(sel, k),
                        "1 package")

    def check_sig(self, b, sel):
        b.wait_not_visible("{0} div.signatures".format(sel))
        b.wait_present('{0} ul'.format(sel))
        b.click('{0} ul li a:contains("Signature")'.format(sel))
        b.wait_visible("{0} div.signatures".format(sel))
        b.wait_in_text("{0} div.signatures".format(sel), KEY_ID)
        b.wait_in_text("{0} div.signatures".format(sel), "RSA")
        b.wait_in_text("{0} div.signatures".format(sel), "Cockpit Tester <do-not-reply@cockpit-project.org>")
        b.wait_in_text("{0} div.signatures".format(sel), "Good Signature")
        b.wait_in_text("{0} div.signatures".format(sel), "When")

    def testOstree(self):
        b = self.browser
        m = self.machine

        # Delete local remote so we start clean, without a file based remote
        ensure_remote_http_port (m)

        docker_pkg = m.execute("rpm -qa | grep cockpit-docker").strip()

        rhsmcertd_hack(m)

        cockpit_shell = m.execute("rpm -qa | grep cockpit-ostree").strip()
        cockpit_machines = m.execute("rpm -qa | grep cockpit-machines | grep -v cockpit-machines-ovirt").strip()

        self.login_and_go("/updates")
        b.enter_page("/updates")

        name = get_os_name(m)

        # Check current and rollback target
        b.wait_present('table.listing-ct')
        b.wait_present('table.listing-ct tbody')
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " cockpit-base.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')
        b.wait_present('table.listing-ct tbody:nth-child(3).active')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Running")
        b.wait_not_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")

        b.wait_visible("table.listing-ct tbody:nth-child(3) div.tree")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.os", name)
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.version", "cockpit-base.1")
        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(3)",
                                ['rpms-col1', 'rpms-col2'])
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.packages", docker_pkg)
        for pkg in INSTALL_RPMS:
            b.wait_not_in_text("table.listing-ct tbody:nth-child(3) div.packages", pkg)

        b.wait_not_visible("table.listing-ct tbody:nth-child(3) div.signatures")
        b.wait_present('table.listing-ct tbody:nth-child(3) ul')
        b.click('table.listing-ct tbody:nth-child(3) ul li a:contains("Signature")')
        b.wait_visible("table.listing-ct tbody:nth-child(3) div.signatures")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.signatures", "No signature avaliable")

        # Require signatures
        m.execute("sed -i /gpg-verify/d /etc/ostree/remotes.d/local.conf")
        # HACK: rpm-ostree doesn't reload remote settings properly
        # https://github.com/projectatomic/rpm-ostree/issues/401
        m.execute("systemctl restart rpm-ostreed")

        b.wait_not_in_text('table.listing-ct tbody:nth-child(4) div.listing-ct-head h3', "cockpit")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(4) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) div.listing-ct-actions button", "Roll Back")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) dd.os", name)
        b.wait_visible("table.listing-ct tbody:nth-child(4) div.tree")
        b.wait_not_visible("table.listing-ct tbody:nth-child(4) div.packages")

        # Check for new commit, get error
        b.wait_present('table.listing-ct tbody:nth-child(2) i.fa-caret-up')
        b.wait_in_text("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button", "Check for Updates")
        b.wait_not_present('table.listing-ct tbody:nth-child(2) div.alert-warning')
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present('table.listing-ct tbody:nth-child(2) div.alert-warning')
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")

        # Serve repo
        server_pid = start_trivial_httpd(m)

        # Check for new commit
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.disabled")
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")

        # Generate new commit
        generate_new_commit(m, docker_pkg)

        # Check again not trusted
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present('table.listing-ct tbody:nth-child(2) div.alert-warning')
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")
        b.wait_in_text("table.listing-ct tbody:nth-child(2) div.alert-warning", "trusted keyring")

        m.upload(["verify/files/publickey.asc"], "/root/")
        m.execute("ostree remote gpg-import local -k /root/publickey.asc")

        # Check again have update data
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button", "Update")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-check-circle-o')

        # Check update data
        b.wait_visible("table.listing-ct tbody:nth-child(3) div.tree")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.os", name)
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.version", "cockpit-base.2")
        self.check_change_counts(b, "table.listing-ct tbody:nth-child(3)")

        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(3)",
                                ['upgrades', 'downgrades',
                                 'additions', 'removals'])

        b.wait_text("table.listing-ct tbody:nth-child(3) dl.upgrades dd", "cockpit-machines-99999-2.noarch")
        b.wait_text("table.listing-ct tbody:nth-child(3) dl.downgrades dd", "cockpit-ostree-wip-0.1.noarch")
        b.wait_text("table.listing-ct tbody:nth-child(3) dl.additions dd", "empty-1-0.noarch")
        b.wait_text("table.listing-ct tbody:nth-child(3) dl.removals dd", docker_pkg)

        # Check signatures
        self.check_sig (b, "table.listing-ct tbody:nth-child(3)")

        # Force an error
        stop_trivial_httpd(m, server_pid)
        b.wait_not_present('table.listing-ct tbody:nth-child(3) div.listing-ct-error')
        b.click("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button.disabled")
        b.wait_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button.enabled")
        b.wait_present('table.listing-ct tbody:nth-child(3) div.listing-ct-error')
        server_pid = start_trivial_httpd(m)

        # Apply update
        b.click("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(3) div.ostree-progress")
        b.wait_not_present('table.listing-ct tbody:nth-child(3) div.listing-ct-error')

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")
        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")

        # After reboot, check commit
        b.wait_present('table.listing-ct')
        b.wait_present('table.listing-ct tbody')
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " cockpit-base.2")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')
        b.wait_present('table.listing-ct tbody:nth-child(3) div.listing-ct-panel,active')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Running")
        b.wait_not_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")
        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(3)",
                                ['rpms-col1', 'rpms-col2'])
        b.wait_not_in_text("table.listing-ct tbody:nth-child(3) div.packages", docker_pkg)
        for pkg in INSTALL_RPMS:
            b.wait_in_text("table.listing-ct tbody:nth-child(3) div.packages", pkg)

        # Check signatures
        self.check_sig (b, "table.listing-ct tbody:nth-child(3)")

        # Check rollback target
        b.wait_text('table.listing-ct tbody:nth-child(4) div.listing-ct-head h3', name + " cockpit-base.1")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(4) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) div.listing-ct-actions button", "Roll Back")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) dd.os", name)
        self.check_change_counts(b, "table.listing-ct tbody:nth-child(4)")
        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(4)",
                                ['upgrades', 'downgrades',
                                 'additions', 'removals'])

        b.wait_text("table.listing-ct tbody:nth-child(4) dl.upgrades dd", cockpit_shell)
        b.wait_text("table.listing-ct tbody:nth-child(4) dl.downgrades dd", cockpit_machines)
        b.wait_text("table.listing-ct tbody:nth-child(4) dl.additions dd", docker_pkg)
        b.wait_text("table.listing-ct tbody:nth-child(4) dl.removals dd", "empty-1-0.noarch")

        # Rollback
        b.wait_present("button:contains('Roll Back')");
        b.click("table.listing-ct tbody:nth-child(4) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(4) div.ostree-progress")

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")
        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")

        # After reboot upgrade but no rollback target
        b.wait_present('table.listing-ct')
        b.wait_present('table.listing-ct tbody')
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button", "Update")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-check-circle-o')
        b.wait_not_present('table.listing-ct tbody:nth-child(5)')
        b.wait_not_present("button:contains('Roll Back')");

        self.allow_restart_journal_messages()

    def testRebase(self):
        m = self.machine
        b = self.browser

        name = get_os_name(m)

        start_trivial_httpd(m)
        branch = m.execute("ostree refs --repo={0}".format(REPO_LOCATION)).strip()

        # Add a new branch to the default repo
        m.execute(["ostree", "commit", "--repo={}".format(REPO_LOCATION),
                   "-b", "znew-branch", "--tree=ref={}".format(branch),
                   "--add-metadata-string", "version=branch-version"])
        m.execute(["ostree", "summary", "--repo={}".format(REPO_LOCATION), "-u"])

        rhsmcertd_hack(m)
        self.login_and_go("/updates")
        b.enter_page("/updates")

        b.wait_present('table.listing-ct tbody:nth-child(2) i.fa-caret-up')
        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "local")
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", branch)
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li")
        b.wait_not_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li div.alert")
        b.wait_in_text("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:last", "znew-branch")
        b.call_js_func("ph_count_check", "table.listing-ct tbody:nth-child(2) ul.dropdown-menu li",  2)
        b.click("#change-branch")
        b.click("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:last a")
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " cockpit-base.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.origin", "local:{}".format(branch))

        b.wait_in_text("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button", "Check for Updates")
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")

        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " branch-version")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button", "Rebase")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.os", name)
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.origin", "local:znew-branch")

        b.wait_not_visible("table.listing-ct tbody:nth-child(3) div.packages")
        b.wait_present('table.listing-ct tbody:nth-child(3) ul')
        b.click('table.listing-ct tbody:nth-child(3) ul li a:contains("Packages")')
        b.wait_visible("table.listing-ct tbody:nth-child(3) div.packages")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.packages", "contains the same packages as your currently booted")

        # Apply update
        b.wait_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button") # wait to be rendered
        b.wait_not_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button.disabled") # and enabled
        b.click("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(3) div.ostree-progress")
        b.wait_not_present('table.listing-ct tbody:nth-child(3) div.listing-ct-error')

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")
        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")

        # After reboot, check commit
        b.wait_present('table.listing-ct')
        b.wait_present('table.listing-ct tbody')
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " branch-version")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')
        b.wait_present('table.listing-ct tbody:nth-child(3) div.listing-ct-panel,active')
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.origin", "local:znew-branch")

        self.allow_restart_journal_messages()

@skipImage("No OSTree available", "centos-7", "debian-stable", "debian-testing", "fedora-26", "fedora-27", "fedora-testing", "fedora-i386", "rhel-7", "rhel-7-4", "rhel-7-5", "ubuntu-1604", "ubuntu-stable")
class OstreeCase(MachineCase):
    provision = {
        "machine1": { "address": "10.111.113.2/20", "dns": "10.111.113.2" }
    }

    def testRemoteManagement(self):
        m = self.machine
        b = self.browser

        name = get_os_name(m)

        start_trivial_httpd(m)
        branch = m.execute("ostree refs --repo={0}".format(REPO_LOCATION)).strip()

        rhsmcertd_hack(m)
        self.login_and_go("/updates")
        b.enter_page("/updates")

        b.wait_present('table.listing-ct tbody:nth-child(2) i.fa-caret-up')
        b.wait_not_present('table.listing-ct tbody:nth-child(2) div.alert-warning')
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", branch)
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li")
        b.wait_not_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li div.alert")
        b.call_js_func("ph_count_check", "table.listing-ct tbody:nth-child(2) ul.dropdown-menu li",  1)
        b.wait_in_text("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li", branch)
        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "local")
        b.click("#change-repo")

        b.wait_present("modal-dialog")
        b.wait_in_text("modal-dialog .modal-title", "Change Repository")
        b.wait_present("modal-dialog .modal-body .list-group")
        b.wait_in_text("modal-dialog .modal-body .list-group .active", "local")
        b.wait_in_text("modal-dialog .modal-body .list-group-item:last", "Add New Repository")

        b.wait_present("modal-dialog .modal-footer button.btn-primary")

        b.click("modal-dialog .modal-body .list-group-item:last")
        b.wait_present("modal-dialog .modal-footer button.disabled")
        b.wait_present("modal-dialog #remote-name")
        b.wait_present("modal-dialog #remote-url")
        b.wait_present("modal-dialog #gpg-verify")
        b.click("modal-dialog #gpg-verify")
        b.set_val("modal-dialog #remote-url", "http://localhost:12344")
        b.set_val("modal-dialog #remote-name", "zremote test")
        b.wait_present("modal-dialog .group-buttons button.btn-primary")
        b.wait_in_text("modal-dialog .group-buttons button.btn-primary", "Add")
        b.click("modal-dialog .group-buttons button.btn-primary")
        b.wait_present("modal-dialog .list-group-item:last div.dialog-error")
        b.set_val("modal-dialog #remote-name", "zremote-test1")
        b.click("modal-dialog .group-buttons button.btn-primary")

        b.wait_not_present ("modal-dialog #remote-name")
        b.wait_not_present ("modal-dialog .group-buttons")
        b.wait_not_present("modal-dialog .modal-footer button.disabled")
        b.wait_present("modal-dialog .modal-footer button.btn-primary")
        b.wait_in_text("modal-dialog .modal-body .list-group .active", "local")
        b.wait_present("modal-dialog .modal-body .list-group-item:contains('zremote-test1')")
        b.wait_in_text("modal-dialog .modal-body .list-group-item:last", "Add New Repository")
        b.click("modal-dialog .modal-body .list-group-item:contains('zremote-test1')")

        b.click("modal-dialog .modal-footer button.btn-primary")
        b.wait_not_present("modal-dialog")

        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "zremote-test1")
        # Branch is still default
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", branch)
        # But can't list
        b.click("#change-branch")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu div.alert")
        # Actual error message changes between versions
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu div.alert span.pficon-warning-triangle-o")

        # Config created
        self.assertEqual(m.execute("cat /etc/ostree/remotes.d/zremote-test1.conf").strip(),
                        '[remote "zremote-test1"]\nurl=http://localhost:12344\ngpg-verify=true')
        # No custom keyring
        self.assertFalse(m.execute("ls /sysroot/ostree/repo/zremote-test1.trustedkeys.gpg || true"))

        # Refresh goes back to default
        b.reload()
        b.enter_page("/updates")
        b.wait_present('table.listing-ct tbody:nth-child(2) i.fa-caret-up')
        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "local")
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", branch)
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li")
        b.wait_not_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu div.alert")

        # Create a new remote with commits, just use the rpm dir
        zrepo = "/var/zrepo"
        m.execute("mkdir -p {}".format(zrepo))
        m.execute("mkdir -p /tmp/rpm-data/usr/share")
        m.execute("cp -r /usr/share/rpm /tmp/rpm-data/usr/share/")
        m.execute(["ostree", "init", "--repo", zrepo, "--mode", "archive-z2"])
        m.execute(["ostree", "commit", "--repo={}".format(zrepo),
                   "-b", "zremote-branch1", "--orphan", "--tree=dir=/tmp/rpm-data",
                   "--add-metadata-string", "version=zremote-branch1.1"])
        m.execute(["ostree", "commit", "--repo={}".format(zrepo),
                   "-b", "zremote-branch2", "--orphan", "--tree=dir=/tmp/rpm-data",
                   "--add-metadata-string", "version=zremote-branch2.1"])
        start_trivial_httpd(m, remote="zremote-test1", location=zrepo)

        # Add a new branch to the default repo
        m.execute(["ostree", "commit", "--repo={}".format(REPO_LOCATION),
                   "-b", branch, "--tree=ref={}".format(branch),
                   "--add-metadata-string", "version=bad-version"])
        m.execute(["ostree", "summary", "--repo={}".format(REPO_LOCATION), "-u"])

        # Edit
        b.wait_present('#change-repo')
        b.click("#change-repo")
        b.wait_present("modal-dialog")
        b.wait_present("modal-dialog .modal-body .list-group")
        b.wait_present("modal-dialog .modal-body .list-group-item:contains('zremote-test1')")
        b.wait_present("modal-dialog .modal-body .list-group-item:contains('zremote-test1') .listing-ct-actions button.pficon-edit")
        b.click("modal-dialog .modal-body .list-group-item:contains('zremote-test1') .listing-ct-actions button.pficon-edit")
        b.wait_present("modal-dialog .group-buttons")
        b.wait_present("modal-dialog .modal-footer button.disabled")
        b.wait_present("modal-dialog .group-buttons button.btn-primary")
        b.wait_in_text("modal-dialog .group-buttons", "zremote-test1")
        b.wait_present("modal-dialog .form-table-ct")
        b.wait_in_text("modal-dialog .form-table-ct", "http://localhost:12344")
        b.wait_present("modal-dialog .form-table-ct #gpg-verify:checked")
        b.wait_not_present("modal-dialog #gpg-data")
        b.wait_present("modal-dialog .form-table-ct button.btn-default")
        b.click("modal-dialog .form-table-ct button.btn-default")
        b.wait_present("modal-dialog #gpg-data")
        b.wait_not_present("modal-dialog .form-table-ct button.btn-default")
        b.set_val("modal-dialog #gpg-data", "bad")
        b.click("modal-dialog .group-buttons button.btn-primary")
        b.wait_present("modal-dialog .group-buttons div.dialog-error")

        with open(os.path.join(TEST_DIR, "verify", "files", "publickey.asc"), 'rb') as fp:
            gpg_data = fp.read()

        b.set_val("modal-dialog #gpg-data", gpg_data)
        b.click("modal-dialog .group-buttons button.btn-primary")
        b.wait_not_present ("modal-dialog .group-buttons")
        b.wait_not_present("modal-dialog .form-table-ct")
        b.wait_not_present("modal-dialog .modal-footer button.disabled")
        m.execute("ls /sysroot/ostree/repo/zremote-test1.trustedkeys.gpg")

        b.wait_present("modal-dialog .modal-body .list-group-item:contains('zremote-test1') .listing-ct-actions button.pficon-edit")
        b.click("modal-dialog .modal-body .list-group-item:contains('zremote-test1') .listing-ct-actions button.pficon-edit")
        b.wait_present("modal-dialog .group-buttons")
        b.wait_present("modal-dialog .modal-footer button.disabled")
        b.wait_present("modal-dialog .group-buttons button.btn-primary")
        b.wait_present("modal-dialog .form-table-ct")
        b.wait_in_text("modal-dialog .form-table-ct", "http://localhost:12344")
        b.wait_present("modal-dialog .form-table-ct button.btn-default")
        b.wait_present("modal-dialog .form-table-ct #gpg-verify:checked")
        b.click("modal-dialog .form-table-ct #gpg-verify")
        b.click("modal-dialog .group-buttons button.btn-primary")
        b.wait_not_present("modal-dialog .form-table-ct")
        b.wait_not_present ("modal-dialog .group-buttons")
        b.wait_not_present("modal-dialog .modal-footer button.disabled")
        b.click("modal-dialog .modal-body .list-group-item:contains('zremote-test1')")
        b.click("modal-dialog .modal-footer button.btn-primary")
        b.wait_not_present("modal-dialog")

        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "zremote-test1")
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", "zremote-branch1")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li")
        b.wait_not_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu div.alert")
        b.wait_in_text("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:first", "zremote-branch1")
        b.wait_in_text("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:last", "zremote-branch2")
        b.call_js_func("ph_count_check", "table.listing-ct tbody:nth-child(2) ul.dropdown-menu li",  2)

        self.assertEqual(m.execute("cat /etc/ostree/remotes.d/zremote-test1.conf").strip(),
                         '[remote "zremote-test1"]\nurl=http://localhost:12344\ngpg-verify = false')

        # Check updates display
        b.wait_present("table.listing-ct tbody:nth-child(3)")
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " cockpit-base.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.origin", "local:{}".format(branch))


        b.wait_in_text("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button", "Check for Updates")
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")

        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " zremote-branch1.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button", "Rebase")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.os", name)
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.origin", "zremote-test1:zremote-branch1")

        b.wait_not_visible("table.listing-ct tbody:nth-child(3) div.packages")
        b.wait_present('table.listing-ct tbody:nth-child(3) ul')
        b.click('table.listing-ct tbody:nth-child(3) ul li a:contains("Packages")')
        b.wait_visible("table.listing-ct tbody:nth-child(3) div.packages")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.packages", "contains the same packages as your currently booted")

        # Switching branch hides other
        b.click("#change-branch")
        b.wait_visible("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_in_text("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:last", "zremote-branch2")
        b.click("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:last a")
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " cockpit-base.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')

        # Switching back shows pulled
        b.wait_present('#change-branch')
        b.click("#change-branch")
        b.wait_visible("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_in_text("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:first", "zremote-branch1")
        b.click("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li:first a")
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " zremote-branch1.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')

        # Refresh, back to local, pull in update
        b.reload()
        b.enter_page("/updates")
        b.wait_present('table.listing-ct tbody:nth-child(2) i.fa-caret-up')
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", branch)
        b.wait_in_text("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button", "Check for Updates")
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " bad-version")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')

        # Switching to branch shows pulled
        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "local")
        b.click("#change-repo")
        b.wait_present("modal-dialog")
        b.wait_present("modal-dialog .modal-body .list-group")
        b.wait_in_text("modal-dialog .modal-body .list-group-item:contains('zremote-test1')", "zremote-test1")
        b.click("modal-dialog .modal-body .list-group-item:contains('zremote-test1')")
        b.click("modal-dialog .modal-footer button.btn-primary")
        b.wait_not_present("modal-dialog")

        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "zremote-test1")
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", "zremote-branch1")
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " zremote-branch1.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_not_in_text('table.listing-ct', name + " bad-version")

        # delete
        b.wait_present('#change-repo')
        b.click("#change-repo")
        b.wait_present("modal-dialog")
        b.wait_present("modal-dialog .modal-body .list-group")
        b.wait_in_text("modal-dialog .modal-body .list-group-item:contains('zremote-test1')", "zremote-test1")
        b.wait_present("modal-dialog .modal-body .list-group-item:contains('zremote-test1') .listing-ct-actions button.pficon-edit")
        b.click("modal-dialog .modal-body .list-group-item:contains('zremote-test1') .listing-ct-actions button.pficon-edit")
        b.wait_present("modal-dialog .group-buttons")
        b.wait_present("modal-dialog .group-buttons button.btn-danger")
        b.wait_present("modal-dialog .modal-footer button.disabled")
        b.wait_present("modal-dialog .form-table-ct")
        b.wait_in_text("modal-dialog .form-table-ct", "http://localhost:12344")
        b.wait_present("#gpg-verify")
        b.wait_present("modal-dialog .form-table-ct #gpg-verify:not(:checked)")
        b.click("modal-dialog .group-buttons button.btn-danger")
        b.wait_not_present("modal-dialog .form-table-ct")
        b.wait_not_present("modal-dialog .group-buttons")

        b.wait_present("modal-dialog .modal-body .list-group")
        b.wait_not_in_text("modal-dialog .modal-body .list-group", "zremote-test1")
        b.wait_in_text("modal-dialog .modal-body .list-group", "local")
        b.wait_not_present("modal-dialog .modal-body .list-group .active")
        b.wait_present("modal-dialog .modal-footer button.disabled")
        b.click("modal-dialog .modal-body .list-group-item:contains('local')")
        b.wait_present("modal-dialog .modal-body .list-group .active")
        b.click("modal-dialog .modal-footer button.btn-primary")
        b.wait_not_present("modal-dialog")
        b.wait_present('#change-repo')
        b.wait_text("#change-repo", "local")
        b.wait_present("#change-branch")
        b.wait_in_text("#change-branch", branch)
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu")
        b.wait_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li")
        b.wait_not_present("table.listing-ct tbody:nth-child(2) ul.dropdown-menu li div.alert")
        b.call_js_func("ph_count_check", "table.listing-ct tbody:nth-child(2) ul.dropdown-menu li",  1)
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " bad-version")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.origin", "local:{}".format(branch))

    def testPermission(self):
        m = self.machine
        b = self.browser

        rhsmcertd_hack(m)

        # Create a user
        m.execute("useradd user -c 'User' || true")
        m.execute("echo foobar | passwd --stdin user")

        # login
        m.start_cockpit()
        b.open("/updates")
        b.wait_visible("#login")
        b.set_val("#login-user-input", "user")
        b.set_val("#login-password-input", "foobar")
        b.click('#login-button')
        b.expect_load()

        b.enter_page("/updates")

        b.wait_present(".curtains-ct")
        b.wait_in_text(".curtains-ct", "Not authorized")
        self.assertIn("Reconnect", b.text(".blank-slate-pf-main-action button"))
        self.allow_authorize_journal_messages()

if __name__ == "__main__":
    test_main()
