bodhi/config/app.cfg | 6 ++
bodhi/model.py | 42 +++++++++++++++++--
bodhi/templates/show.kid | 6 +-
bodhi/tests/test_controllers.py | 85 ++++++++++++++++++++++++++++++++++++----
4 files changed, 124 insertions(+), 15 deletions(-)
New commits:
commit 4a9336f4b911d52843b6e6ee3f86b4fc31c39a75
Author: Luke Macken <lmacken(a)redhat.com>
Date: Wed Jan 20 10:37:51 2010 -0500
Require one admin +1, and a minimum karma of +2 before pushing critical path
packages to pending releases.
diff --git a/bodhi/config/app.cfg b/bodhi/config/app.cfg
index 2c94f87..317d7b5 100644
--- a/bodhi/config/app.cfg
+++ b/bodhi/config/app.cfg
@@ -74,7 +74,7 @@ release_team_address = None
security_team = None
# Superuser groups
-admin_groups = 'releng security_respons cvsadmin'
+admin_groups = 'releng security_respons cvsadmin qa'
# Where do we send update announcements to ?
# These variables should be named per: Release.prefix_id.lower()_announce_list
@@ -155,6 +155,10 @@ tgcaptcha.key = 'Y`h`f&s}TZz'
##
http://kojipkgs.fedoraproject.org/mash/rawhide-20100107/logs/critpath.txt
critpath = "ConsoleKit ConsoleKit-libs ConsoleKit-x11 DeviceKit-power GConf2
GConf2-gtk MAKEDEV ModemManager NetworkManager NetworkManager-glib ORBit2 acl alsa-lib
anaconda anaconda-yum-plugins at-spi atk attr audit audit-libs authconfig authconfig-gtk
avahi avahi-autoipd avahi-glib basesystem bash binutils bluecurve-cursor-theme bzip2
bzip2-libs c-ares ca-certificates cairo checkpolicy chkconfig cloog-ppl compat-db47
comps-extras constantine-backgrounds constantine-backgrounds-single control-center
control-center-filesystem coreutils coreutils-libs cpio cpp cracklib cracklib-dicts
cracklib-python createrepo cronie cronie-anacron crontabs cryptsetup-luks
cryptsetup-luks-libs cups-libs curl cyrus-sasl cyrus-sasl-lib dash db4 db4-utils dbus
dbus-glib dbus-libs dbus-python dbus-x11 deltarpm desktop-backgrounds-basic
desktop-file-utils device-mapper device-mapper-event device-mapper-event-libs
device-mapper-libs dhclient diffutils dmidecode dmraid dmraid-events dnsmasq dosfstools
dracut e2fsprogs e2fsprogs-libs efibootmgr eggdbus elfutils elfutils-libelf elfutils-libs
ethtool evolution-data-server expat fedora-gnome-theme fedora-icon-theme fedora-logos
fedora-release fedora-setup-keyboard file file-libs filesystem findutils fipscheck
fipscheck-lib firstboot flac fontconfig freetype gamin gawk gcc gcc-c++ gdbm gdm
genisoimage glib2 glibc glibc-common glibc-devel glibc-headers gmp gnome-desktop
gnome-icon-theme gnome-keyring gnome-keyring-pam gnome-menus gnome-panel-libs
gnome-python2 gnome-python2-canvas gnome-python2-gnome gnome-python2-gnomevfs
gnome-session gnome-settings-daemon gnome-themes gnome-vfs2 gnupg2 gnutls gpgme grep grub
grubby gstreamer gstreamer-tools gtk2 gtk2-engines gzip hal hal-info hal-libs hdparm
hesiod hicolor-icon-theme hostname hwdata info initscripts iptables iptables-ipv6 iputils
iscsi-initiator-utils iso-codes isomd5sum jasper-libs kbd kernel kernel-headers
keyutils-libs koji kpartx krb5-libs less libICE libIDL libSM libX11 libX11-common
libXScrnSaver libXau libXcomposite libXcursor libXdamage libXdmcp libXext libXfixes
libXfont libXft libXi libXinerama libXmu libXrandr libXrender libXres libXt libXtst libXv
libXvMC libXxf86misc libXxf86vm libacl libart_lgpl libasyncns libattr libblkid libbonobo
libbonoboui libcanberra libcanberra-gtk2 libcap libcap-ng libcom_err libcroco libcurl
libdaemon libdrm libedit libffi libfontenc libgail-gnome libgcc libgcrypt libglade2
libgnome libgnomecanvas libgnomekbd libgnomeui libgomp libgpg-error libgsf libgudev1
libgweather libical libidn libjpeg libmcpp libnl libnotify libogg libpcap libpciaccess
libpng libproxy libproxy-bin libproxy-python librsvg2 libselinux libselinux-python
libselinux-utils libsemanage libsepol libsndfile libsoup libss libssh2 libstdc++
libstdc++-devel libtasn1 libtdb libthai libtiff libtool-ltdl libudev libusb libuser
libuser-python libutempter libuuid libvorbis libwnck libx86 libxcb libxkbfile libxklavier
libxml2 libxml2-python libxslt linux-firmware livecd-tools logrotate lua lvm2 lvm2-libs
lzma lzma-libs m4 makebootfat mash mcpp mdadm metacity mingetty
mobile-broadband-provider-info module-init-tools mpfr mtools mysql-libs nash ncurses
ncurses-base ncurses-libs net-tools newt newt-python notification-daemon
notification-daemon-engine-slider nspr nss nss-softokn nss-softokn-freebl nss-sysinit
nss-util ntp ntpdate openldap openssh openssh-clients openssh-server openssl pam pango
parted passwd patch pciutils-libs pcre perl perl-Crypt-PasswdMD5 perl-Digest-SHA1
perl-Module-Pluggable perl-Pod-Escapes perl-Pod-Simple perl-libs perl-version pinentry
pixman pkgconfig plymouth plymouth-gdm-hooks plymouth-libs plymouth-scripts plymouth-utils
pm-utils policycoreutils polkit polkit-desktop-policy polkit-gnome popt postfix ppl ppp
procmail procps psmisc pth pulseaudio-gdm-hooks pulseaudio-libs pulseaudio-libs-glib2
pungi pyOpenSSL pycairo pygobject2 pygpgme pygtk2 pygtk2-libglade pykickstart pyparted
python python-bugzilla python-cryptsetup python-decorator python-deltarpm python-ethtool
python-imgcreate python-iniparse python-kid python-krbV python-libs python-meh python-nss
python-pyblock python-pycurl python-slip python-urlgrabber pyxf86config radeontool rarian
rarian-compat readline redhat-menus redhat-rpm-config repoview rootfiles rpm rpm-build
rpm-libs rpm-python rsyslog sed selinux-policy selinux-policy-targeted sendmail setserial
setup setuptool sgml-common sgpio shadow-utils shared-mime-info slang
sound-theme-freedesktop sqlite squashfs-tools startup-notification sudo sysklogd syslinux
system-config-date system-config-firewall-base system-config-keyboard system-config-users
sysvinit-tools tar tcp_wrappers-libs tzdata udev unique unzip upstart usermode
usermode-gtk ustr util-linux-ng vbetool vim-minimal which wpa_supplicant xcb-util
xdg-utils xkeyboard-config xml-common xorg-x11-drivers xorg-x11-drv-acecad
xorg-x11-drv-aiptek xorg-x11-drv-apm xorg-x11-drv-ast xorg-x11-drv-ati xorg-x11-drv-cirrus
xorg-x11-drv-dummy xorg-x11-drv-elographics xorg-x11-drv-evdev xorg-x11-drv-fbdev
xorg-x11-drv-fpit xorg-x11-drv-geode xorg-x11-drv-glint xorg-x11-drv-hyperpen
xorg-x11-drv-i128 xorg-x11-drv-i740 xorg-x11-drv-intel xorg-x11-drv-keyboard
xorg-x11-drv-mach64 xorg-x11-drv-mga xorg-x11-drv-mouse xorg-x11-drv-mutouch
xorg-x11-drv-neomagic xorg-x11-drv-nouveau xorg-x11-drv-nv xorg-x11-drv-openchrome
xorg-x11-drv-penmount xorg-x11-drv-r128 xorg-x11-drv-rendition xorg-x11-drv-s3virge
xorg-x11-drv-savage xorg-x11-drv-siliconmotion xorg-x11-drv-sis xorg-x11-drv-sisusb
xorg-x11-drv-synaptics xorg-x11-drv-tdfx xorg-x11-drv-trident xorg-x11-drv-v4l
xorg-x11-drv-vesa xorg-x11-drv-vmmouse xorg-x11-drv-vmware xorg-x11-drv-void
xorg-x11-drv-voodoo xorg-x11-drv-wacom xorg-x11-server-Xorg xorg-x11-server-common
xorg-x11-server-utils xorg-x11-xauth xorg-x11-xinit xorg-x11-xkb-utils xz xz-libs yum
yum-metadata-parser yum-utils zenity zlib"
+# The number of admin approvals it takes to be able to push a critical path
+# update to stable for a pending release.
+critpath.num_admin_approvals = 1
+critpath.min_karma = 2
# The commented out values below are the defaults
diff --git a/bodhi/model.py b/bodhi/model.py
index 3428379..e25ab34 100644
--- a/bodhi/model.py
+++ b/bodhi/model.py
@@ -410,10 +410,14 @@ class PackageUpdate(SQLObject):
# [No Frozen Rawhide] Disable pushing critical path updates for
# pending releases directly to stable.
if action == 'stable' and self.release.locked and self.critpath:
- if ('releng' in identity.current.groups or
- 'qa' in identity.current.groups):
- self.comment('Critical path update approved by %s' %
- identity.current.user_name, author='bodhi')
+ admin_groups = config.get('admin_groups', 'qa
releng').split()
+ for group in identity.current.groups:
+ if group in admin_groups:
+ if not self.critpath_approved:
+ action = 'testing'
+ self.comment('Critical path update approved',
+ author=identity.current.user_name)
+ break
else:
log.info('Forcing critical path update into testing')
action = 'testing'
@@ -683,6 +687,14 @@ class PackageUpdate(SQLObject):
as stable. If it reaches the 'unstable_karma', it is unpushed.
"""
if not author: author = identity.current.user_name
+
+ # Hack: Add admin groups to usernames (eg: "lmacken (releng)")
+ admin_groups = config.get('admin_groups', 'releng qa
security_respons').split()
+ for group in identity.current.groups:
+ if group in admin_groups:
+ author += ' (%s)' % group
+ break
+
if not anonymous and karma != 0 and \
not filter(lambda c: c.author == author and c.karma == karma,
self.comments):
@@ -706,6 +718,7 @@ class PackageUpdate(SQLObject):
log.info("Automatically unpushing %s" % self.title)
self.obsolete()
mail.send(self.submitter, 'unstable', self)
+
Comment(text=text, karma=karma, update=self, author=author,
anonymous=anonymous)
@@ -838,6 +851,27 @@ class PackageUpdate(SQLObject):
break
return critical
+ @property
+ def num_admin_approvals(self):
+ """ Return the number of Releng/QA approvals of this update
"""
+ approvals = 0
+ for comment in self.comments:
+ # FIXME:
+ # We need to actually store the groups or approvals sanely.
+ # Hack, to get this working for F13 w/o changing the DB
+ if comment.author.endswith(')'):
+ group = comment.author[:-1].split('(')[-1]
+ if group in ('qa', 'releng'):
+ approvals += 1
+ return approvals
+
+ @property
+ def critpath_approved(self):
+ """ Return whether or not this critpath update has been approved
"""
+ return self.num_admin_approvals >= config.get(
+ 'critpath.num_admin_approvals', 2) and \
+ self.karma >= config.get('critpath.min_karma', 2)
+
class Comment(SQLObject):
timestamp = DateTimeCol(default=datetime.utcnow)
diff --git a/bodhi/templates/show.kid b/bodhi/templates/show.kid
index 2fa903b..f7c63cd 100644
--- a/bodhi/templates/show.kid
+++ b/bodhi/templates/show.kid
@@ -71,7 +71,7 @@ karma = "<img src=\"%s\" align=\"top\" />
<b>%d</b>" % (tg.url('/static/images/k
Push to Testing
</a>
</td>
- <span py:if="update.release.locked and
update.critpath">
+ <span py:if="update.release.locked and
update.critpath and update.critpath_approved">
<span py:if="'qa' in
tg.identity.groups or 'releng' in tg.identity.groups">
<td>
<a
href="${util.url('/request/stable/%s' % update.title)}"
class="list">
@@ -81,7 +81,7 @@ karma = "<img src=\"%s\" align=\"top\" />
<b>%d</b>" % (tg.url('/static/images/k
</td>
</span>
</span>
- <span py:if="update.release.locked and not
update.critpath">
+ <span py:if="update.release.locked and not
update.critpath and update.critpath_approved">
<td>
<a
href="${util.url('/request/stable/%s' % update.title)}"
class="list">
<img
src="${tg.url('/static/images/submit.png')}" border="0"/>
@@ -120,7 +120,7 @@ karma = "<img src=\"%s\" align=\"top\"
/> <b>%d</b>" % (tg.url('/static/images/k
</td>
<span py:if="update.status ==
'testing'">
<span py:if="update.request == None">
- <span py:if="update.release.locked and
update.critpath">
+ <span py:if="update.release.locked and
update.critpath and update.critpath_approved">
<span py:if="'qa' in
tg.identity.groups or 'releng' in tg.identity.groups">
<td>
<a
href="${util.url('/request/stable/%s' % update.title)}"
class="list">
diff --git a/bodhi/tests/test_controllers.py b/bodhi/tests/test_controllers.py
index 4ba206e..de6755e 100644
--- a/bodhi/tests/test_controllers.py
+++ b/bodhi/tests/test_controllers.py
@@ -1201,7 +1201,6 @@ class TestControllers(testutil.DBTest):
assert 'TurboGears-1.0.2.2-2.fc7' in json
assert json['TurboGears-1.0.2.2-2.fc7']['notes'] ==
'foobar'
-
def test_updating_build_during_edit(self):
session = login()
create_release()
@@ -1366,7 +1365,7 @@ class TestControllers(testutil.DBTest):
}
self.save_update(params, session)
update = PackageUpdate.byTitle(params['builds'])
- assert update.request == 'stable'
+ assert update.request == 'testing'
def test_critpath_to_frozen_release_available_actions(self):
"""
@@ -1398,11 +1397,12 @@ class TestControllers(testutil.DBTest):
update = PackageUpdate.byTitle(params['builds'])
assert update.request == 'testing'
- def test_critpath_to_frozen_release_available_actions_for_releng(self):
+ def test_critpath_to_pending_release_num_approved_comments(self):
"""
Ensure releng/qa can push critpath updates to stable for pending releases
+ after 1 releng/qa karma, and 1 other karma
"""
- session = login(group='releng')
+ releng = login(group='releng')
create_release(locked=True)
params = {
'builds' : 'kernel-2.6.31-1.fc7',
@@ -1414,14 +1414,62 @@ class TestControllers(testutil.DBTest):
'request': None,
'unstable_karma' : -1,
}
- self.save_update(params, session)
+ self.save_update(params, releng)
update = PackageUpdate.byTitle(params['builds'])
testutil.create_request('/updates/%s' % params['builds'],
- method='GET', headers=session)
+ method='GET', headers=releng)
+ # Ensure releng/QA can't push critpath updates alone
assert "Push to Testing" in cherrypy.response.body[0]
+ assert "Push Critical Path update to Stable" not in
cherrypy.response.body[0]
+
+ # Have a developer +1 the update
+ developer = login(username='bob')
+
testutil.create_request('/updates/comment?text=foobar&title=%s&karma=1' %
+ params['builds'], method='POST',
headers=developer)
+ testutil.create_request('/updates/%s' % params['builds'],
+ method='GET', headers=developer)
+ assert "Push Critical Path update to Stable" not in
cherrypy.response.body[0]
+ update = PackageUpdate.byTitle(params['builds'])
+ assert not update.request
+ assert len(update.comments) == 1
+ assert update.comments[0].author == 'bob'
+
+ # Have another developer +1 it, so it gets up to +2
+ # Ensure we can't push it to stable, until we get admin approval
+
testutil.create_request('/updates/comment?text=foobar&title=%s&karma=1' %
+ params['builds'], method='POST',
+ headers=login(username='foobar'))
+ testutil.create_request('/updates/%s' % params['builds'],
+ method='GET',
headers=login(username='foobar'))
+ assert "Push Critical Path update to Stable" not in
cherrypy.response.body[0]
+ update = PackageUpdate.byTitle(params['builds'])
+ assert not update.request
+ assert update.karma == 2
+
+ # Have releng try again, and ensure it can be pushed to stable
+
testutil.create_request('/updates/comment?text=foobar&title=%s&karma=1' %
+ params['builds'], method='POST',
headers=releng)
+ update = PackageUpdate.byTitle(params['builds'])
+ assert not update.request
+
+ testutil.create_request('/updates/%s' % params['builds'],
+ method='GET', headers=developer)
+ assert "Push Critical Path update to Stable" not in
cherrypy.response.body[0]
+
+ testutil.create_request('/updates/%s' % params['builds'],
+ method='GET', headers=releng)
+ update = PackageUpdate.byTitle(params['builds'])
+ print update.comments
assert "Push Critical Path update to Stable" in
cherrypy.response.body[0]
+ testutil.create_request('/updates/request/stable/%s' %
+ params['builds'], method='POST',
+ headers=releng)
+
+ update = PackageUpdate.byTitle(params['builds'])
+ assert update.request == 'stable'
+
def test_critpath_to_frozen_release_testing(self):
"""
Ensure devs can *not* push critpath updates directly to stable
@@ -1506,7 +1554,30 @@ class TestControllers(testutil.DBTest):
testutil.create_request('/updates/%s' % params['builds'],
method='GET', headers=session)
- assert "Mark Critical Path update as Stable" in
cherrypy.response.body[0]
+ assert "Mark Critical Path update as Stable" not in
cherrypy.response.body[0]
+
+
testutil.create_request('/updates/comment?text=foobar&title=%s&karma=1' %
+ params['builds'], method='POST',
headers=session)
+
+ testutil.create_request('/updates/%s' % params['builds'],
+ method='GET', headers=session)
+
+ assert "Mark Critical Path update as Stable" not in
cherrypy.response.body[0]
+
+
testutil.create_request('/updates/comment?text=foobar&title=%s&karma=1' %
+ params['builds'], method='POST',
+ headers=login(username='bob'))
+
+ update = PackageUpdate.byTitle(params['builds'])
+ assert len(update.comments) == 2
+ assert update.comments[1].author == 'bob', update.comments
+
+ testutil.create_request('/updates/%s' % params['builds'],
+ method='GET', headers=session)
+ update = PackageUpdate.byTitle(params['builds'])
+ assert update.comments[0].author == 'guest (qa)'
+
+ assert "Mark Critical Path update as Stable" in
cherrypy.response.body[0], cherrypy.response.body[0]
def test_created_since(self):
session = login()