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

import parent
from testlib import *
import json
import os
import sys
import unittest
import testinfra

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 start_trivial_httpd(m):
    remote = m.execute("ostree remote show-url local")
    parts = remote.strip().split(":")

    # If ostree supports rpm-ostreed then it support the -P option
    args = [parts[-1], REPO_LOCATION]
    m.execute("ostree trivial-httpd -d -P {0} {1} ".format(*args))


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))

@skipImage("No OSTree available", "centos-7", "debian-8", "debian-unstable", "fedora-24", "fedora-25", "fedora-testing", "rhel-7", "ubuntu-1604")
class OstreeRestartCase(MachineCase):

    def setUp(self):
        # we need a static ip for this test becuase we restart
        MachineCase.setUp(self, macaddr='52:54:00:9e:00:F5')

    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

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

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

        # Require signatures
        m.execute("sed -i /gpg-verify/d /etc/ostree/remotes.d/local.conf")

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

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

        if "rhel-atomic" in m.image:
            name = "rhel-atomic-host"
        elif 'continuous' in m.image:
            name = 'centos-atomic-host'
        else:
            name = "fedora-atomic"

        # Check current and rollback target
        b.wait_present('table.listing-ct')
        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")

        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')
        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')
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")

        # Serve repo
        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')
        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", "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
        pid = m.execute('ps -C ostree -o pid h')
        m.execute('kill {0}'.format(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')
        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")
        m.reset_reboot_flag()

        # After reboot, check commit
        b.wait_present('table.listing-ct')
        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")
        m.reset_reboot_flag()

        # After reboot upgrade but no rollback target
        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()


@skipImage("No OSTree available", "centos-7", "debian-8", "debian-unstable", "fedora-24", "fedora-25", "fedora-testing", "rhel-7", "ubuntu-1604")
class OstreeCase(MachineCase):

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

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

        # 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.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()
