#!/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 parent
from testlib import *
import os
import unittest
import time
import sys
import re
from kubelib import *
# NOTE: Both TestOpenshift and TestRegistry are in this single file to
# prevent them from being run concurrently. Both use a 'openshift'
# machine, and we can only run a single one of those at the same time.
def wait_project(machine, project):
i = 0
found = True
while True:
try:
output = machine.execute("oc get projects")
if project not in output:
if not found:
print >> sys.stderr, output
found = True
raise Exception(output)
break
except:
if i > 60:
raise
i = i + 1
time.sleep(2)
@skipImage("Kubernetes not packaged", "debian-stable", "debian-testing", "ubuntu-1604", "ubuntu-stable", "fedora-i386")
@skipImage("No cockpit-kubernetes packaged", "continuous-atomic", "fedora-atomic", "rhel-atomic")
class TestOpenshift(MachineCase, OpenshiftCommonTests):
additional_machines = {
'openshift': { 'machine': { 'image': 'openshift' } }
}
def setUp(self):
super(TestOpenshift, self).setUp()
self.openshift = self.machines['openshift']
self.openshift.upload(["verify/files/mock-app-openshift.json"], "/tmp")
self.kubeconfig = os.path.join(self.tmpdir, "config")
self.openshift.download("/root/.kube/config", self.kubeconfig)
m = self.machine
m.execute("mkdir -p /home/admin/.kube")
m.upload([self.kubeconfig], "/home/admin/.kube/config")
m.execute("chown -R admin:admin /home/admin/.kube")
wait_project(self.openshift, "marmalade")
# Expect the default container user limitations during testing
self.openshift.execute("oc patch scc restricted -p '{ \"runAsUser\": { \"type\": \"MustRunAsRange\" } }'")
self.browser.wait_timeout(120)
def testConnect(self):
m = self.machine
b = self.browser
# Make sure we can write to kubeconfig
m.execute("chown -R admin:admin /home/admin/.kube")
self.login_and_go("/kubernetes")
b.wait_present("#service-list")
b.wait_in_text("#service-list", "docker-registry")
b.wait_present("a[href='#/volumes']")
b.click("a[href='#/volumes']")
b.wait_present(".pv-listing")
b.wait_in_text(".pv-listing", "No volumes are present")
b.click("a[href='#/']")
b.wait_present("#kubernetes-change-connection")
b.click("#kubernetes-change-connection")
b.wait_present("modal-dialog")
b.wait_present("#kubernetes-cluster")
b.wait_present("#kubernetes-user")
b.wait_not_present("#kubernetes-username")
b.wait_not_present("#kubernetes-password")
b.wait_not_present("#kubernetes-token")
b.wait_in_text("#kubernetes-cluster button", "10-111-112-101:8443")
b.wait_in_text("#kubernetes-user button", "system:admin/10-111-112-101:8443")
b.wait_in_text("modal-dialog", "Client Certificate")
b.click("#kubernetes-user button")
b.click("#kubernetes-user ul li:last-child a")
b.wait_in_text("#kubernetes-user button", "Add New User")
b.wait_not_present("#kubernetes-token")
b.wait_val("#kubernetes-username", "")
b.wait_val("#kubernetes-password", "")
b.set_val("#kubernetes-username", "new-user")
b.set_val("#kubernetes-password", "new-user")
b.click("modal-dialog div.modal-footer button.btn-primary")
b.wait_not_present("modal-dialog")
# scruffy isn't a admin
b.wait_present("#service-list")
b.wait_not_in_text("#service-list", "docker-registry")
b.wait_present("a[href='#/volumes']")
b.click("a[href='#/volumes']")
b.wait_present(".pv-listing")
b.wait_in_text(".pv-listing", "cannot watch all")
b.click("a[href='#/']")
b.wait_present("#kubernetes-change-connection")
b.click("#kubernetes-change-connection")
b.wait_present("modal-dialog")
b.wait_present("#kubernetes-cluster")
b.wait_present("#kubernetes-user")
b.wait_not_present("#kubernetes-username")
b.wait_not_present("#kubernetes-password")
b.wait_present("#kubernetes-token")
b.wait_in_text("#kubernetes-cluster button", "10-111-112-101:8443")
b.wait_in_text("#kubernetes-user button", "new-user/10-111-112-101:8443")
b.wait_not_in_text("modal-dialog", "Client Certificate")
self.assertFalse(b.val("#kubernetes-token") == "")
b.logout()
# Test the saved kube config file
m.execute("rm /home/admin/.kube/config")
m.upload([self.kubeconfig], "/home/admin/.kube/config")
m.execute("chown -R admin:admin /home/admin/.kube")
self.login_and_go("/kubernetes")
b.wait_present("#service-list")
b.wait_in_text("#service-list", "docker-registry")
@unittest.skipIf(True, "Nulecule deploys temporarily removed.")
def testDeployDialog(self):
b = self.browser
m = self.machine
b.wait_timeout(240)
m.execute("systemctl start docker")
# m.execute("docker pull submod/helloapache")
tmpfile = os.path.join(self.tmpdir, "oc")
self.openshift.download("/usr/bin/oc", tmpfile)
m.upload([tmpfile], "/usr/local/bin")
self.login_and_go("/kubernetes")
b.wait_present("#service-list")
b.wait_in_text("#service-list", "registry")
# 1)check atomic version
output = m.execute("atomic -v 2>&1")
self.assertTrue(float(output) >= 1.1)
# 2)check provider is supported
m.execute("mkdir /var/tmp/invalid-app1")
m.execute("""echo -e '
FROM busybox
MAINTAINER cockpit
LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
RUN="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} run \${OPT3} /atomicapp" \
STOP="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} stop \${OPT3} /atomicapp" \
INSTALL="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} install \${OPT3} --destination /atomicapp /application-entity" \
io.projectatomic.nulecule.providers="kubernetes" \
io.projectatomic.nulecule.specversion=0.0.2 \
io.projectatomic.nulecule.atomicappversion="0.1.11"
' > /var/tmp/invalid-app1/Dockerfile""")
m.execute("docker build -t test/invalid-app1 /var/tmp/invalid-app1")
m.execute("rm -rf /var/tmp/invalid-app1")
b.click("#deploy-app")
b.wait_popup("deploy-app-dialog")
b.set_val("#deploy-app-type", "nulecule")
b.set_val("#deploy-app-nulecule-image", "test/invalid-app1")
b.set_val("#deploy-app-namespace", "mynamespace")
b.click("#deploy-app-start")
b.wait_not_attr("#deploy-app-start", "disabled", "disabled")
b.is_visible(".modal-footer .alert")
self.assertEqual(b.text(".modal-footer .alert") ,"No supported providers found.")
b.dialog_cancel("#deploy-app-dialog")
# 3)check atomicappversion is supported
m.execute("mkdir /var/tmp/invalid-app2")
m.execute("""echo -e '
FROM busybox
MAINTAINER cockpit
LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
RUN="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} run \${OPT3} /atomicapp" \
STOP="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} stop \${OPT3} /atomicapp" \
INSTALL="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} install \${OPT3} --destination /atomicapp /application-entity" \
io.projectatomic.nulecule.providers="kubernetes,openshift" \
io.projectatomic.nulecule.specversion=0.0.2 \
io.projectatomic.nulecule.atomicappversion="0.0.11"
' > /var/tmp/invalid-app2/Dockerfile""")
m.execute("docker build -t test/invalid-app2 /var/tmp/invalid-app2")
m.execute("rm -rf /var/tmp/invalid-app2")
b.click("#deploy-app")
b.wait_popup("deploy-app-dialog")
b.set_val("#deploy-app-type", "nulecule")
b.set_val("#deploy-app-nulecule-image", "test/invalid-app2")
b.set_val("#deploy-app-namespace", "mynamespace")
b.click("#deploy-app-start")
b.wait_not_attr("#deploy-app-start", "disabled", "disabled")
b.is_visible(".modal-footer .alert")
self.assertEqual(b.text(".modal-footer .alert") ,"atomicapp version 0.0.11 is not supported.")
b.dialog_cancel("#deploy-app-dialog")
# 5)check for all metadata
m.execute("mkdir /var/tmp/invalid-app4")
m.execute("""echo -e '
FROM busybox
MAINTAINER cockpit
LABEL io.projectatomic.nulecule.providers="kubernetes,openshift"
' > /var/tmp/invalid-app4/Dockerfile""")
m.execute("docker build -t test/invalid-app4 /var/tmp/invalid-app4")
m.execute("rm -rf /var/tmp/invalid-app4")
b.click("#deploy-app")
b.wait_popup("deploy-app-dialog")
b.set_val("#deploy-app-type", "nulecule")
b.set_val("#deploy-app-nulecule-image", "test/invalid-app4")
b.set_val("#deploy-app-namespace", "mynamespace")
b.click("#deploy-app-start")
b.wait_not_attr("#deploy-app-start", "disabled", "disabled")
b.is_visible(".modal-footer .alert")
self.assertEqual(b.text(".modal-footer .alert") ,"This image is not a supported Nulecule image")
b.dialog_cancel("#deploy-app-dialog")
# 6)check when atomicapp is not available
m.execute("mkdir /var/tmp/invalid-app5")
m.execute("""echo -e '
FROM busybox
MAINTAINER cockpit
LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
RUN="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} run \${OPT3} /atomicapp" \
STOP="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} stop \${OPT3} /atomicapp" \
INSTALL="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} install \${OPT3} --destination /atomicapp /application-entity" \
io.projectatomic.nulecule.providers="kubernetes,openshift" \
io.projectatomic.nulecule.specversion=0.0.2 \
io.projectatomic.nulecule.atomicappversion="0.1.11"
' > /var/tmp/invalid-app5/Dockerfile""")
m.execute("docker build -t test/invalid-app5 /var/tmp/invalid-app5")
m.execute("rm -rf /var/tmp/invalid-app5")
b.click("#deploy-app")
b.wait_popup("deploy-app-dialog")
b.set_val("#deploy-app-type", "nulecule")
b.set_val("#deploy-app-nulecule-image", "test/invalid-app5")
b.set_val("#deploy-app-namespace", "mynamespace")
b.click("#deploy-app-start")
b.wait_not_attr("#deploy-app-start", "disabled", "disabled")
b.is_visible(".modal-footer .alert")
self.assertEqual(b.text(".modal-footer .alert") ,"Image failed to install.")
b.dialog_cancel("#deploy-app-dialog")
# 7) fail when Unable to pull Nulecule app
b.click("#deploy-app")
b.wait_popup("deploy-app-dialog")
b.set_val("#deploy-app-type", "nulecule")
b.set_val("#deploy-app-nulecule-image", "submod/helloapache1")
b.set_val("#deploy-app-namespace", "mynamespace")
b.click("#deploy-app-start")
b.wait_not_attr("#deploy-app-start", "disabled", "disabled")
b.is_visible(".modal-footer .alert")
self.assertEqual(b.text(".modal-footer .alert") ,"Unable to pull Nulecule app image.")
b.dialog_cancel("#deploy-app-dialog")
# 8) check if app can be deployed
b.click("#deploy-app")
b.wait_popup("deploy-app-dialog")
b.set_val("#deploy-app-type", "nulecule")
b.set_val("#deploy-app-nulecule-image", "submod/helloapache:0.1.11")
b.set_val("#deploy-app-namespace", "mynamespace")
b.click("#deploy-app-start")
self.allow_journal_messages('Could not find any image matching "submod/helloapache:0.1.11".')
b.wait_not_attr("#deploy-app-start", "disabled", "disabled")
b.click("#deploy-app-start")
b.wait_popdown("deploy-app-dialog")
b.click("a[href='#/list']")
b.wait_present("#content .details-listing")
b.wait_present(".details-listing tbody[data-id='pods/default/helloapache'] th")
self.assertEqual(b.text(".details-listing tbody[data-id='pods/default/helloapache'] th"), "helloapache")
def testReconnectChangeCert(self):
m = self.machine
b = self.browser
# Try to connect with an old and non-matching client cert
old_cert = ('LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUREVENDQWZXZ0F3SUJBZ0lCQnpBTkJna3Foa2'
'lHOXcwQkFRc0ZBREFtTVNRd0lnWURWUVFEREJ0dmNHVnUKYzJocFpuUXRjMmxuYm1WeVFERTBPVEEy'
'T0RFMk16RXdIaGNOTVRjd016STRNRFl4TXpVeVdoY05NVGt3TXpJNApNRFl4TXpVeldqQTNNUjR3SE'
'FZRFZRUUtFeFZ6ZVhOMFpXMDZZMngxYzNSbGNpMWhaRzFwYm5NeEZUQVRCZ05WCkJBTVRESE41YzNS'
'bGJUcGhaRzFwYmpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUIKQU5Rel'
'J5SzUzQUhmdlZnME5GTHRSSENhMTEyK3l5a0xXdG14bjAxb2JTMy85VmovQU1UdmZwNVRkUlhKNjF'
'WcAo3L0N1L1pkQ3RDc1pyNnJpYVMxbUJGcmtTSkZJdmFjN2NNa3k2M0tVVXNaQmU5ZGFLdG1OMmhY'
'TUt0VitESStvCjJFQVJNWlV5YTZIMzJXZUpzRGM0L1lscUR5TFAxYVR3NWNwRTJPY3dWQTdoQ1dCS'
'ysyajIvZTl6RDhrYzM2R24KNG0wZWd3YWxlZ2UwTXFaUk1BbTFkenRpS3I1UWZ4MG9ZVUY3Z0JIYm'
'RjM253cGZ6a3M2K1F4Tkl6V0hlRmN2WApxcXpsMGxnT2ZGeWc0VWptYzhFcTBiKy9ER3lYSGlHNXN'
'vVmw1RGlVa1RKRjNrcURCZVVjZWNZaEx1VDd4emxzClk0bldYVFprc3lJMXVoZFJmS2NnbVZjQ0F3'
'RUFBYU0xTURNd0RnWURWUjBQQVFIL0JBUURBZ1dnTUJNR0ExVWQKSlFRTU1Bb0dDQ3NHQVFVRkJ3T'
'UNNQXdHQTFVZEV3RUIvd1FDTUFBd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQgpBRy9rTElnQ2YwK2'
'1MZ1VXcWZjS2NLN0Nmdm9PbS9qL0FUSW1MR0YvSUtvQTRCWGhqMG5EcEszeVd3ZGt4d0hZCmxxUDh'
'xZ1NyQ1FaNkVoSlpMSWtjQWovTUlTUEUvSlJPa3R5TWFTMis4OGhqeGpxdUhucnZ5ODA5ZlJ5QzhF'
'R2kKeVIyRzhtNGJ5MEJrOWhENkVxbDYxb21VU0MzL2ozR3lPUGNZWDJEQjZsU2h4ZlFJVEpqUWNKQ'
'0oyMnNDdlBBOApVeU9EaUgrNllZSVdtVFN5a2kzazk0Q3NOZXlRbERjNzh3a1BseUdrN0p1anFIK2p'
'KaURXSDQ2TXE3TTNaVVArCmowQWxhd3dtdllsRjBVZEIwdGRCenZWR21RbTRudEwwSkhVMGFqRnUy'
'QTYvTjJmT3VrZWI0TDR6elBzSkJFNHIKaUUyNWRJUlAvWHRoM0tjRFYyYkxtMUk9Ci0tLS0tRU5EI'
'ENFUlRJRklDQVRFLS0tLS0K')
old_key = ('LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBMUROSElybmNBZC'
's5V0RRMFV1MUVjSnJYWGI3TEtRdGEyYkdmVFdodExmLzFXUDhBCnhPOStubE4xRmNuclZXbnY4Szc5'
'bDBLMEt4bXZxdUpwTFdZRVd1UklrVWk5cHp0d3lUTHJjcFJTeGtGNzExb3EKMlkzYUZjd3ExWDRNa'
'jZqWVFCRXhsVEpyb2ZmWlo0bXdOemo5aVdvUElzL1ZwUERseWtUWTV6QlVEdUVKWUVyNwphUGI5Nz'
'NNUHlSemZvYWZpYlI2REJxVjZCN1F5cGxFd0NiVjNPMklxdmxCL0hTaGhRWHVBRWR0MXplZkNsL09'
'TCnpyNURFMGpOWWQ0Vnk5ZXFyT1hTV0E1OFhLRGhTT1p6d1NyUnY3OE1iSmNlSWJteWhXWGtPSlNS'
'TWtYZVNvTUYKNVJ4NXhpRXU1UHZIT1d4amlkWmRObVN6SWpXNkYxRjhweUNaVndJREFRQUJBb0lCQ'
'UNxbjNDYlk0YWJteVBNUQpHMnlJRVhmcFNGMnAyc0QzYlYzUlhNcDhzV1hMekJBRndxdlQwTW9XME'
'xSK2tIWHRBN1NJR0tYdFhMWkZSWkMrClRwSTNyYXh2c3o2eE5wNkZUbGpEaVp6UXdBcm1ZdlNaUlg'
'vU0NnTFR0ZENRdEFtMDBUT2Z3UzNTb3R3K0xFK3AKMStoaDVtVlhFby9XNDRWeWYxNjNsRHAwOXBD'
'K3dpS0ZEa2JHVExBdnA1bnFaMnhtZDRyNzhyMi9TZmZ2YUplZQpJSlpwbENMYzMyQkVZaE4yeDRIa'
'HpqQkhOdTJwYkFXS2twUDVjWkZNS3QwSUkrRTN0UUNWMlkrYVZvNTY0TzRGCjZxMmFUUzVxMnRuaW'
'VBTS9uay8zN3hkNUVoNjRpMU8vQTU2YzdoZmxkMDVQMC9PdU9OY2dsaVVYRG44cnFvOUoKdXpFQ1F'
'ORUNnWUVBN0xOeWRQQlhkdEZRZ3NrWG56UlVYM3hhYWFCYU8xYnJYZzVmUnlMVm5DY2thWlRORXlE'
'dAp3eURxSGRUOGtRNXNIcXZ3WDNxNXR1elZBZ3NJUlhvcTZIUGtxRWdLeFZIclhTNzY1OFhLUnR1S'
'GswbE11ZkQxCnVPVVBaTTJYNVR4NXRmTmh1Zlh3dXZqTkdyN0E3ZEw4VUFiY3ZtS2VuSnF2bDNJZX'
'I0cVpkdDBDZ1lFQTVZQncKZ2pNTGJRZStEUzd1QmdsOFVmdGl5YnZCbDdTSEQ4T2RWamVOdzNEZjJ'
'uckltQVdLTVhNTk5GWldwbmhhc0g3Swp1bWtMQWdMNWFEWXJhaFJHN2ZwMGd0YnN0RVE5Uit2dFVp'
'azMxaFNHUS83dFBENU1LaU1jcFpnazhYUXI5UnlFCkVEN285bWFvUEZibnJKbFl5VXNQY0FCN3Y1W'
'FNxaVdqeFZMODI4TUNnWUVBNUhoSk1DcVVvZkZqL3ZsUFBiSnIKQmtlbmxYRGI1NDc4WEtzT3VFRW'
'RZajQ5M1ZOdHB0c1A1RnF1MytDbmNQUTAxRjR1QkZzWFMwUEtUdENMU1ZTawplZjd6WktNMUVrVUN'
'JODJuRFhSU3pKWTFoS3NwemdpUmhjaERWWTlFNEZYQlBTa1EyVWhVOW9RVXBZNGQ5dkRCCjdoVFJt'
'VXJqd2xGa3o0K3RvczdyVmxrQ2dZRUF4RW8vY0V5aVNDV29HblI2Sm5XMGZCWUxuMGxVUWlHb3B3W'
'UQKS3Z1bTUzTkNNd1p6VFByb0FIVkw1T2kzZ2ZoTWNNcHhNRkNwbHBYZXBaQTNQNnFLSS83ajZnaF'
'RPYmRueG56MgpaU0JWM21kOWt1aVdGY0dldVNlQTErMHlJOFhkMXU0RjBqTk1ZM3JZQjR1NDZQbmJ'
'ZNGNzYy9vbDNXNFNXVzZLCkRUcDJoS3NDZ1lCSlpMQys3Uy9zRGVqdkl0MTZ2Q3JzbDJlWlFsb1p0'
'clNoVCtTb0hmR1NPRXZkMXp1NStBL3UKaVVDYyt1SHdUa1c0RVpMTEdBYkUzTG1xSllJcXNkNVpUW'
'UkxWTZ1TGoyV1NGWFZYUXVLanVlTDZJdGttc1dvZgpyMHFtQU93RHdFVDFvRXlNVUJoOTJVMmhxSHR'
'aamlMdkxQdDc5aUhacDNtTnlLVjc5QXY3dVE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=')
m.execute("sed -i '/client-certificate-data:/ s/:.*$/: %s/; /client-key-data:/ s/:.*$/: %s/' /home/admin/.kube/config" % (
old_cert, old_key))
m.execute("chown -R admin:admin /home/admin/.kube")
self.login_and_go("/kubernetes")
b.wait_present(".curtains-ct")
b.wait_visible(".curtains-ct")
b.wait_in_text(".curtains-ct", "Couldn't connect to server")
b.wait_in_text(".curtains-ct", "Unauthorized")
b.wait_present(".curtains-ct #kubernetes-reconnect")
# now provide a good certificate, and reconnect
m.upload([self.kubeconfig], "/home/admin/.kube/config")
m.execute("chown -R admin:admin /home/admin/.kube")
b.click("#kubernetes-reconnect")
b.wait_present("#service-list")
@skipImage("Kubernetes not packaged", "debian-stable", "debian-testing", "ubuntu-1604", "ubuntu-stable", "fedora-i386")
@skipImage("No cockpit-kubernetes packaged", "continuous-atomic", "fedora-atomic", "rhel-atomic")
class TestRegistry(MachineCase):
additional_machines = {
'openshift': { 'machine': { 'image': 'openshift' } }
}
def setUp(self):
super(TestRegistry, self).setUp()
self.openshift = self.machines['openshift']
# Sync over the kube config file
tmpfile = os.path.join(self.tmpdir, "config")
self.openshift.download("/root/.kube/config", tmpfile)
with open(tmpfile, "r") as f:
self.machine.execute("mkdir -p /home/admin/.kube && cat > /home/admin/.kube/config", input=f.read())
wait_project(self.openshift, "marmalade")
self.browser.wait_timeout(120)
def setupDockerRegistry(self):
"""Run a docker registry instance and populate it
The OpenShift registry can pull image streams from localhost:5555 for
testing.
"""
# set up a docker registry with cert, as openshift registry expects https
self.openshift.execute("docker run -d -p 5555:5000 --name testreg "
"-v /openshift.local.config/master/:/certs "
"-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/master.server.crt "
"-e REGISTRY_HTTP_TLS_KEY=/certs/master.server.key "
"registry:2")
self.openshift.execute("while ! curl -s --connect-timeout 1 https://localhost:5555/; do sleep 1; done")
self.addCleanup(self.openshift.execute, "docker rm -f testreg")
# populate it with some images
self.openshift.execute("docker tag registry:5000/marmalade/juggs:latest localhost:5555/juggs:latest; "
"docker tag registry:5000/marmalade/juggs:2.11 localhost:5555/juggs:2.11; "
"docker push localhost:5555/juggs")
def testImages(self):
b = self.browser
o = self.openshift
self.login_and_go("/kubernetes/registry")
b.wait_present(".dashboard-images")
# The default view should be overwhelmed with pizzazz images
b.wait_in_text(".card-pf-wide.dashboard-images", "pizzazz/monster")
b.wait_not_in_text(".card-pf-wide.dashboard-images", "default/busybox")
b.wait_not_in_text(".card-pf-wide.dashboard-images", "marmalade/busybee")
b.wait_not_in_text(".card-pf-wide.dashboard-images", "marmalade/juggs")
b.wait_not_in_text(".card-pf-wide.dashboard-images", "marmalade/origin")
# Filter the dashboard to marmalide project
b.click(".dashboard-images .namespace-filter button")
b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu")
b.wait_present(".dashboard-images .namespace-filter a[value='marmalade']")
b.click(".dashboard-images .namespace-filter a[value='marmalade']")
b.wait_not_in_text(".card-pf-wide.dashboard-images", "pizzazz/")
b.wait_in_text(".card-pf-wide.dashboard-images", "marmalade/busybee")
# Lets navigate to an image stream
b.click("a[href='#/images/marmalade/busybee']")
b.wait_in_text(".content-filter h3", "marmalade/busybee")
b.click("tbody[data-id='marmalade/busybee:0.x'] tr td.listing-ct-toggle")
b.wait_present("tbody[data-id='marmalade/busybee:0.x'] .listing-ct-panel dl.registry-image-tags")
b.wait_in_text("tbody[data-id='marmalade/busybee:0.x'] .listing-ct-panel dl.registry-image-tags", "marmalade/busybee:0.x")
# Look at the image layers
b.click(".listing-ct-head li:last-child a")
b.wait_present(".listing-ct-body .registry-image-layers")
b.wait_visible(".listing-ct-body .registry-image-layers")
b.wait_in_text(".listing-ct-body .registry-image-layers", "KiB")
# Add postgres into the stream
output = o.execute("oc get imagestream --namespace=marmalade --template='{{.spec}}' busybee")
self.assertNotIn("postgres", output)
b.click(".pficon-edit")
b.wait_present("modal-dialog")
b.wait_visible("#imagestream-modify-populate")
b.click("#imagestream-modify-populate button")
b.wait_visible("#imagestream-modify-populate .dropdown-menu")
b.click("#imagestream-modify-populate a[value='pull']")
b.wait_visible("#imagestream-modify-pull")
b.set_val("#imagestream-modify-pull", "postgres")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_in_text ("#content", "postgres")
output = o.execute("oc get imagestream --namespace=marmalade --template='{{.spec}}' busybee")
self.assertIn("postgres", output)
# Remove postgres from the stream
b.click(".pficon-edit")
b.wait_present("modal-dialog")
b.wait_visible("#imagestream-modify-populate")
b.click("#imagestream-modify-populate button")
b.wait_visible("#imagestream-modify-populate .dropdown-menu")
b.click("#imagestream-modify-populate a[value='none']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_not_in_text ("#content", "postgres")
output = o.execute("oc get imagestream --namespace=marmalade --template='{{.spec}}' busybee")
self.assertNotIn("postgres", output)
# Go to the images view and create a new imagestream
b.click("#content a[href='#/images/marmalade']")
b.wait_present("a i.pficon-add-circle-o")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#imagestream-modify-name")
b.set_val("#imagestream-modify-name", "zero")
b.wait_val("#imagestream-modify-project-text", "marmalade")
b.click("#imagestream-modify-project button")
b.wait_visible("#imagestream-modify-project .dropdown-menu")
b.click("#imagestream-modify-project a[value='default']")
b.wait_val("#imagestream-modify-project-text", "default")
b.set_val("#imagestream-modify-project-text", "###")
b.click(".btn-primary")
b.wait_visible(".dialog-error")
b.set_val("#imagestream-modify-project-text", "default")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
# Switch to the default namespace and look for what we created
b.click("filter-bar .namespace-filter button")
b.wait_visible("filter-bar .namespace-filter .dropdown-menu")
b.click("filter-bar .namespace-filter a[value='default']")
b.wait_visible("tbody[data-id='default/zero']")
# Go to the images view and check annotations
b.wait_present("tbody[data-id='default/busybox']")
b.click("tbody[data-id='default/busybox'] th")
b.wait_present(".content-filter h3")
b.wait_in_text(".content-filter h3", "default/busybox")
b.wait_in_text("#content", "Annotations")
b.wait_in_text("registry-imagestream-meta", "openshift.io/image.dockerRepositoryCheck")
# Delete the tagged image from its own screen
b.go("#/images/marmalade/busybee:0.x")
b.wait_in_text(".content-filter h3", "marmalade/busybee:0.x")
b.click(".pficon-delete")
b.wait_present("modal-dialog")
b.click("modal-dialog .btn-danger")
b.wait_not_present("modal-dialog")
# Should redirect to the imagestream page
b.wait_in_text(".content-filter", "Show all image streams")
b.wait_not_in_text("#content", "0.x")
# Delete via the main UI
b.wait_present("tbody[data-id='marmalade/busybee:latest']")
b.click("tbody[data-id='marmalade/busybee:latest'] tr.listing-ct-item td.listing-ct-toggle")
b.wait_present("tbody[data-id='marmalade/busybee:latest'] .listing-ct-panel dl.registry-image-tags")
b.wait_in_text("tbody[data-id='marmalade/busybee:latest'] .listing-ct-panel dl.registry-image-tags", "marmalade/busybee:latest")
b.click("tbody[data-id='marmalade/busybee:latest'] .listing-ct-head .pficon-delete")
b.wait_present("modal-dialog")
b.click("modal-dialog .btn-danger")
b.wait_not_present("modal-dialog")
# All tags here have been removed
b.wait_not_in_text("#content", "latest")
# Show the image on the right screen
b.go("#/images/marmalade/juggs")
b.wait_in_text(".content-filter h3", "marmalade/juggs")
b.wait_present("tbody[data-id='marmalade/juggs:2.9']")
b.click("tbody[data-id='marmalade/juggs:2.9'] tr.listing-ct-item td.listing-ct-toggle")
# Various labels should show up in this image
b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] .listing-ct-panel", "Juggs Image")
b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] registry-image-body", "This is a test description of an image. It can be as long as a paragraph, featuring a nice brogrammer sales pitch.")
b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] registry-image-body", "http://hipsum.co")
# And some key labels shouldn't show up on the metadata
b.click("tbody[data-id='marmalade/juggs:2.9'] .listing-ct-head li:last-child a")
b.wait_present("tbody[data-id='marmalade/juggs:2.9'] registry-image-meta")
b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] registry-image-meta", "build-date=2016-03-04")
def testProjectGroups(self):
b = self.browser
self.login_and_go("/kubernetes/registry")
b.go("#/projects")
b.wait_present("tbody[data-id='marmalade']")
# Create a new group
b.click("#add-group")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.set_val("#group_name", "production")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_present("tbody[data-id='marmalade']")
#group page
b.click("tbody[data-id='production'] tr:first-child td:nth-of-type(2)")
b.wait_in_text(".content-filter h3", "production")
#add member
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_user_to_group")
b.click("#add_user_to_group button")
b.wait_visible("#add_user_to_group .dropdown-menu")
b.click(".dropdown-menu a[value='scruffy']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#delete member
b.click("tbody[data-id='scruffy'] tr td:last-child a i.pficon-close")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_user_to_group")
b.click("#add_user_to_group button")
b.wait_visible("#add_user_to_group .dropdown-menu")
b.click(".dropdown-menu a[value='scruffy']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#delete user
b.wait_in_text(".content-filter h3", "production")
b.click(".content-filter .pficon-delete")
b.wait_present("modal-dialog")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_not_present("tbody[data-id='production']")
def testProjectUsers(self):
o = self.openshift
b = self.browser
self.login_and_go("/kubernetes/registry")
b.go("#/projects")
o.execute("oc get projects")
o.execute("oc get rolebinding -n marmalade")
b.wait_present("tbody[data-id='default']")
# Create a new project
b.click("#add-project")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.wait_visible("#project-new-name")
b.set_val("#project-new-name", "testprojectuserproj")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#wait for it
b.wait_present("tbody[data-id='testprojectuserproj']")
o.execute("oc get projects")
# Create a new user
b.click("#add-user")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.wait_visible("#identities")
b.set_val("#user_name", "testprojectuser")
b.set_val("#identities", "anypassword:abc123")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#wait for it
b.wait_present("tbody[data-id='testprojectuser']")
#goto user page
b.click("tbody[data-id='testprojectuser'] tr:first-child td:nth-of-type(2)")
b.wait_in_text(".content-filter h3", "testprojectuser")
#modify user
b.click(".content-filter .pficon-edit")
b.wait_present("modal-dialog")
b.wait_visible("#identities")
b.set_val("#identities", "anypassword:abc1234")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_in_text(".user-body dd", "anypassword:abc1234")
#add project member
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_parent_for_user")
b.click("#add_parent_for_user button")
b.wait_visible("#add_parent_for_user .dropdown-menu")
b.click(".dropdown-menu a[value='testprojectuserproj']")
b.wait_visible("#add_role_for_user")
b.click("#add_role_for_user button")
b.wait_visible("#add_role_for_user .dropdown-menu")
b.click("#add_role_for_user a[value='Admin']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#delete project member X
b.click("tbody[data-id='testprojectuserproj'] tr td:last-child a i.pficon-close")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#add project member again
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_parent_for_user")
b.click("#add_parent_for_user button")
b.wait_visible("#add_parent_for_user .dropdown-menu")
b.click(".dropdown-menu a[value='testprojectuserproj']")
b.wait_visible("#add_role_for_user")
b.click("#add_role_for_user button")
b.wait_visible("#add_role_for_user .dropdown-menu")
b.click("#add_role_for_user a[value='Admin']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#add another role to project member
b.wait_present("tbody[data-id='testprojectuserproj']")
b.wait_present("tbody[data-id='testprojectuserproj'] tr .btn-group")
b.wait_visible("tbody[data-id='testprojectuserproj'] tr .btn-group")
b.click("tbody[data-id='testprojectuserproj'] tr .btn-group button")
b.wait_visible("tbody[data-id='testprojectuserproj'] tr .btn-group .dropdown-menu")
b.click("tbody[data-id='testprojectuserproj'] tr .dropdown-menu a[value='Push']")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_present("table.listing-ct")
wait(lambda: re.search(r"registry-editor\s+/registry-editor\s+testprojectuser\b",
o.execute("oc get rolebinding -n testprojectuserproj")))
#delete user
b.wait_in_text(".content-filter h3", "testprojectuser")
b.click(".content-filter .pficon-delete")
b.wait_present("modal-dialog")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
# HACK: In order to test issue, log the output to the journal
wait(lambda: not re.search(r"\btestprojectuser\b", o.execute("oc get rolebinding -n testprojectuserproj | logger -s 2>&1")))
#add/remove members for other roles
b.go("#/projects/testprojectuserproj")
for (role, perm) in [("Push", "editor"), ("Pull", "viewer")]:
username = "testprojectuser" + role.lower()
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_member_name")
b.set_val("#add_member_name", username)
b.wait_visible("#add_role")
b.click("#add_role button")
b.wait_visible("#add_role .dropdown-menu")
b.click("#add_role a[value='%s']" % role)
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_present("tbody[data-id='%s']" % username)
wait(lambda: username in o.execute("oc get rolebinding -n testprojectuserproj"), delay=5)
output = o.execute("oc get rolebinding -n testprojectuserproj")
self.assertRegexpMatches(output, "registry-%s\s+/registry-%s\s.*\\b%s\\b" % (perm, perm, username))
self.assertNotRegexpMatches(output, "registry-admin.*%s" % username)
b.wait_present("tbody[data-id='%s']" % username)
b.click("tbody[data-id='%s'] a i.pficon-close" % username)
b.wait_present("modal-dialog")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_present("table.listing-ct")
wait(lambda: username not in o.execute("oc get rolebinding -n testprojectuserproj"), delay=5)
# try to add user with invalid name from testprojectuserproj page
b.go("#/projects/testprojectuserproj")
b.wait_present("a i.pficon-add-circle-o")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_member_name")
b.set_val("#add_member_name", "foo ^ bar")
b.wait_visible("#add_role")
b.click("#add_role button")
b.wait_visible("#add_role .dropdown-menu")
b.click("#add_role a[value='Admin']")
b.click(".btn-primary")
b.wait_in_text(".dialog-error", "The member name contains invalid characters.")
# but email-style user name should be accepted
b.set_val("#add_member_name", "foo@bar.com")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_present("tbody[data-id='foo@bar.com']")
wait(lambda: 'foo@bar.com' in o.execute("oc get rolebinding -n testprojectuserproj"), delay=5)
self.assertNotIn('foo ^ bar', o.execute("oc get rolebinding -n testprojectuserproj"))
# it appears on the "All projects" page too
b.go("#/projects/")
b.wait_present("tbody[data-id='foo@bar.com']")
# try to add user with invalid name from "All projects" page
b.click("#add-user")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.wait_visible("#identities")
b.set_val("#user_name", "bar ^ baz")
b.set_val("#identities", "anypassword:abc123")
b.click(".btn-primary")
b.wait_in_text(".dialog-error", "The member name contains invalid characters.")
# email-style user name should be accepted
b.set_val("#user_name", "bar@baz.com")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_present("tbody[data-id='bar@baz.com']")
def testProjectPolicy(self):
o = self.openshift
b = self.browser
self.login_and_go("/kubernetes/registry")
b.wait_present(".dashboard-images")
b.go("#/projects")
#wait for it
b.wait_present("tbody[data-id='default']")
# Create a new project
b.click("#add-project")
b.wait_present("modal-dialog")
b.wait_visible(".modal-body")
b.wait_visible("#project-new-name")
b.set_val("#project-new-name", "testprojectpolicyproj")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
#wait for it
b.wait_present("tbody[data-id='testprojectpolicyproj']")
o.execute("oc get projects")
#goto project page
b.click("tbody[data-id='testprojectpolicyproj'] tr:first-child td:nth-of-type(2)")
b.wait_in_text(".content-filter h3", "testprojectpolicyproj")
#add user with role
b.wait_present("a i.pficon-add-circle-o")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_member_group")
b.click("#add_member_group button")
b.wait_visible("#add_member_group .dropdown-menu")
b.click("#add_member_group a[value='scruffy']")
b.click("#add_role button")
b.wait_visible("#add_role .dropdown-menu")
b.click("#add_role a[value='Admin']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_present(".inner-project-listing")
b.wait_present("a i.pficon-add-circle-o")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_member_group")
b.click(".btn-primary")
b.wait_present("modal-dialog")
self.assertEqual(b.text(".dialog-error") ,"Please select a valid Member.")
b.click(".btn-cancel")
b.wait_not_present("modal-dialog")
b.wait_present(".inner-project-listing")
b.wait_present("a i.pficon-add-circle-o")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_member_group")
b.click("#add_member_group button")
b.wait_visible("#add_member_group .dropdown-menu")
b.click("#add_member_group a[value='scruffy']")
b.click(".btn-primary")
b.wait_present("modal-dialog")
self.assertEqual(b.text(".dialog-error") ,"Please select a valid Role.")
b.click(".btn-cancel")
b.wait_not_present("modal-dialog")
# Add a non-existent user
b.wait_present(".inner-project-listing")
b.wait_present("a i.pficon-add-circle-o")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_member_group")
b.set_val("#add_member_name", "randomuser")
b.click("#add_role button")
b.wait_visible("#add_role .dropdown-menu")
b.click("#add_role a[value='Admin']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
# Add a non-existent user, negative case
b.wait_present(".inner-project-listing")
b.wait_present("a i.pficon-add-circle-o")
b.click("a i.pficon-add-circle-o")
b.wait_present("modal-dialog")
b.wait_visible("#add_member_group")
b.set_val("#add_member_name", "")
b.click("#add_role button")
b.wait_visible("#add_role .dropdown-menu")
b.click("#add_role a[value='Admin']")
b.click(".btn-primary")
b.wait_present("modal-dialog")
self.assertEqual(b.text(".dialog-error") ,"Please select a valid Member.")
b.click(".btn-cancel")
b.wait_not_present("modal-dialog")
def testProjectAdmin(self):
o = self.openshift
b = self.browser
m = self.machine
# Log in as scruffy
tmpfile = os.path.join(self.tmpdir, "scruffy")
o.execute('printf "scruffy\r\nscruffy\r\n" | oc login')
o.download("/root/.kube/config", tmpfile)
with open(tmpfile, "r") as f:
m.execute("mkdir -p /home/admin/.kube && cat > /home/admin/.kube/config", input=f.read())
self.login_and_go("/kubernetes/registry")
# Make sure the default view is not visible to non cluster admins
b.wait_present(".dashboard-images")
b.wait_visible(".dashboard-images:nth-child(1)")
b.wait_not_in_text(".card-pf-wide.dashboard-images", "default/busybox")
# Show that the project displays shared access data
b.wait_present("tr[data-name='marmalade']")
b.wait_present("tr[data-name='marmalade'] .fa-lock")
# Change the project access
b.go("#/projects/marmalade")
b.wait_in_text(".content-filter h3", "marmalade")
b.wait_in_text(".listing-ct-body", "Project access policy only allows specific members to access images. Grant access to specific members below.")
b.click(".content-filter .pficon-edit")
b.wait_present("modal-dialog")
b.wait_visible("#project-access-policy")
b.wait_in_text("#project-access-policy button", "Private: Allow only specific users or groups to pull images")
b.click("#project-access-policy button")
b.wait_visible("#project-access-policy .dropdown-menu")
b.click("#project-access-policy a[value='shared']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_not_in_text(".listing-ct-body", "Project access policy allows all authenticated users to pull images. Grant additional access to specific members below.")
output = o.execute("oc policy who-can get --namespace=marmalade imagestreams/layers")
self.assertIn("system:authenticated", output)
self.assertNotIn("system:unauthenticated", output)
# Look for change in state
b.go("#/")
b.wait_present("tr[data-name='marmalade'] .fa-unlock-alt")
# Change project to shared
b.go("#/projects/marmalade")
b.wait_in_text(".content-filter h3", "marmalade")
b.click(".content-filter .pficon-edit")
b.wait_present("modal-dialog")
b.wait_visible("#project-access-policy")
b.wait_in_text("#project-access-policy button", "Shared: Allow any authenticated user to pull images")
b.click("#project-access-policy button")
b.wait_visible("#project-access-policy .dropdown-menu")
b.click("#project-access-policy a[value='anonymous']")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_in_text(".listing-ct-body", "Project access policy allows anonymous users to pull images. Grant additional push or admin access to specific members below.")
output = o.execute("oc policy who-can get --namespace=marmalade imagestreams/layers")
self.assertIn("system:unauthenticated", output)
# Look for change in state
b.go("#/")
b.wait_present("tr[data-name='marmalade'] .fa-unlock")
# New project doesn't exist
b.go("#/")
b.wait_present(".dashboard-images")
output = o.execute("oc get projects")
self.assertNotIn("llama", output)
b.wait_not_in_text(".dashboard-images:first-child", "llama")
# Create a new project
b.wait_visible("a.new-project-link")
b.click("a.new-project-link")
b.wait_present("modal-dialog")
b.wait_visible("#project-new-name")
b.set_val("#project-new-name", "invalid...!")
b.click(".btn-primary")
b.wait_visible(".dialog-error")
b.set_val("#project-new-name", "llama")
b.set_val("#project-new-display", "Display llama")
b.set_val("#project-new-description", "Description goes here")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
# Check that the projcet exists
b.wait_in_text(".dashboard-images:first-child", "llama")
# Go and modify the project
b.go("#/projects")
b.wait_present("tbody[data-id='llama']")
b.wait_present("tbody[data-id='llama'] tr.listing-ct-item")
b.click("tbody[data-id='llama'] tr.listing-ct-item td:nth-of-type(2)")
b.wait_in_text(".content-filter h3", "Display llama (llama)")
b.wait_in_text("#content", "Description goes here")
b.click(".pficon-edit")
b.wait_present("modal-dialog")
b.wait_visible("#project-new-display")
b.set_val("#project-new-display", "What the llama say")
b.wait_visible("#project-new-description")
b.set_val("#project-new-description", "Blearrrrrrrrgh")
b.click(".btn-primary")
b.wait_not_present("modal-dialog")
b.wait_in_text(".content-filter h3", "What the llama say (llama)")
b.wait_in_text("#content", "Blearrrrrrrrgh")
# Make sure it showed up in the console
wait_project(o, "llama");
def testDockerCommandInfo(self):
o = self.openshift
b = self.browser
m = self.machine
# create push and pull user and login as pushuser
o.execute("oadm policy add-role-to-user registry-viewer pulluser -n marmalade")
o.execute("oadm policy add-role-to-user registry-editor pushuser -n marmalade")
o.execute("oadm policy add-role-to-user registry-viewer pushuser -n pizzazz")
tmpfile = os.path.join(self.tmpdir, "kubeconfig")
o.execute('printf "pushuser\r\na\r\n" | oc login')
o.download("/root/.kube/config", tmpfile)
with open(tmpfile, "r") as f:
m.execute("mkdir -p /home/admin/.kube && cat > /home/admin/.kube/config", input=f.read())
self.login_and_go("/kubernetes/registry")
# always visible on "All projects" page
b.wait_in_text("body", "Pull an image")
b.wait_visible('#docker-push-commands')
b.wait_visible('#docker-pull-commands')
# push user should not see docker push command on pizzazz overview page (only a viewer there)
b.wait_visible(".dashboard-images .namespace-filter")
b.click(".dashboard-images .namespace-filter button")
b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu")
b.wait_present(".dashboard-images .namespace-filter a[value='pizzazz']")
b.click(".dashboard-images .namespace-filter a[value='pizzazz']")
b.wait_visible('#docker-pull-commands')
b.wait_not_visible('#docker-push-commands')
# push user should see docker push and pull commands on marmalade overview page
b.click(".dashboard-images .namespace-filter button")
b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu")
b.wait_present(".dashboard-images .namespace-filter a[value='marmalade']")
b.click(".dashboard-images .namespace-filter a[value='marmalade']")
b.wait_visible('#docker-push-commands')
# .. and also on the image page
b.go("#/images/marmalade/origin")
b.wait_in_text("body", "push to an image to this image stream")
b.wait_in_text("body", "docker tag")
b.wait_in_text("body", "docker push")
b.wait_visible('.registry-imagestream-push')
# log in as pulluser
b.logout()
o.execute('printf "pulluser\r\na\r\n" | oc login')
o.download("/root/.kube/config", tmpfile)
with open(tmpfile, "r") as f:
m.execute("cat > /home/admin/.kube/config", input=f.read())
self.login_and_go("/kubernetes/registry")
# always visible on "All projects" page
b.wait_in_text("body", "Pull an image")
b.wait_visible('#docker-push-commands')
b.wait_visible('#docker-pull-commands')
# pull user should only see docker pull command, but not push on project specific overview page
b.wait_visible(".dashboard-images .namespace-filter")
b.click(".dashboard-images .namespace-filter button")
b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu")
b.wait_present(".dashboard-images .namespace-filter a[value='marmalade']")
b.click(".dashboard-images .namespace-filter a[value='marmalade']")
b.wait_visible('#docker-pull-commands')
b.wait_not_visible('#docker-push-commands')
# and neither the push command on the image page
b.go("#/images/marmalade/origin")
b.wait_in_text("body", "Images")
b.wait_not_visible('.registry-imagestream-push')
def testImagestreamImport(self):
b = self.browser
self.setupDockerRegistry()
# Add new "alltags" image stream pulling from localhost:5555/juggs
self.login_and_go("/kubernetes/registry#/images/marmalade")
b.wait_present("a.pull-right span:contains('New image stream')")
b.click("a.pull-right")
b.wait_present("modal-dialog")
b.wait_val("#imagestream-modify-project-text", "marmalade")
b.set_val("#imagestream-modify-name", "alltags")
b.wait_present("#imagestream-modify-populate")
b.click("#imagestream-modify-populate button")
b.wait_visible("#imagestream-modify-populate .dropdown-menu")
b.click("#imagestream-modify-populate .dropdown-menu a[value='pull']")
b.wait_present("#imagestream-modify-pull")
b.wait_visible("#imagestream-modify-pull")
b.set_val("#imagestream-modify-pull", "localhost:5555/juggs")
b.click("modal-dialog div.modal-footer button.btn-primary")
b.wait_not_present("modal-dialog")
# new stream with both "latest" and "2.11" tags should now appear
b.wait_present("tr.imagestream-item th:contains('marmalade/alltags')")
b.wait_present('tbody[data-id="marmalade/alltags:latest"]')
b.wait_present('tbody[data-id="marmalade/alltags:2.11"]')
# also check with CLI
output = self.openshift.execute("oc get imagestream --namespace=marmalade alltags")
self.assertIn("localhost:5555/juggs", output)
self.assertIn("latest", output)
self.assertIn("2.11", output)
# Add new "sometags" image stream pulling only the 2.11 tag
b.click("a.pull-right")
b.wait_present("modal-dialog")
b.wait_val("#imagestream-modify-project-text", "marmalade")
b.set_val("#imagestream-modify-name", "sometags")
b.wait_present("#imagestream-modify-populate")
b.click("#imagestream-modify-populate button")
b.wait_visible("#imagestream-modify-populate .dropdown-menu")
b.click("#imagestream-modify-populate .dropdown-menu a[value='tags']")
b.wait_present("#imagestream-modify-tags")
b.wait_visible("#imagestream-modify-tags")
b.set_val("#imagestream-modify-pull", "localhost:5555/juggs")
# fields.tags is not an element, type manually
b.click("#imagestream-modify-tags")
b.key_press(['2', '.', '1', '1'])
b.click("modal-dialog div.modal-footer button.btn-primary")
b.wait_not_present("modal-dialog")
# new stream with only "2.11" tags should now appear
b.wait_present("tr.imagestream-item th:contains('marmalade/sometags')")
b.go("/kubernetes/registry#/images/marmalade/sometags")
# EXFAIL: https://bugzilla.redhat.com/show_bug.cgi?id=1373332
# b.wait_present('tbody[data-id="marmalade/sometags:2.11"]')
self.assertFalse(b.is_present('tbody[data-id="marmalade/sometags:latest"]'))
# also check with CLI
output = self.openshift.execute("oc get imagestream --namespace=marmalade sometags")
self.assertIn("localhost:5555/juggs", output)
self.assertIn("2.11", output)
# EXFAIL: https://bugzilla.redhat.com/show_bug.cgi?id=1373332
# self.assertNotIn("latest", output)
if __name__ == '__main__':
test_main()