Initial code drop
3
Authors
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Mike McLean <mikem@redhat.com>
|
||||
Dennis Gregorovic <dgregor@redhat.com>
|
||||
Mike Bonnet <mikeb@redhat.com>
|
||||
16
COPYING
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Koji - a system for building and tracking RPMS.
|
||||
Copyright (c) 2007 Red Hat
|
||||
|
||||
Koji 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;
|
||||
version 2.1 of the License.
|
||||
|
||||
This software 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 this software; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
458
LGPL
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
108
Makefile
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
NAME=koji
|
||||
SPECFILE = $(firstword $(wildcard *.spec))
|
||||
SUBDIRS = hub builder koji cli docs util www
|
||||
|
||||
ifdef DIST
|
||||
DIST_DEFINES := --define "dist $(DIST)"
|
||||
endif
|
||||
|
||||
ifndef VERSION
|
||||
VERSION := $(shell rpm $(RPM_DEFINES) $(DIST_DEFINES) -q --qf "%{VERSION}\n" --specfile $(SPECFILE)| head -1)
|
||||
endif
|
||||
# the release of the package
|
||||
ifndef RELEASE
|
||||
RELEASE := $(shell rpm $(RPM_DEFINES) $(DIST_DEFINES) -q --qf "%{RELEASE}\n" --specfile $(SPECFILE)| head -1)
|
||||
endif
|
||||
|
||||
ifndef WORKDIR
|
||||
WORKDIR := $(shell pwd)
|
||||
endif
|
||||
## Override RPM_WITH_DIRS to avoid the usage of these variables.
|
||||
ifndef SRCRPMDIR
|
||||
SRCRPMDIR = $(WORKDIR)
|
||||
endif
|
||||
ifndef BUILDDIR
|
||||
BUILDDIR = $(WORKDIR)
|
||||
endif
|
||||
ifndef RPMDIR
|
||||
RPMDIR = $(WORKDIR)
|
||||
endif
|
||||
## SOURCEDIR is special; it has to match the CVS checkout directory,-
|
||||
## because the CVS checkout directory contains the patch files. So it basically-
|
||||
## can't be overridden without breaking things. But we leave it a variable
|
||||
## for consistency, and in hopes of convincing it to work sometime.
|
||||
ifndef SOURCEDIR
|
||||
SOURCEDIR := $(shell pwd)
|
||||
endif
|
||||
|
||||
|
||||
# RPM with all the overrides in place;
|
||||
ifndef RPM
|
||||
RPM := $(shell if test -f /usr/bin/rpmbuild ; then echo rpmbuild ; else echo rpm ; fi)
|
||||
endif
|
||||
ifndef RPM_WITH_DIRS
|
||||
RPM_WITH_DIRS = $(RPM) --define "_sourcedir $(SOURCEDIR)" \
|
||||
--define "_builddir $(BUILDDIR)" \
|
||||
--define "_srcrpmdir $(SRCRPMDIR)" \
|
||||
--define "_rpmdir $(RPMDIR)"
|
||||
endif
|
||||
|
||||
# CVS-safe version/release -- a package name like 4Suite screws things
|
||||
# up, so we have to remove the leaving digits from the name
|
||||
TAG_NAME := $(shell echo $(NAME) | sed -e s/\\\./_/g -e s/^[0-9]\\\+//g)
|
||||
TAG_VERSION := $(shell echo $(VERSION) | sed s/\\\./_/g)
|
||||
TAG_RELEASE := $(shell echo $(RELEASE) | sed s/\\\./_/g)
|
||||
|
||||
# tag to export, defaulting to current tag in the spec file
|
||||
ifndef TAG
|
||||
TAG=$(TAG_NAME)-$(TAG_VERSION)-$(TAG_RELEASE)
|
||||
endif
|
||||
|
||||
_default:
|
||||
@echo "read the makefile"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~ koji*.bz2 koji*.src.rpm
|
||||
rm -rf koji-$(VERSION)
|
||||
for d in $(SUBDIRS); do make -s -C $$d clean; done
|
||||
|
||||
subdirs:
|
||||
for d in $(SUBDIRS); do make -C $$d; [ $$? = 0 ] || exit 1; done
|
||||
|
||||
tarball: clean
|
||||
@rm -rf .koji-$(VERSION)
|
||||
@mkdir .koji-$(VERSION)
|
||||
@cp -rl $(SUBDIRS) Makefile *.spec .koji-$(VERSION)
|
||||
@mv .koji-$(VERSION) koji-$(VERSION)
|
||||
tar --bzip2 --exclude '*.tar.bz2' --exclude '*.rpm' --exclude '.#*' --exclude '.cvsignore' --exclude CVS \
|
||||
-cpf koji-$(VERSION).tar.bz2 koji-$(VERSION)
|
||||
@rm -rf koji-$(VERSION)
|
||||
|
||||
srpm: tarball
|
||||
$(RPM_WITH_DIRS) $(DIST_DEFINES) -ts koji-$(VERSION).tar.bz2
|
||||
|
||||
rpm: tarball
|
||||
$(RPM_WITH_DIRS) $(DIST_DEFINES) -tb koji-$(VERSION).tar.bz2
|
||||
|
||||
tag:: $(SPECFILE)
|
||||
cvs tag $(TAG_OPTS) -c $(TAG)
|
||||
@echo "Tagged with: $(TAG)"
|
||||
@echo
|
||||
|
||||
# If and only if "make build" fails, use "make force-tag" to
|
||||
# re-tag the version.
|
||||
force-tag: $(SPECFILE)
|
||||
@$(MAKE) tag TAG_OPTS="-F $(TAG_OPTS)"
|
||||
|
||||
DESTDIR ?= /
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)
|
||||
|
||||
for d in $(SUBDIRS); do make DESTDIR=`cd $(DESTDIR); pwd` \
|
||||
-C $$d install; [ $$? = 0 ] || exit 1; done
|
||||
28
builder/Makefile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
BINFILES = kojid
|
||||
PYFILES = $(wildcard *.py)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/usr/sbin
|
||||
install -m 755 $(BINFILES) $(DESTDIR)/usr/sbin
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/mock/koji
|
||||
mkdir -p $(DESTDIR)/etc/rc.d/init.d
|
||||
install -m 755 kojid.init $(DESTDIR)/etc/rc.d/init.d/kojid
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/sysconfig
|
||||
install -m 644 kojid.sysconfig $(DESTDIR)/etc/sysconfig/kojid
|
||||
|
||||
install -m 644 kojid.conf $(DESTDIR)/etc/kojid.conf
|
||||
2429
builder/kojid
Executable file
24
builder/kojid.conf
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[kojid]
|
||||
; The number of seconds to sleep between tasks
|
||||
; sleeptime=15
|
||||
|
||||
; The maximum number of jobs that kojid will handle at a time
|
||||
; maxjobs=10
|
||||
|
||||
; The minimum amount of free space (in MBs) required for each build root
|
||||
; minspace=8192
|
||||
|
||||
; The directory root where work data can be found from the koji hub
|
||||
; topdir=/mnt/koji
|
||||
|
||||
; The directory root for temporary storage
|
||||
; workdir=/tmp/koji
|
||||
|
||||
; The directory root for mock
|
||||
; mockdir=/var/lib/mock
|
||||
|
||||
; The user to run as when doing builds
|
||||
; mockuser=kojibuilder
|
||||
|
||||
; The URL for the xmlrpc server
|
||||
server=http://hub.example.com/kojihub
|
||||
85
builder/kojid.init
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#! /bin/sh
|
||||
#
|
||||
# kojid Start/Stop kojid
|
||||
#
|
||||
# chkconfig: 345 99 99
|
||||
# description: kojid server
|
||||
# processname: kojid
|
||||
|
||||
# This is an interactive program, we need the current locale
|
||||
|
||||
# Source function library.
|
||||
. /etc/init.d/functions
|
||||
|
||||
# Check that we're a priviledged user
|
||||
[ `id -u` = 0 ] || exit 0
|
||||
|
||||
[ -f /etc/sysconfig/kojid ] && . /etc/sysconfig/kojid
|
||||
|
||||
prog="kojid"
|
||||
|
||||
# Check that networking is up.
|
||||
if [ "$NETWORKING" = "no" ]
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -f /usr/sbin/kojid ] || exit 0
|
||||
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n $"Starting $prog: "
|
||||
cd /
|
||||
ARGS=""
|
||||
[ "$FORCE_LOCK" == "Y" ] && ARGS="$ARGS --force-lock"
|
||||
[ "$KOJID_DEBUG" == "Y" ] && ARGS="$ARGS --debug"
|
||||
[ "$KOJID_VERBOSE" == "Y" ] && ARGS="$ARGS --verbose"
|
||||
# XXX Fix for make download-checks in kernel builds
|
||||
# Remove once we're running the buildSRPMFromCVS task
|
||||
# as an unpriviledged user with their own environment
|
||||
export HOME="/root"
|
||||
daemon /usr/sbin/kojid $ARGS
|
||||
RETVAL=$?
|
||||
echo
|
||||
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/kojid
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping $prog: "
|
||||
killproc kojid
|
||||
RETVAL=$?
|
||||
echo
|
||||
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/kojid
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
# See how we were called.
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status $prog
|
||||
;;
|
||||
restart|reload)
|
||||
restart
|
||||
;;
|
||||
condrestart)
|
||||
[ -f /var/lock/subsys/kojid ] && restart || :
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit $?
|
||||
3
builder/kojid.sysconfig
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
FORCE_LOCK=Y
|
||||
KOJID_DEBUG=N
|
||||
KOJID_VERBOSE=Y
|
||||
18
cli/Makefile
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
FILES = koji
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/usr/bin
|
||||
install -m 755 $(FILES) $(DESTDIR)/usr/bin
|
||||
install -m 644 koji.conf $(DESTDIR)/etc/koji.conf
|
||||
13
cli/koji.conf
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[koji]
|
||||
|
||||
;configuration for koji cli tool
|
||||
|
||||
;url of XMLRPC server
|
||||
;server = http://hub.example.com/kojihub
|
||||
|
||||
;url of web interface
|
||||
;weburl = http://www.example.com/koji
|
||||
|
||||
;path to the koji top directory
|
||||
;topdir = /mnt/koji
|
||||
|
||||
321
docs/HOWTO.html
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Koji HOWTO</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Introduction</h1>
|
||||
|
||||
Koji is a system for building and tracking RPMs. It was designed with the following
|
||||
features in mind:
|
||||
|
||||
<p>
|
||||
<b>Security</b>
|
||||
<ul>
|
||||
<li>New buildroot for each build</li>
|
||||
<li>nfs is used (mostly) read-only</li>
|
||||
</ul>
|
||||
|
||||
<b>Leverage other software</b>
|
||||
<ul>
|
||||
<li>Uses Yum and Mock open-source components</li>
|
||||
<li>XML-RPC APIs for easy integration with other tools</li>
|
||||
</ul>
|
||||
|
||||
<b>Flexibility</b>
|
||||
<ul>
|
||||
<li>rich data model</li>
|
||||
<li>active code base</li>
|
||||
</ul>
|
||||
|
||||
<b>Usability</b>
|
||||
<ul>
|
||||
<li>Web interface with Kerberos authentication</li>
|
||||
<li>Thin, portable client</li>
|
||||
<li>Users can create local buildroots</li>
|
||||
</ul>
|
||||
|
||||
<b>Reproducibility</b>
|
||||
<ul>
|
||||
<li>Buildroot contents are tracked in the database</li>
|
||||
<li>Versioned data</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<p> This HOWTO document covers the basic tasks that a developer needs to be
|
||||
able to accomplish with Koji.
|
||||
</p>
|
||||
|
||||
<h1>Getting started</h1>
|
||||
|
||||
<h2>The web interface</h2>
|
||||
<p>The primary interface for viewing Koji data is a web application. Most of the interface
|
||||
is read-only, but if you are logged in (see below) and have sufficient privileges there
|
||||
are some actions that can be performed though the web. For example:
|
||||
<ul>
|
||||
<li>Cancel a build</li>
|
||||
<li>Resubmit a failed task</li>
|
||||
</ul>
|
||||
Those with admin privileges will find additional actions, such as:
|
||||
<ul>
|
||||
<li>Create/Edit/Delete a tag</li>
|
||||
<li>Create/Edit/Delete a target</li>
|
||||
<li>Enable/Disable a build host</li>
|
||||
</ul>
|
||||
|
||||
<p>The web site utilizes Kerberos authentication. In order to log in you will
|
||||
need a valid Kerberos ticket and your web browser will need to be configured to send the
|
||||
Kerberos information to the server.
|
||||
|
||||
<p>In Firefox or Mozilla, you will need to use the about:config page to set a few parameters.
|
||||
Use the search term 'negotiate' to filter the list. Change
|
||||
network.negotiate-auth.trusted-uris to the domain you want to authenticate against,
|
||||
e.g .example.com. You can leave network.negotiate-auth.delegation-uris blank, as it
|
||||
enables Kerberos ticket passing, which is not required. If you do not see those two
|
||||
config options listed, your version of Firefox or Mozilla may be too old to support
|
||||
Negotiate authentication, and you should consider upgrading.
|
||||
|
||||
<p>In order to obtain a Kerberos ticket, use the kinit command.
|
||||
|
||||
|
||||
<h2>Installing the Koji cli</h2>
|
||||
<p>There is a single point of entry for most operations. The command is
|
||||
called 'koji' and is included in the main koji package.
|
||||
|
||||
<p>Repos/webpage TBD
|
||||
|
||||
<p>
|
||||
The koji tool authenticates to the central server using Kerberos, so you will need
|
||||
to have a valid Kerberos ticket to use many features. However, many of the read-only
|
||||
commands will work without authentication.
|
||||
|
||||
<h2>Building a package</h2>
|
||||
<p>Builds are initiated with the command line tool.
|
||||
To build a package, the syntax is:</p>
|
||||
<pre>$ koji build <build target> <cvs URL></pre>
|
||||
|
||||
<p>For example:</p>
|
||||
<pre>$ koji build dist-fc7-scratch 'cvs://cvs.example.com/cvs/dist?rpms/kernel/FC-7#kernel-2_6_20-1_2925_fc7'</pre>
|
||||
<p>
|
||||
The <code>koji build</code> command creates a build task in Koji. By default
|
||||
the tool will wait
|
||||
and print status updates until the build completes. You can override this with
|
||||
the <code>--nowait</code> option. To view other options to the build command use the
|
||||
<code>--help</code> option.
|
||||
</p>
|
||||
|
||||
<pre>$ koji build --help
|
||||
</pre>
|
||||
|
||||
<h2>Build Options</h2>
|
||||
<p>
|
||||
There are a few options to the build command. Here are some more detailed explanations
|
||||
of them:
|
||||
</p>
|
||||
|
||||
<dl>
|
||||
<dt>--skip-tag</dt>
|
||||
<dd>Normally the package is tagged after the build completes. This option causes
|
||||
the tagging step to be skipped. The package will be in the system, but untagged
|
||||
(you can later tag it with the tag-pkg command)</dd>
|
||||
<dt>--scratch</dt>
|
||||
<dd>This makes the build into a scratch build. The build will not be
|
||||
imported into the db, it will just be built. The rpms will land under
|
||||
<topdir>/scratch. Scratch builds are not tracked and can never
|
||||
be tagged, but can be convenient for testing. Scratch builds are
|
||||
typically removed from the filesystem after one week.
|
||||
</dd>
|
||||
<dt>--nowait</dt>
|
||||
<dd>As stated above, this prevents the cli from waiting on the build task.</dd>
|
||||
<dt>--arch-override</dt>
|
||||
<dd>This option allows you to override the base set of arches to build for.
|
||||
This option is really only for testing during the beta period, but it may
|
||||
be retained for scratch builds in the future.</dd>
|
||||
</dl>
|
||||
|
||||
<h2>Build Failures</h2>
|
||||
<p>If your package fails to build, you will see something like this.</p>
|
||||
<pre>
|
||||
420066 buildArch (kernel-2.6.18-1.2739.10.9.el5.jjf.215394.2.src.rpm,
|
||||
ia64): open (build-1.example.com) -> FAILED: BuildrootError:
|
||||
error building package (arch ia64), mock exited with status 10
|
||||
</pre>
|
||||
|
||||
<p>You can figure out why the build failed by looking at the log files. If
|
||||
there is a build.log, start there. Otherwise, look at init.log</p>
|
||||
|
||||
<pre>
|
||||
$ ls -1 <topdir>/work/tasks/420066/*
|
||||
<topdir>/work/tasks/420066/build.log
|
||||
<topdir>/work/tasks/420066/init.log
|
||||
<topdir>/work/tasks/420066/mockconfig.log
|
||||
<topdir>/work/tasks/420066/root.log
|
||||
</pre>
|
||||
|
||||
<h2>Filing Bugs</h2>
|
||||
|
||||
<p>bug tracking TBD
|
||||
|
||||
<h1>Koji Architecture</h1>
|
||||
|
||||
<h2>Terminology</h2>
|
||||
|
||||
In Koji, it is sometimes necessary to distinguish between the a package in general,
|
||||
a specific build of a package, and the various rpm files created by a build. When
|
||||
precision is needed, these terms should be interpreted as follows:
|
||||
|
||||
<dl>
|
||||
<dt>Package</dt>
|
||||
<dd>The name of a source rpm. This refers to the package in general and not
|
||||
any particular build or subpackage. For example: kernel, glibc, etc.</dd>
|
||||
<dt>Build</dt>
|
||||
<dd>A particular build of a package. This refers to the entire build: all arches
|
||||
and subpackages. For example: kernel-2.6.9-34.EL, glibc-2.3.4-2.19.</dd>
|
||||
<dt>RPM</dt>
|
||||
<dd>A particular rpm. A specific arch and subpackage of a build.
|
||||
For example: kernel-2.6.9-34.EL.x86_64, kernel-devel-2.6.9-34.EL.s390,
|
||||
glibc-2.3.4-2.19.i686, glibc-common-2.3.4-2.19.ia64</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<h2>Koji Components</h2>
|
||||
|
||||
Koji is comprised of several components:
|
||||
|
||||
<ul>
|
||||
<li><em>koji-hub</em> is the center of all Koji operations. It is an XML-RPC server
|
||||
running under mod_python in Apache. koji-hub is passive in that it only
|
||||
receives XML-RPC calls and relies upon the build daemons and other
|
||||
components to initiate communication. koji-hub is the only component that
|
||||
has direct access to the database and is one of the two components that have
|
||||
write access to the file system.</li>
|
||||
|
||||
<li><em>kojid</em> is the build daemon that runs on each of the build machines. Its
|
||||
primary responsibility is polling for incoming build requests and handling
|
||||
them accordingly. Koji also has support for tasks other than building.
|
||||
Creating install images is one example. kojid is responsible for handling
|
||||
these tasks as well.
|
||||
|
||||
<p>kojid uses mock for building. It also creates a fresh buildroot for
|
||||
every build. kojid is written in Python and communicates with koji-hub via
|
||||
XML-RPC.</p></li>
|
||||
|
||||
<li><em>koji-web</em> is a set of scripts that run in mod_python and use the Cheetah
|
||||
templating engine to provide an web interface to Koji. koji-web exposes a
|
||||
lot of information and also provides a means for certain operations, such as
|
||||
cancelling builds.</li>
|
||||
|
||||
<li><em>koji</em> is a CLI written in Python that provides many hooks into
|
||||
Koji. It allows the user to query much of the data as well as perform
|
||||
actions such as build initiation.</li>
|
||||
|
||||
<li><em>kojirepod</em> is a daemon that keeps the build root repodata
|
||||
updated.</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>Package Organization</h2>
|
||||
<p><i>Tags and Targets</i></p>
|
||||
<p>Koji organizes packages using tags. In Koji a tag is roughly analogous to
|
||||
a beehive collection instance, but differ in a number of ways:</p>
|
||||
<ul>
|
||||
<li>Tags are tracked in the database but not on disk</li>
|
||||
<li>Tags support multiple inheritance</li>
|
||||
<li>Each tag has its own list of valid packages (inheritable)</li>
|
||||
<li>Package ownership can be set per-tag (inheritable)</li>
|
||||
<li>Tag inheritance is more configurable</li>
|
||||
<li>When you build you specify a <i>target</i> rather than a tag</li>
|
||||
</ul>
|
||||
<p>
|
||||
A build target specifies where a package should be built and how it
|
||||
should be tagged afterwards. This allows target names to remain fixed
|
||||
as tags change through releases. You can get a full list of build targets
|
||||
with the following command:</p>
|
||||
<pre>$ koji list-targets
|
||||
</pre>
|
||||
You can see just a single target with the <code>--name</code> option:
|
||||
<pre>$ koji list-targets --name dist-fc7
|
||||
Name Buildroot Destination
|
||||
---------------------------------------------------------------------------------------------
|
||||
dist-fc7 dist-fc7-build dist-fc7
|
||||
</pre>
|
||||
This tells you a build for target dist-fc7 will use a buildroot with packages
|
||||
from the tag dist-fc7-build and tag the resulting packages as dist-fc7.
|
||||
<p>
|
||||
You can get a list of tags with the following command:</p>
|
||||
<pre>$ koji list-tags
|
||||
</pre>
|
||||
<p><i>Package lists</i></p>
|
||||
<p>
|
||||
As mentioned above, each tag has its own list of packages that may be placed
|
||||
in the tag. To see that list for a tag, use the <code>list-pkgs</code> command:</p>
|
||||
<pre>$ koji list-pkgs --tag dist-fc7
|
||||
Package Tag Extra Arches Owner
|
||||
----------------------- ----------------------- ---------------- ----------------
|
||||
ElectricFence dist-fc6 pmachata
|
||||
GConf2 dist-fc6 rstrode
|
||||
lucene dist-fc6 dbhole
|
||||
lvm2 dist-fc6 lvm-team
|
||||
ImageMagick dist-fc6 nmurray
|
||||
m17n-db dist-fc6 majain
|
||||
m17n-lib dist-fc6 majain
|
||||
MAKEDEV dist-fc6 clumens
|
||||
...
|
||||
</pre>
|
||||
The first column is the name of the package, the second tells you which tag
|
||||
the package entry has been inherited from, and the third tells you the owner
|
||||
of the package.
|
||||
<p><i>Latest Builds</i></p>
|
||||
<p>
|
||||
To see the latest builds for a tag, use the <code>latest-pkg</code> command:</p>
|
||||
<pre>$ koji latest-pkg --all dist-fc7
|
||||
Build Tag Built by
|
||||
---------------------------------------- -------------------- ----------------
|
||||
ConsoleKit-0.1.0-5.fc7 dist-fc7 davidz
|
||||
ElectricFence-2.2.2-20.2.2 dist-fc6 jkeating
|
||||
GConf2-2.16.0-6.fc7 dist-fc7 mclasen
|
||||
ImageMagick-6.2.8.0-3.fc6.1 dist-fc6-updates nmurray
|
||||
MAKEDEV-3.23-1.2 dist-fc6 nalin
|
||||
MySQL-python-1.2.1_p2-2 dist-fc7 katzj
|
||||
NetworkManager-0.6.5-0.3.cvs20061025.fc7 dist-fc7 caillon
|
||||
ORBit2-2.14.6-1.fc7 dist-fc7 mclasen
|
||||
</pre>
|
||||
The output gives you not only the latest builds, but which tag they have
|
||||
been inherited from and who built them (note: for builds imported from beehive
|
||||
the "built by" field may be misleading)
|
||||
|
||||
|
||||
<h2>Exploring Koji</h2>
|
||||
|
||||
<p>We've tried to make Koji self-documenting wherever possible. The command
|
||||
line tool will print a list of valid commands and each command supports
|
||||
<code>--help</code>. For example:</p>
|
||||
|
||||
<pre>
|
||||
$ koji help
|
||||
Koji commands are:
|
||||
build Build a package from source
|
||||
cancel-task Cancel a task
|
||||
help List available commands
|
||||
latest-build Print the latest rpms for a tag
|
||||
latest-pkg Print the latest builds for a tag
|
||||
...
|
||||
$ koji build --help
|
||||
usage: koji build [options] tag URL
|
||||
(Specify the --help global option for a list of other help options)
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--skip-tag Do not attempt to tag package
|
||||
--scratch Perform a scratch build
|
||||
--nowait Don't wait on build
|
||||
...
|
||||
</pre>
|
||||
|
||||
<h1>Getting Involved</h1>
|
||||
|
||||
If you would like to be more involved with the Koji project...
|
||||
|
||||
<p>Project data TBD
|
||||
|
||||
</body>
|
||||
</html>
|
||||
4
docs/Makefile
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
install:
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
607
docs/schema.sql
Normal file
|
|
@ -0,0 +1,607 @@
|
|||
|
||||
-- vim:noet:sw=8
|
||||
-- still needs work
|
||||
DROP TABLE build_notifications;
|
||||
|
||||
DROP TABLE log_messages;
|
||||
|
||||
DROP TABLE buildroot_listing;
|
||||
|
||||
DROP TABLE rpmfiles;
|
||||
DROP TABLE rpmdeps;
|
||||
DROP TABLE rpminfo;
|
||||
|
||||
DROP TABLE group_package_listing;
|
||||
DROP TABLE group_req_listing;
|
||||
DROP TABLE group_config;
|
||||
DROP TABLE groups;
|
||||
|
||||
DROP TABLE tag_listing;
|
||||
DROP TABLE tag_packages;
|
||||
|
||||
DROP TABLE buildroot;
|
||||
DROP TABLE repo;
|
||||
|
||||
DROP TABLE build_target_config;
|
||||
DROP TABLE build_target;
|
||||
|
||||
DROP TABLE tag_config;
|
||||
DROP TABLE tag_inheritance;
|
||||
DROP TABLE tag;
|
||||
|
||||
DROP TABLE build;
|
||||
|
||||
DROP TABLE task;
|
||||
|
||||
DROP TABLE host_channels;
|
||||
DROP TABLE host;
|
||||
|
||||
DROP TABLE channels;
|
||||
DROP TABLE package;
|
||||
|
||||
DROP TABLE user_groups;
|
||||
DROP TABLE user_perms;
|
||||
DROP TABLE permissions;
|
||||
|
||||
DROP TABLE sessions;
|
||||
DROP TABLE users;
|
||||
|
||||
DROP TABLE event_labels;
|
||||
DROP TABLE events;
|
||||
DROP FUNCTION get_event();
|
||||
DROP FUNCTION get_event_time(INTEGER);
|
||||
|
||||
BEGIN WORK;
|
||||
|
||||
-- We use the events table to sequence time
|
||||
-- in the event that the system clock rolls back, event_ids will retain proper sequencing
|
||||
CREATE TABLE events (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
time TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- A function that creates an event and returns the id, used as DEFAULT value for versioned tables
|
||||
CREATE FUNCTION get_event() RETURNS INTEGER AS '
|
||||
INSERT INTO events (time) VALUES (''now'');
|
||||
SELECT currval(''events_id_seq'')::INTEGER;
|
||||
' LANGUAGE SQL;
|
||||
|
||||
-- A convenience function for converting events to timestamps, useful for
|
||||
-- quick queries where you want to avoid JOINs.
|
||||
CREATE FUNCTION get_event_time(INTEGER) RETURNS TIMESTAMP AS '
|
||||
SELECT time FROM events WHERE id=$1;
|
||||
' LANGUAGE SQL;
|
||||
|
||||
-- this table is used to label events
|
||||
-- most events will be unlabeled, so keeping this separate saves space
|
||||
CREATE TABLE event_labels (
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
label VARCHAR(255) UNIQUE NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
|
||||
-- User and session data
|
||||
CREATE TABLE users (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255),
|
||||
status INTEGER,
|
||||
usertype INTEGER,
|
||||
krb_principal VARCHAR(255) UNIQUE
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE TABLE permissions (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(50) UNIQUE NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- Some basic perms
|
||||
INSERT INTO permissions (name) VALUES ('admin');
|
||||
INSERT INTO permissions (name) VALUES ('build');
|
||||
INSERT INTO permissions (name) VALUES ('repo');
|
||||
INSERT INTO permissions (name) VALUES ('runroot');
|
||||
|
||||
CREATE TABLE user_perms (
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
perm_id INTEGER NOT NULL REFERENCES permissions(id),
|
||||
-- versioned - see VERSIONING
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, user_id, perm_id),
|
||||
UNIQUE (user_id,perm_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- groups are represented as users w/ usertype=2
|
||||
CREATE TABLE user_groups (
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
group_id INTEGER NOT NULL REFERENCES users(id),
|
||||
-- versioned - see VERSIONING
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, user_id, group_id),
|
||||
UNIQUE (user_id,group_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- a session can create subsessions, which are just new sessions whose
|
||||
-- 'master' field points back to the session. This field should
|
||||
-- always point to the top session. If the master session is expired,
|
||||
-- the all its subsessions should be expired as well.
|
||||
-- If a session is exclusive, it is the only session allowed for its
|
||||
-- user. The 'exclusive' field is either NULL or TRUE, never FALSE. This
|
||||
-- is so exclusivity can be enforced with a unique condition.
|
||||
CREATE TABLE sessions (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
expired BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
master INTEGER REFERENCES sessions(id),
|
||||
key VARCHAR(255),
|
||||
authtype INTEGER,
|
||||
hostip VARCHAR(255),
|
||||
callnum INTEGER,
|
||||
start_time TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
update_time TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
exclusive BOOLEAN CHECK (exclusive),
|
||||
CONSTRAINT no_exclusive_subsessions CHECK (
|
||||
master IS NULL OR "exclusive" IS NULL),
|
||||
CONSTRAINT exclusive_expired_sane CHECK (
|
||||
expired IS FALSE OR "exclusive" IS NULL),
|
||||
UNIQUE (user_id,exclusive)
|
||||
) WITHOUT OIDS;
|
||||
CREATE INDEX sessions_master ON sessions(master);
|
||||
CREATE INDEX sessions_active_and_recent ON sessions(expired, master, update_time) WHERE (expired IS NOT TRUE AND master IS NULL);
|
||||
|
||||
-- Channels are used to limit which tasks are run on which machines.
|
||||
-- Each task is assigned to a channel and each host 'listens' on one
|
||||
-- or more channels. A host will only accept tasks for channels it is
|
||||
-- listening to.
|
||||
CREATE TABLE channels (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(128) UNIQUE NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- create default channel
|
||||
INSERT INTO channels (name) VALUES ('default');
|
||||
INSERT INTO channels (name) VALUES ('runroot');
|
||||
|
||||
-- Here we track the build machines
|
||||
-- each host has an entry in the users table also
|
||||
-- capacity: the hosts weighted task capacity
|
||||
CREATE TABLE host (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users (id),
|
||||
name VARCHAR(128) UNIQUE NOT NULL,
|
||||
arches TEXT,
|
||||
task_load FLOAT CHECK (NOT task_load < 0) NOT NULL DEFAULT 0.0,
|
||||
capacity FLOAT CHECK (capacity > 1) NOT NULL DEFAULT 2.0,
|
||||
ready BOOLEAN NOT NULL DEFAULT 'false',
|
||||
enabled BOOLEAN NOT NULL DEFAULT 'true'
|
||||
) WITHOUT OIDS;
|
||||
CREATE INDEX HOST_IS_READY_AND_ENABLED ON host(enabled, ready) WHERE (enabled IS TRUE AND ready IS TRUE);
|
||||
|
||||
CREATE TABLE host_channels (
|
||||
host_id INTEGER NOT NULL REFERENCES host(id),
|
||||
channel_id INTEGER NOT NULL REFERENCES channels(id),
|
||||
UNIQUE (host_id,channel_id)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
|
||||
-- tasks are pretty general and may refer to all sorts of jobs, not
|
||||
-- just package builds.
|
||||
-- tasks may spawn subtasks (hence the parent field)
|
||||
-- top-level tasks have NULL parent
|
||||
-- the request and result fields are base64-encoded xmlrpc data.
|
||||
-- this means each task is effectively an xmlrpc call, using this table as
|
||||
-- the medium.
|
||||
-- the host_id field indicates which host is running the task. This field
|
||||
-- is used to lock the task.
|
||||
-- weight: the weight of the task (vs. host capacity)
|
||||
-- label: this field is used to label subtasks. top-level tasks will not
|
||||
-- have a label. some subtasks may be unlabeled. labels are used in task
|
||||
-- failover to prevent duplication of work.
|
||||
CREATE TABLE task (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
state INTEGER,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
completion_time TIMESTAMP,
|
||||
channel_id INTEGER NOT NULL REFERENCES channels(id),
|
||||
host_id INTEGER REFERENCES host (id),
|
||||
parent INTEGER REFERENCES task (id),
|
||||
label VARCHAR(255),
|
||||
waiting BOOLEAN,
|
||||
awaited BOOLEAN,
|
||||
owner INTEGER REFERENCES users(id) NOT NULL,
|
||||
method TEXT,
|
||||
request TEXT,
|
||||
result TEXT,
|
||||
eta INTEGER,
|
||||
arch VARCHAR(16) NOT NULL,
|
||||
priority INTEGER,
|
||||
weight FLOAT CHECK (NOT weight < 0) NOT NULL DEFAULT 1.0,
|
||||
CONSTRAINT parent_label_sane CHECK (
|
||||
parent IS NOT NULL OR label IS NULL),
|
||||
UNIQUE (parent,label)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE INDEX task_by_state ON task (state);
|
||||
-- CREATE INDEX task_by_parent ON task (parent); (unique condition creates similar index)
|
||||
CREATE INDEX task_by_host ON task (host_id);
|
||||
|
||||
|
||||
-- by package, we mean srpm
|
||||
-- we mean the package in general, not an individual build
|
||||
CREATE TABLE package (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- CREATE INDEX package_by_name ON package (name);
|
||||
-- (implicitly created by unique constraint)
|
||||
|
||||
|
||||
-- here we track the built packages
|
||||
-- this is at the srpm level, since builds are by srpm
|
||||
-- see rpminfo for isolated packages
|
||||
-- even though we track epoch, we demand that N-V-R be unique
|
||||
-- task_id: a reference to the task creating the build, may be
|
||||
-- null, or may point to a deleted task.
|
||||
CREATE TABLE build (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
pkg_id INTEGER NOT NULL REFERENCES package (id) DEFERRABLE,
|
||||
version TEXT NOT NULL,
|
||||
release TEXT NOT NULL,
|
||||
epoch INTEGER,
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
completion_time TIMESTAMP,
|
||||
state INTEGER NOT NULL,
|
||||
task_id INTEGER REFERENCES task (id),
|
||||
owner INTEGER NOT NULL REFERENCES users (id),
|
||||
CONSTRAINT build_pkg_ver_rel UNIQUE (pkg_id, version, release),
|
||||
CONSTRAINT completion_sane CHECK ((state = 0 AND completion_time IS NULL) OR
|
||||
(state != 0 AND completion_time IS NOT NULL))
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE INDEX build_by_pkg_id ON build (pkg_id);
|
||||
CREATE INDEX build_completion ON build(completion_time);
|
||||
|
||||
CREATE TABLE changelogs (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
build_id INTEGER NOT NULL REFERENCES build (id),
|
||||
date TIMESTAMP NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
text TEXT
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE INDEX changelogs_by_date on changelogs (date);
|
||||
CREATE INDEX changelogs_by_build on changelogs (build_id);
|
||||
|
||||
-- Note: some of these CREATEs may seem a little out of order. This is done to keep
|
||||
-- the references sane.
|
||||
|
||||
CREATE TABLE tag (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(50) UNIQUE NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- CREATE INDEX tag_by_name ON tag (name);
|
||||
-- (implicitly created by unique constraint)
|
||||
|
||||
|
||||
-- VERSIONING
|
||||
-- Several tables are versioned with the following scheme. Since this
|
||||
-- is the first, here is the explanation of how it works.
|
||||
-- The versioning fields are: create_event, revoke_event, and active
|
||||
-- The active field is either True or NULL, it is never False!
|
||||
-- The create_event and revoke_event fields refer to the event table
|
||||
-- A version is active if active is not NULL
|
||||
-- (an active version also has NULL revoke_event.)
|
||||
-- A UNIQUE condition can incorporate the 'active' field, making it
|
||||
-- apply only to the active versions.
|
||||
-- When a version is made inactive (revoked):
|
||||
-- revoke_event is set
|
||||
-- active is set to NULL
|
||||
-- Query for current data with WHERE active is not NULL
|
||||
-- (should be same as WHERE revoke_event is NULL)
|
||||
-- Query for data at event e with WHERE create_event <= e AND e < revoke_event
|
||||
CREATE TABLE tag_inheritance (
|
||||
tag_id INTEGER NOT NULL REFERENCES tag(id),
|
||||
parent_id INTEGER NOT NULL REFERENCES tag(id),
|
||||
priority INTEGER NOT NULL,
|
||||
maxdepth INTEGER,
|
||||
intransitive BOOLEAN NOT NULL DEFAULT 'false',
|
||||
noconfig BOOLEAN NOT NULL DEFAULT 'false',
|
||||
pkg_filter TEXT,
|
||||
-- versioned - see desc above
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, tag_id, priority),
|
||||
UNIQUE (tag_id,priority,active),
|
||||
UNIQUE (tag_id,parent_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE INDEX tag_inheritance_by_parent ON tag_inheritance (parent_id);
|
||||
|
||||
-- XXX - need more config options listed here
|
||||
-- perm_id: the permission that is required to apply the tag. can be NULL
|
||||
--
|
||||
CREATE TABLE tag_config (
|
||||
tag_id INTEGER NOT NULL REFERENCES tag(id),
|
||||
arches TEXT,
|
||||
perm_id INTEGER REFERENCES permissions(id),
|
||||
locked BOOLEAN NOT NULL DEFAULT 'false',
|
||||
-- versioned - see desc above
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, tag_id),
|
||||
UNIQUE (tag_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
|
||||
-- a build target tells the system where to build the package
|
||||
-- and how to tag it afterwards.
|
||||
CREATE TABLE build_target (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(50) UNIQUE NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
|
||||
CREATE TABLE build_target_config (
|
||||
build_target_id INTEGER NOT NULL REFERENCES build_target(id),
|
||||
build_tag INTEGER NOT NULL REFERENCES tag(id),
|
||||
dest_tag INTEGER NOT NULL REFERENCES tag(id),
|
||||
-- versioned - see desc above
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, build_target_id),
|
||||
UNIQUE (build_target_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
|
||||
-- track repos
|
||||
CREATE TABLE repo (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
tag_id INTEGER NOT NULL REFERENCES tag(id),
|
||||
state INTEGER
|
||||
) WITHOUT OIDS;
|
||||
|
||||
|
||||
-- here we track the buildroots on the machines
|
||||
CREATE TABLE buildroot (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
host_id INTEGER NOT NULL REFERENCES host(id),
|
||||
repo_id INTEGER NOT NULL REFERENCES repo (id),
|
||||
arch VARCHAR(16) NOT NULL,
|
||||
task_id INTEGER REFERENCES task (id),
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
retire_event INTEGER,
|
||||
state INTEGER,
|
||||
dirtyness INTEGER
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- this table associates tags with builds. an entry here tags a package
|
||||
CREATE TABLE tag_listing (
|
||||
build_id INTEGER NOT NULL REFERENCES build (id),
|
||||
tag_id INTEGER NOT NULL REFERENCES tag (id),
|
||||
-- versioned - see earlier description of versioning
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, build_id, tag_id),
|
||||
UNIQUE (build_id,tag_id,active)
|
||||
) WITHOUT OIDS;
|
||||
CREATE INDEX tag_listing_tag_id_key ON tag_listing(tag_id);
|
||||
|
||||
-- this is a per-tag list of packages, with some extra info
|
||||
-- so this allows you to explicitly state which packages belong where
|
||||
-- (as opposed to beehive where this can only be done at the collection level)
|
||||
-- these are packages in general, not specific builds.
|
||||
-- this list limits which builds can be tagged with which tags
|
||||
-- if blocked is true, then the package is specifically not included. this
|
||||
-- prevents the package from being included via inheritance
|
||||
CREATE TABLE tag_packages (
|
||||
package_id INTEGER NOT NULL REFERENCES package (id),
|
||||
tag_id INTEGER NOT NULL REFERENCES tag (id),
|
||||
owner INTEGER NOT NULL REFERENCES users(id),
|
||||
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
extra_arches TEXT,
|
||||
-- versioned - see earlier description of versioning
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, package_id, tag_id),
|
||||
UNIQUE (package_id,tag_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- package groups (per tag). used for generating comps for the tag repos
|
||||
CREATE TABLE groups (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(50) UNIQUE NOT NULL
|
||||
-- corresponds to the id field in a comps group
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- if blocked is true, then the group is specifically not included. this
|
||||
-- prevents the group from being included via inheritance
|
||||
CREATE TABLE group_config (
|
||||
group_id INTEGER NOT NULL REFERENCES groups (id),
|
||||
tag_id INTEGER NOT NULL REFERENCES tag (id),
|
||||
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
exported BOOLEAN DEFAULT TRUE,
|
||||
display_name TEXT NOT NULL,
|
||||
is_default BOOLEAN,
|
||||
uservisible BOOLEAN,
|
||||
description TEXT,
|
||||
langonly TEXT,
|
||||
biarchonly BOOLEAN,
|
||||
-- versioned - see earlier description of versioning
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, group_id, tag_id),
|
||||
UNIQUE (group_id,tag_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE TABLE group_req_listing (
|
||||
group_id INTEGER NOT NULL REFERENCES groups (id),
|
||||
tag_id INTEGER NOT NULL REFERENCES tag (id),
|
||||
req_id INTEGER NOT NULL REFERENCES groups (id),
|
||||
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
type VARCHAR(25),
|
||||
is_metapkg BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
-- versioned - see earlier description of versioning
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, group_id, tag_id, req_id),
|
||||
UNIQUE (group_id,tag_id,req_id,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- if blocked is true, then the package is specifically not included. this
|
||||
-- prevents the package from being included in the group via inheritance
|
||||
-- package refers to an rpm name, not necessarily an srpm name (so it does
|
||||
-- not reference the package table).
|
||||
CREATE TABLE group_package_listing (
|
||||
group_id INTEGER NOT NULL REFERENCES groups (id),
|
||||
tag_id INTEGER NOT NULL REFERENCES tag (id),
|
||||
package TEXT,
|
||||
blocked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
type VARCHAR(25) NOT NULL,
|
||||
basearchonly BOOLEAN,
|
||||
requires TEXT,
|
||||
-- versioned - see earlier description of versioning
|
||||
create_event INTEGER NOT NULL REFERENCES events(id) DEFAULT get_event(),
|
||||
revoke_event INTEGER REFERENCES events(id),
|
||||
active BOOLEAN DEFAULT 'true' CHECK (active),
|
||||
CONSTRAINT active_revoke_sane CHECK (
|
||||
(active IS NULL AND revoke_event IS NOT NULL )
|
||||
OR (active IS NOT NULL AND revoke_event IS NULL )),
|
||||
PRIMARY KEY (create_event, group_id, tag_id, package),
|
||||
UNIQUE (group_id,tag_id,package,active)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- rpminfo tracks individual rpms (incl srpms)
|
||||
-- buildroot_id can be NULL (for externally built packages)
|
||||
-- even though we track epoch, we demand that N-V-R.A be unique
|
||||
-- we don't store filename b/c filename should be N-V-R.A.rpm
|
||||
CREATE TABLE rpminfo (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
build_id INTEGER REFERENCES build (id),
|
||||
buildroot_id INTEGER REFERENCES buildroot (id),
|
||||
name TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
release TEXT NOT NULL,
|
||||
epoch INTEGER,
|
||||
arch VARCHAR(16) NOT NULL,
|
||||
payloadhash TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
buildtime BIGINT NOT NULL,
|
||||
CONSTRAINT rpminfo_unique_nvra UNIQUE (name,version,release,arch)
|
||||
) WITHOUT OIDS;
|
||||
CREATE INDEX rpminfo_build ON rpminfo(build_id);
|
||||
|
||||
-- sighash is the checksum of the signature header
|
||||
CREATE TABLE rpmsigs (
|
||||
rpm_id INTEGER NOT NULL REFERENCES rpminfo (id),
|
||||
sigkey TEXT NOT NULL,
|
||||
sighash TEXT NOT NULL,
|
||||
CONSTRAINT rpmsigs_no_resign UNIQUE (rpm_id, sigkey)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- buildroot_listing needs to be created after rpminfo so it can reference it
|
||||
CREATE TABLE buildroot_listing (
|
||||
buildroot_id INTEGER NOT NULL REFERENCES buildroot(id),
|
||||
rpm_id INTEGER NOT NULL REFERENCES rpminfo(id),
|
||||
is_update BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
UNIQUE (buildroot_id,rpm_id)
|
||||
) WITHOUT OIDS;
|
||||
CREATE INDEX buildroot_listing_rpms ON buildroot_listing(rpm_id);
|
||||
|
||||
-- this table holds the requires, provides, obsoletes, and conflicts
|
||||
-- for an rpminfo entry
|
||||
CREATE TABLE rpmdeps (
|
||||
pkey SERIAL NOT NULL PRIMARY KEY,
|
||||
rpm_id INTEGER NOT NULL REFERENCES rpminfo (id),
|
||||
dep_name TEXT NOT NULL,
|
||||
dep_version TEXT,
|
||||
dep_flags INTEGER,
|
||||
dep_type INTEGER NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE INDEX rpmdeps_by_rpm_id ON rpmdeps (rpm_id);
|
||||
CREATE INDEX rpmdeps_by_depssolve ON rpmdeps (dep_type, dep_name, dep_flags, dep_version);
|
||||
|
||||
CREATE TABLE rpmfiles (
|
||||
rpm_id INTEGER NOT NULL REFERENCES rpminfo (id),
|
||||
filename TEXT NOT NULL,
|
||||
filemd5 VARCHAR(32) NOT NULL,
|
||||
filesize INTEGER NOT NULL,
|
||||
fileflags INTEGER NOT NULL,
|
||||
PRIMARY KEY (filename, rpm_id)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE INDEX rpmfiles_by_rpm_id ON rpmfiles (rpm_id);
|
||||
CREATE INDEX rpmfiles_by_filename ON rpmfiles (filename);
|
||||
|
||||
CREATE TABLE log_messages (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
message TEXT NOT NULL,
|
||||
message_time TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
logger_name VARCHAR(200) NOT NULL,
|
||||
level VARCHAR(10) NOT NULL,
|
||||
location VARCHAR(200),
|
||||
host VARCHAR(200)
|
||||
) WITHOUT OIDS;
|
||||
|
||||
CREATE TABLE build_notifications (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users (id),
|
||||
package_id INTEGER REFERENCES package (id),
|
||||
tag_id INTEGER REFERENCES tag (id),
|
||||
success_only BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
email TEXT NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
|
||||
GRANT SELECT ON build, package, task, tag,
|
||||
tag_listing, tag_config, tag_inheritance, tag_packages,
|
||||
rpminfo, rpmdeps,
|
||||
rpmfiles TO PUBLIC;
|
||||
|
||||
-- example code to add initial admins
|
||||
-- insert into users (name, usertype, krb_principal) values ('admin', 0, 'admin@EXAMPLE.COM');
|
||||
-- insert into user_perms (user_id, perm_id)
|
||||
-- select users.id, permissions.id from users, permissions
|
||||
-- where users.name in ('admin')
|
||||
-- and permissions.name = 'admin';
|
||||
|
||||
COMMIT WORK;
|
||||
1
hub/.cvsignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.pyc
|
||||
33
hub/Makefile
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
PYTHON=python
|
||||
PACKAGE = $(shell basename `pwd`)
|
||||
PYFILES = $(wildcard *.py)
|
||||
PYVER := $(shell $(PYTHON) -c 'import sys; print "%.3s" %(sys.version)')
|
||||
PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print sys.prefix')
|
||||
PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)
|
||||
PKGDIR = $(PYLIBDIR)/site-packages/$(PACKAGE)
|
||||
|
||||
SERVERDIR = /var/www/koji-hub
|
||||
PYFILES = $(wildcard *.py)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/httpd/conf.d
|
||||
install -m 644 httpd.conf $(DESTDIR)/etc/httpd/conf.d/kojihub.conf
|
||||
|
||||
mkdir -p $(DESTDIR)/$(SERVERDIR)
|
||||
for p in $(PYFILES) ; do \
|
||||
install -m 644 $$p $(DESTDIR)/$(SERVERDIR)/$$p; \
|
||||
done
|
||||
$(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(SERVERDIR)', 1, '$(PYDIR)', 1)"
|
||||
|
||||
28
hub/httpd.conf
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# koji-hub is an xmlrpc interface to the Koji database
|
||||
#
|
||||
|
||||
Alias /kojihub "/var/www/koji-hub/XMLRPC"
|
||||
Alias /koji-hub "/var/www/koji-hub/XMLRPC"
|
||||
|
||||
<Directory /var/www/koji-hub>
|
||||
SetHandler mod_python
|
||||
PythonHandler kojixmlrpc
|
||||
PythonOption DBName koji
|
||||
PythonOption DBUser koji
|
||||
PythonOption DBHost db.example.com
|
||||
PythonOption KojiDir /mnt/koji
|
||||
PythonOption AuthPrincipal kojihub@EXAMPLE.COM
|
||||
PythonOption AuthKeytab /etc/koji.keytab
|
||||
PythonOption ProxyPrincipals kojihub@EXAMPLE.COM
|
||||
PythonOption KojiWebURL http://kojiweb.example.com/koji
|
||||
#format string for host principals (%s = hostname)
|
||||
PythonOption HostPrincipalFormat %s@EXAMPLE.COM
|
||||
#PythonOption KojiDebug On
|
||||
#PythonOption KojiTraceback "extended"
|
||||
PythonDebug Off
|
||||
#sending tracebacks to the client isn't very helpful for debugging xmlrpc
|
||||
PythonAutoReload Off
|
||||
#autoreload is mostly useless to us (it would only reload kojixmlrpc.py)
|
||||
</Directory>
|
||||
|
||||
6130
hub/kojihub.py
Normal file
295
hub/kojixmlrpc.py
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
# mod_python script
|
||||
|
||||
# kojixmlrpc - an XMLRPC interface for koji.
|
||||
# Copyright (c) 2005-2007 Red Hat
|
||||
#
|
||||
# Koji 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;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# This software 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 this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Mike McLean <mikem@redhat.com>
|
||||
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import pprint
|
||||
from xmlrpclib import loads,dumps,Fault
|
||||
from mod_python import apache
|
||||
|
||||
import koji
|
||||
import koji.auth
|
||||
import koji.db
|
||||
from kojihub import RootExports
|
||||
from kojihub import HostExports
|
||||
from koji.context import context
|
||||
|
||||
class ModXMLRPCRequestHandler(object):
|
||||
"""Simple XML-RPC handler for mod_python environment"""
|
||||
|
||||
def __init__(self):
|
||||
self.funcs = {}
|
||||
self.traceback = False
|
||||
#introspection functions
|
||||
self.register_function(self.list_api, name="_listapi")
|
||||
self.register_function(self.system_listMethods, name="system.listMethods")
|
||||
self.register_function(self.system_methodSignature, name="system.methodSignature")
|
||||
self.register_function(self.system_methodHelp, name="system.methodHelp")
|
||||
self.register_function(self.multiCall)
|
||||
|
||||
def register_function(self, function, name = None):
|
||||
if name is None:
|
||||
name = function.__name__
|
||||
self.funcs[name] = function
|
||||
|
||||
def register_module(self, instance, prefix=None):
|
||||
"""Register all the public functions in an instance with prefix prepended
|
||||
|
||||
For example
|
||||
h.register_module(exports,"pub.sys")
|
||||
will register the methods of exports with names like
|
||||
pub.sys.method1
|
||||
pub.sys.method2
|
||||
...etc
|
||||
"""
|
||||
for name in dir(instance):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
function = getattr(instance, name)
|
||||
if not callable(function):
|
||||
continue
|
||||
if prefix is not None:
|
||||
name = "%s.%s" %(prefix,name)
|
||||
self.register_function(function, name=name)
|
||||
|
||||
def register_instance(self,instance):
|
||||
self.register_module(instance)
|
||||
|
||||
def _marshaled_dispatch(self, data):
|
||||
"""Dispatches an XML-RPC method from marshalled (XML) data."""
|
||||
|
||||
params, method = loads(data)
|
||||
|
||||
start = time.time()
|
||||
# generate response
|
||||
try:
|
||||
response = self._dispatch(method, params)
|
||||
# wrap response in a singleton tuple
|
||||
response = (response,)
|
||||
response = dumps(response, methodresponse=1, allow_none=1)
|
||||
except Fault, fault:
|
||||
self.traceback = True
|
||||
response = dumps(fault)
|
||||
except:
|
||||
self.traceback = True
|
||||
# report exception back to server
|
||||
e_class, e = sys.exc_info()[:2]
|
||||
faultCode = getattr(e_class,'faultCode',1)
|
||||
tb_type = context.opts.get('KojiTraceback',None)
|
||||
tb_str = ''.join(traceback.format_exception(*sys.exc_info()))
|
||||
if issubclass(e_class, koji.GenericError):
|
||||
if not context.opts.get('KojiDebug',False):
|
||||
faultString = str(e)
|
||||
elif tb_type == "extended":
|
||||
faultString = koji.format_exc_plus()
|
||||
else:
|
||||
faultString = tb_str
|
||||
else:
|
||||
if tb_type == "normal":
|
||||
faultString = tb_str
|
||||
elif tb_type == "extended":
|
||||
faultString = koji.format_exc_plus()
|
||||
else:
|
||||
faultString = "%s: %s" % (e_class,e)
|
||||
sys.stderr.write(tb_str)
|
||||
sys.stderr.write('\n')
|
||||
response = dumps(Fault(faultCode, faultString))
|
||||
|
||||
sys.stderr.write("Returning %d bytes after %f seconds\n" %
|
||||
(len(response),time.time() - start))
|
||||
sys.stderr.flush()
|
||||
return response
|
||||
|
||||
def _dispatch(self,method,params):
|
||||
func = self.funcs.get(method,None)
|
||||
if func is None:
|
||||
raise koji.GenericError, "Invalid method: %s" % method
|
||||
context.method = method
|
||||
if not hasattr(context,"session"):
|
||||
#we may be called again by one of our meta-calls (like multiCall)
|
||||
#so we should only create a session if one does not already exist
|
||||
context.session = koji.auth.Session()
|
||||
try:
|
||||
context.session.validate()
|
||||
except koji.AuthLockError:
|
||||
#might be ok, depending on method
|
||||
if method not in ('exclusiveSession','login', 'krbLogin', 'logout'):
|
||||
raise
|
||||
if context.opts.get('LockOut',False) and method not in ('login', 'krbLogin', 'logout'):
|
||||
if not context.session.hasPerm('admin'):
|
||||
raise koji.GenericError, "Server disabled for maintenance"
|
||||
# handle named parameters
|
||||
params,opts = koji.decode_args(*params)
|
||||
sys.stderr.write("Handling method %s for session %s (#%s)\n" \
|
||||
% (method, context.session.id, context.session.callnum))
|
||||
if method != 'uploadFile' and context.opts.get('KojiDebug',False):
|
||||
sys.stderr.write("Params: %s\n" % pprint.pformat(params))
|
||||
sys.stderr.write("Opts: %s\n" % pprint.pformat(opts))
|
||||
start = time.time()
|
||||
ret = func(*params,**opts)
|
||||
sys.stderr.write("Completed method %s for session %s (#%s): %f seconds\n"
|
||||
% (method, context.session.id, context.session.callnum,
|
||||
time.time()-start))
|
||||
sys.stderr.flush()
|
||||
return ret
|
||||
|
||||
def multiCall(self, calls):
|
||||
"""Execute a multicall. Execute each method call in the calls list, collecting
|
||||
results and errors, and return those as a list."""
|
||||
results = []
|
||||
for call in calls:
|
||||
try:
|
||||
result = self._dispatch(call['methodName'], call['params'])
|
||||
except Fault, fault:
|
||||
results.append({'faultCode': fault.faultCode, 'faultString': fault.faultString})
|
||||
except:
|
||||
# transform unknown exceptions into XML-RPC Faults
|
||||
# don't create a reference to full traceback since this creates
|
||||
# a circular reference.
|
||||
exc_type, exc_value = sys.exc_info()[:2]
|
||||
faultCode = getattr(exc_type, 'faultCode', 1)
|
||||
faultString = ', '.join(exc_value.args)
|
||||
trace = traceback.format_exception(*sys.exc_info())
|
||||
# traceback is not part of the multicall spec, but we include it for debugging purposes
|
||||
results.append({'faultCode': faultCode, 'faultString': faultString, 'traceback': trace})
|
||||
else:
|
||||
results.append([result])
|
||||
|
||||
return results
|
||||
|
||||
def list_api(self):
|
||||
funcs = []
|
||||
for name,func in self.funcs.items():
|
||||
#the keys in self.funcs determine the name of the method as seen over xmlrpc
|
||||
#func.__name__ might differ (e.g. for dotted method names)
|
||||
args = self._getFuncArgs(func)
|
||||
funcs.append({'name': name,
|
||||
'doc': func.__doc__,
|
||||
'args': args})
|
||||
return funcs
|
||||
|
||||
def _getFuncArgs(self, func):
|
||||
args = []
|
||||
for x in range(0, func.func_code.co_argcount):
|
||||
if x == 0 and func.func_code.co_varnames[x] == "self":
|
||||
continue
|
||||
if func.func_defaults and func.func_code.co_argcount - x <= len(func.func_defaults):
|
||||
args.append((func.func_code.co_varnames[x], func.func_defaults[x - func.func_code.co_argcount + len(func.func_defaults)]))
|
||||
else:
|
||||
args.append(func.func_code.co_varnames[x])
|
||||
return args
|
||||
|
||||
def system_listMethods(self):
|
||||
return self.funcs.keys()
|
||||
|
||||
def system_methodSignature(self, method):
|
||||
#it is not possible to autogenerate this data
|
||||
return 'signatures not supported'
|
||||
|
||||
def system_methodHelp(self, method):
|
||||
func = self.funcs.get(method)
|
||||
if func is None:
|
||||
return ""
|
||||
arglist = []
|
||||
for arg in self._getFuncArgs(func):
|
||||
if isinstance(arg,str):
|
||||
arglist.append(arg)
|
||||
else:
|
||||
arglist.append('%s=%s' % (arg[0], arg[1]))
|
||||
ret = '%s(%s)' % (method, ", ".join(arglist))
|
||||
if func.__doc__:
|
||||
ret += "\ndescription: %s" % func.__doc__
|
||||
return ret
|
||||
|
||||
def handle_request(self,req):
|
||||
"""Handle a single XML-RPC request"""
|
||||
|
||||
# XMLRPC uses POST only. Reject anything else
|
||||
if req.method != 'POST':
|
||||
req.allow_methods(['POST'],1)
|
||||
raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
|
||||
|
||||
response = self._marshaled_dispatch(req.read())
|
||||
|
||||
req.content_type = "text/xml"
|
||||
req.set_content_length(len(response))
|
||||
req.write(response)
|
||||
|
||||
|
||||
#
|
||||
# mod_python handler
|
||||
#
|
||||
|
||||
def handler(req, profiling=False):
|
||||
if profiling:
|
||||
import profile, pstats, StringIO, tempfile
|
||||
global _profiling_req
|
||||
_profiling_req = req
|
||||
temp = tempfile.NamedTemporaryFile()
|
||||
profile.run("import kojixmlrpc; kojixmlrpc.handler(kojixmlrpc._profiling_req, False)", temp.name)
|
||||
stats = pstats.Stats(temp.name)
|
||||
strstream = StringIO.StringIO()
|
||||
sys.stdout = strstream
|
||||
stats.sort_stats("time")
|
||||
stats.print_stats()
|
||||
req.write("<pre>" + strstream.getvalue() + "</pre>")
|
||||
_profiling_req = None
|
||||
else:
|
||||
opts = req.get_options()
|
||||
log_handler = None
|
||||
try:
|
||||
context._threadclear()
|
||||
context.commit_pending = False
|
||||
context.opts = opts
|
||||
context.req = req
|
||||
koji.db.provideDBopts(database = opts["DBName"],
|
||||
user = opts["DBUser"],
|
||||
host = opts.get("DBhost",None))
|
||||
context.cnx = koji.db.connect(opts.get("KojiDebug",False))
|
||||
log_handler = koji.add_db_logger("koji", context.cnx)
|
||||
functions = RootExports()
|
||||
hostFunctions = HostExports()
|
||||
h = ModXMLRPCRequestHandler()
|
||||
h.register_instance(functions)
|
||||
h.register_module(hostFunctions,"host")
|
||||
h.register_function(koji.auth.login)
|
||||
h.register_function(koji.auth.krbLogin)
|
||||
h.register_function(koji.auth.logout)
|
||||
h.register_function(koji.auth.subsession)
|
||||
h.register_function(koji.auth.logoutChild)
|
||||
h.register_function(koji.auth.exclusiveSession)
|
||||
h.register_function(koji.auth.sharedSession)
|
||||
h.handle_request(req)
|
||||
if h.traceback:
|
||||
#rollback
|
||||
context.cnx.rollback()
|
||||
elif context.commit_pending:
|
||||
context.cnx.commit()
|
||||
finally:
|
||||
if log_handler != None:
|
||||
koji.remove_log_handler("koji", log_handler)
|
||||
#make sure context gets cleaned up
|
||||
if hasattr(context,'cnx'):
|
||||
context.cnx.close()
|
||||
context._threadclear()
|
||||
return apache.OK
|
||||
151
koji.spec
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
|
||||
|
||||
%define testbuild 1
|
||||
%define debug_package %{nil}
|
||||
|
||||
%define baserelease 5
|
||||
%if %{testbuild}
|
||||
%define release %{baserelease}.%(date +%%Y%%m%%d.%%H%%M.%%S)
|
||||
%else
|
||||
%define release %{baserelease}
|
||||
%endif
|
||||
Name: koji
|
||||
Version: 0.9.5
|
||||
Release: %{release}%{?dist}
|
||||
License: LGPL
|
||||
Summary: Build system tools
|
||||
Group: Applications/System
|
||||
Source: koji-%{PACKAGE_VERSION}.tar.bz2
|
||||
BuildRoot: %(mktemp -d %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
|
||||
BuildArch: noarch
|
||||
Requires: python-krbV >= 1.0.13
|
||||
BuildRequires: python
|
||||
|
||||
%description
|
||||
Koji is a system for building and tracking RPMS. The base package
|
||||
contains shared libraries and the command-line interface.
|
||||
|
||||
%package hub
|
||||
Summary: Koji XMLRPC interface
|
||||
Group: Applications/Internet
|
||||
Requires: httpd
|
||||
Requires: mod_python
|
||||
Requires: postgresql-python
|
||||
Requires: koji = %{version}-%{release}
|
||||
|
||||
%description hub
|
||||
koji-hub is the XMLRPC interface to the koji database
|
||||
|
||||
%package builder
|
||||
Summary: Koji RPM builder daemon
|
||||
Group: Applications/System
|
||||
Requires: koji = %{version}-%{release}
|
||||
Requires: mock >= 0.5-3
|
||||
Requires(post): /sbin/chkconfig
|
||||
Requires(post): /sbin/service
|
||||
Requires(preun): /sbin/chkconfig
|
||||
Requires(preun): /sbin/service
|
||||
Requires(pre): /usr/sbin/useradd
|
||||
Requires: cvs
|
||||
Requires: rpm-build
|
||||
Requires: redhat-rpm-config
|
||||
Requires: createrepo >= 0.4.4-3
|
||||
|
||||
%description builder
|
||||
koji-builder is the daemon that runs on build machines and executes
|
||||
tasks that come through the Koji system.
|
||||
|
||||
%package utils
|
||||
Summary: Koji Utilities
|
||||
Group: Applications/Internet
|
||||
Requires: postgresql-python
|
||||
Requires: koji = %{version}-%{release}
|
||||
Requires: rpm-build
|
||||
Requires: createrepo >= 0.4.4-3
|
||||
|
||||
%description utils
|
||||
Utilities for the Koji system
|
||||
|
||||
%package web
|
||||
Summary: Koji Web UI
|
||||
Group: Applications/Internet
|
||||
Requires: httpd
|
||||
Requires: mod_python
|
||||
Requires: mod_auth_kerb
|
||||
Requires: postgresql-python
|
||||
Requires: python-cheetah
|
||||
Requires: koji = %{version}-%{release}
|
||||
Requires: python-krbV >= 1.0.13
|
||||
|
||||
%description web
|
||||
koji-web is a web UI to the Koji system.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
make DESTDIR=$RPM_BUILD_ROOT install
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
%files
|
||||
%defattr(-,root,root)
|
||||
%{_bindir}/*
|
||||
%{python_sitelib}/koji
|
||||
%config(noreplace) %{_sysconfdir}/koji.conf
|
||||
%doc docs
|
||||
|
||||
%files hub
|
||||
%defattr(-,root,root)
|
||||
%{_var}/www/koji-hub
|
||||
%config(noreplace) /etc/httpd/conf.d/kojihub.conf
|
||||
|
||||
%files utils
|
||||
%defattr(-,root,root)
|
||||
%{_sbindir}/kojira
|
||||
%config %{_initrddir}/kojira
|
||||
%config %{_sysconfdir}/sysconfig/kojira
|
||||
%config(noreplace) %{_sysconfdir}/kojira.conf
|
||||
|
||||
%files web
|
||||
%defattr(-,root,root)
|
||||
%{_var}/www/koji-web
|
||||
%config(noreplace) /etc/httpd/conf.d/kojiweb.conf
|
||||
|
||||
%files builder
|
||||
%defattr(-,root,root)
|
||||
%{_sbindir}/kojid
|
||||
%config %{_initrddir}/kojid
|
||||
%config %{_sysconfdir}/sysconfig/kojid
|
||||
%config(noreplace) %{_sysconfdir}/kojid.conf
|
||||
%attr(-,kojibuilder,kojibuilder) /etc/mock/koji
|
||||
|
||||
%pre builder
|
||||
/usr/sbin/useradd -r -s /bin/bash -G mock -d /builddir -M kojibuilder 2>/dev/null ||:
|
||||
|
||||
%post builder
|
||||
/sbin/chkconfig --add kojid
|
||||
/sbin/service kojid condrestart &> /dev/null || :
|
||||
|
||||
%preun builder
|
||||
if [ $1 = 0 ]; then
|
||||
/sbin/service kojid stop &> /dev/null
|
||||
/sbin/chkconfig --del kojid
|
||||
fi
|
||||
|
||||
%post utils
|
||||
/sbin/chkconfig --add kojira
|
||||
/sbin/service kojira condrestart &> /dev/null || :
|
||||
%preun utils
|
||||
if [ $1 = 0 ]; then
|
||||
/sbin/service kojira stop &> /dev/null || :
|
||||
/sbin/chkconfig --del kojira
|
||||
fi
|
||||
|
||||
%changelog
|
||||
* Sun Feb 04 2007 Mike McLean <mikem@redhat.com> - 0.9.5-1
|
||||
- project renamed to koji
|
||||
1
koji/.cvsignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.pyc
|
||||
20
koji/Makefile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
PYTHON=python
|
||||
PACKAGE = $(shell basename `pwd`)
|
||||
PYFILES = $(wildcard *.py)
|
||||
PYVER := $(shell $(PYTHON) -c 'import sys; print "%.3s" %(sys.version)')
|
||||
PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print sys.prefix')
|
||||
PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)
|
||||
PKGDIR = $(PYLIBDIR)/site-packages/$(PACKAGE)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)/$(PKGDIR)
|
||||
for p in $(PYFILES) ; do \
|
||||
install -m 644 $$p $(DESTDIR)/$(PKGDIR)/$$p; \
|
||||
done
|
||||
$(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(PKGDIR)', 1, '$(PYDIR)', 1)"
|
||||
1571
koji/__init__.py
Normal file
587
koji/auth.py
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
# authentication module
|
||||
# Copyright (c) 2005-2007 Red Hat
|
||||
#
|
||||
# Koji 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;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# This software 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 this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Mike McLean <mikem@redhat.com>
|
||||
|
||||
import socket
|
||||
import string
|
||||
import random
|
||||
import base64
|
||||
import krbV
|
||||
import koji
|
||||
import cgi #for parse_qs
|
||||
from context import context
|
||||
from mod_python import apache
|
||||
|
||||
# 1 - load session if provided
|
||||
# - check uri for session id
|
||||
# - load session info from db
|
||||
# - validate session
|
||||
# 2 - create a session
|
||||
# - maybe in two steps
|
||||
# -
|
||||
|
||||
class Session(object):
|
||||
|
||||
def __init__(self,args=None,hostip=None):
|
||||
self.logged_in = False
|
||||
self.id = None
|
||||
self.master = None
|
||||
self.key = None
|
||||
self.user_id = None
|
||||
self.message = ''
|
||||
self.exclusive = False
|
||||
self.lockerror = None
|
||||
self.callnum = None
|
||||
#get session data from request
|
||||
if args is None:
|
||||
req = getattr(context,'req',None)
|
||||
args = getattr(req,'args',None)
|
||||
if not args:
|
||||
self.message = 'no session args'
|
||||
return
|
||||
args = cgi.parse_qs(args,strict_parsing=True)
|
||||
if hostip is None:
|
||||
hostip = context.req.get_remote_host(apache.REMOTE_NOLOOKUP)
|
||||
if hostip == '127.0.0.1':
|
||||
hostip = socket.gethostbyname(socket.gethostname())
|
||||
try:
|
||||
id = long(args['session-id'][0])
|
||||
key = args['session-key'][0]
|
||||
except KeyError, field:
|
||||
raise koji.AuthError, '%s not specified in session args' % field
|
||||
try:
|
||||
callnum = args['callnum'][0]
|
||||
except:
|
||||
callnum = None
|
||||
#lookup the session
|
||||
c = context.cnx.cursor()
|
||||
fields = ('user_id','authtype','expired','start_time','update_time',
|
||||
'master','exclusive','callnum')
|
||||
q = """
|
||||
SELECT %s FROM sessions
|
||||
WHERE id = %%(id)i
|
||||
AND key = %%(key)s
|
||||
AND hostip = %%(hostip)s
|
||||
FOR UPDATE
|
||||
""" % ",".join(fields)
|
||||
c.execute(q,locals())
|
||||
row = c.fetchone()
|
||||
if not row:
|
||||
raise koji.AuthError, 'Invalid session or bad credentials'
|
||||
session_data = dict(zip(fields,row))
|
||||
#check for expiration
|
||||
if session_data['expired']:
|
||||
raise koji.AuthExpired, 'session "%i" has expired' % id
|
||||
#check for callnum sanity
|
||||
if callnum is not None:
|
||||
try:
|
||||
callnum = int(callnum)
|
||||
except (ValueError,TypeError):
|
||||
raise koji.AuthError, "Invalid callnum: %r" % callnum
|
||||
lastcall = session_data['callnum']
|
||||
if lastcall is not None:
|
||||
if lastcall > callnum:
|
||||
raise koji.SequenceError, "%d > %d (session %d)" \
|
||||
% (lastcall,callnum,id)
|
||||
elif lastcall == callnum:
|
||||
#Some explanation:
|
||||
#This function is one of the few that performs its own commit.
|
||||
#However, our storage of the current callnum is /after/ that
|
||||
#commit. This means the the current callnum only gets commited if
|
||||
#a commit happens afterward.
|
||||
#We only schedule a commit for dml operations, so if we find the
|
||||
#callnum in the db then a previous attempt succeeded but failed to
|
||||
#return. Data was changed, so we cannot simply try the call again.
|
||||
raise koji.RetryError, \
|
||||
"unable to retry call %d (method %s) for session %d" \
|
||||
% (callnum, getattr(context, 'method', 'UNKNOWN'), id)
|
||||
|
||||
# read user data
|
||||
#historical note:
|
||||
# we used to get a row lock here as an attempt to maintain sanity of exclusive
|
||||
# sessions, but it was an imperfect approach and the lock could cause some
|
||||
# performance issues.
|
||||
fields = ('name','status','usertype')
|
||||
q = """SELECT %s FROM users WHERE id=%%(user_id)s""" % ','.join(fields)
|
||||
c.execute(q,session_data)
|
||||
user_data = dict(zip(fields,c.fetchone()))
|
||||
|
||||
if user_data['status'] == koji.USER_STATUS['BLOCKED']:
|
||||
raise koji.AuthError, 'User not allowed'
|
||||
#check for exclusive sessions
|
||||
if session_data['exclusive']:
|
||||
#we are the exclusive session for this user
|
||||
self.exclusive = True
|
||||
else:
|
||||
#see if an exclusive session exists
|
||||
q = """SELECT id FROM sessions WHERE user_id=%(user_id)s
|
||||
AND "exclusive" = TRUE AND expired = FALSE"""
|
||||
#should not return multiple rows (unique constraint)
|
||||
c.execute(q,session_data)
|
||||
row = c.fetchone()
|
||||
if row:
|
||||
(excl_id,) = row
|
||||
if excl_id == session_data['master']:
|
||||
#(note excl_id cannot be None)
|
||||
#our master session has the lock
|
||||
self.exclusive = True
|
||||
else:
|
||||
#a session unrelated to us has the lock
|
||||
self.lockerror = "User locked by another session"
|
||||
# we don't enforce here, but rely on the dispatcher to enforce
|
||||
# if appropriate (otherwise it would be impossible to steal
|
||||
# an exclusive session with the force option).
|
||||
|
||||
# update timestamp
|
||||
q = """UPDATE sessions SET update_time=NOW() WHERE id = %(id)i"""
|
||||
c.execute(q,locals())
|
||||
#save update time
|
||||
context.cnx.commit()
|
||||
|
||||
#update callnum (this is deliberately after the commit)
|
||||
#see earlier note near RetryError
|
||||
if callnum is not None:
|
||||
q = """UPDATE sessions SET callnum=%(callnum)i WHERE id = %(id)i"""
|
||||
c.execute(q,locals())
|
||||
|
||||
# record the login data
|
||||
self.id = id
|
||||
self.key = key
|
||||
self.hostip = hostip
|
||||
self.callnum = callnum
|
||||
self.user_id = session_data['user_id']
|
||||
self.authtype = session_data['authtype']
|
||||
self.master = session_data['master']
|
||||
self.session_data = session_data
|
||||
self.user_data = user_data
|
||||
# we look up perms, groups, and host_id on demand, see __getattr__
|
||||
self._perms = None
|
||||
self._groups = None
|
||||
self._host_id = ''
|
||||
self.logged_in = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
# grab perm and groups data on the fly
|
||||
if name == 'perms':
|
||||
if self._perms is None:
|
||||
#in a dict for quicker lookup
|
||||
self._perms = dict([[name,1] for name in get_user_perms(self.user_id)])
|
||||
return self._perms
|
||||
elif name == 'groups':
|
||||
if self._groups is None:
|
||||
self._groups = get_user_groups(self.user_id)
|
||||
return self._groups
|
||||
elif name == 'host_id':
|
||||
if self._host_id == '':
|
||||
self._host_id = self._getHostId()
|
||||
return self._host_id
|
||||
else:
|
||||
raise AttributeError, "%s" % name
|
||||
|
||||
def __str__(self):
|
||||
# convenient display for debugging
|
||||
if not self.logged_in:
|
||||
s = "session: not logged in"
|
||||
else:
|
||||
s = "session %d: %r" % (self.id, self.__dict__)
|
||||
if self.message:
|
||||
s += " (%s)" % self.message
|
||||
return s
|
||||
|
||||
def validate(self):
|
||||
if self.lockerror:
|
||||
raise koji.AuthLockError, self.lockerror
|
||||
return True
|
||||
|
||||
def login(self,user,password,opts=None):
|
||||
"""create a login session"""
|
||||
if opts is None:
|
||||
opts = {}
|
||||
if not isinstance(password,str) or len(password) == 0:
|
||||
raise koji.AuthError, 'invalid username or password'
|
||||
if self.logged_in:
|
||||
raise koji.GenericError, "Already logged in"
|
||||
hostip = opts.get('hostip')
|
||||
if hostip is None:
|
||||
hostip = context.req.get_remote_host(apache.REMOTE_NOLOOKUP)
|
||||
if hostip == '127.0.0.1':
|
||||
hostip = socket.gethostbyname(socket.gethostname())
|
||||
|
||||
# check passwd
|
||||
c = context.cnx.cursor()
|
||||
q = """SELECT id,status,usertype FROM users
|
||||
WHERE name = %(user)s AND password = %(password)s"""
|
||||
c.execute(q,locals())
|
||||
r = c.fetchone()
|
||||
if not r:
|
||||
raise koji.AuthError, 'invalid username or password'
|
||||
(user_id,status,usertype) = r
|
||||
|
||||
#create session and return
|
||||
sinfo = self.createSession(user_id, hostip, koji.AUTHTYPE_NORMAL)
|
||||
session_id = sinfo['session-id']
|
||||
context.cnx.commit()
|
||||
return sinfo
|
||||
|
||||
def krbLogin(self, krb_req, proxyuser=None):
|
||||
"""Authenticate the user using the base64-encoded
|
||||
AP_REQ message in krb_req. If proxyuser is not None,
|
||||
log in that user instead of the user associated with the
|
||||
Kerberos principal. The principal must be an authorized
|
||||
"proxy_principal" in the server config."""
|
||||
if self.logged_in:
|
||||
raise koji.AuthError, "Already logged in"
|
||||
|
||||
ctx = krbV.default_context()
|
||||
srvprinc = krbV.Principal(name=context.req.get_options()['AuthPrincipal'], context=ctx)
|
||||
srvkt = krbV.Keytab(name=context.req.get_options()['AuthKeytab'], context=ctx)
|
||||
|
||||
ac = krbV.AuthContext(context=ctx)
|
||||
ac.flags = krbV.KRB5_AUTH_CONTEXT_DO_SEQUENCE|krbV.KRB5_AUTH_CONTEXT_DO_TIME
|
||||
conninfo = self.getConnInfo()
|
||||
ac.addrs = conninfo
|
||||
|
||||
# decode and read the authentication request
|
||||
req = base64.decodestring(krb_req)
|
||||
ac, opts, sprinc, ccreds = ctx.rd_req(req, server=srvprinc, keytab=srvkt,
|
||||
auth_context=ac,
|
||||
options=krbV.AP_OPTS_MUTUAL_REQUIRED)
|
||||
cprinc = ccreds[2]
|
||||
|
||||
# Successfully authenticated via Kerberos, now log in
|
||||
if proxyuser:
|
||||
proxyprincs = [princ.strip() for princ in context.req.get_options()['ProxyPrincipals'].split(',')]
|
||||
if cprinc.name in proxyprincs:
|
||||
login_principal = proxyuser
|
||||
else:
|
||||
raise koji.AuthError, \
|
||||
'Kerberos principal %s is not authorized to log in other users' % cprinc.name
|
||||
else:
|
||||
login_principal = cprinc.name
|
||||
user_id = self.getUserIdFromKerberos(login_principal)
|
||||
if not user_id:
|
||||
user_id = self.createUserFromKerberos(login_principal)
|
||||
|
||||
hostip = context.req.connection.remote_ip
|
||||
if hostip == '127.0.0.1':
|
||||
hostip = socket.gethostbyname(socket.gethostname())
|
||||
|
||||
sinfo = self.createSession(user_id, hostip, koji.AUTHTYPE_KERB)
|
||||
|
||||
# encode the reply
|
||||
rep = ctx.mk_rep(auth_context=ac)
|
||||
rep_enc = base64.encodestring(rep)
|
||||
|
||||
# encrypt and encode the login info
|
||||
sinfo_priv = ac.mk_priv('%(session-id)s %(session-key)s' % sinfo)
|
||||
sinfo_enc = base64.encodestring(sinfo_priv)
|
||||
|
||||
return (rep_enc, sinfo_enc, conninfo)
|
||||
|
||||
def getConnInfo(self):
|
||||
"""Return a tuple containing connection information
|
||||
in the following format:
|
||||
(local ip addr, local port, remote ip, remote port)"""
|
||||
# For some reason req.connection.{local,remote}_addr contain port info,
|
||||
# but no IP info. Use req.connection.{local,remote}_ip for that instead.
|
||||
# See: http://lists.planet-lab.org/pipermail/devel-community/2005-June/001084.html
|
||||
# local_ip seems to always be set to the same value as remote_ip,
|
||||
# so get the local ip via a different method
|
||||
# local_ip = context.req.connection.local_ip
|
||||
local_ip = socket.gethostbyname(context.req.hostname)
|
||||
remote_ip = context.req.connection.remote_ip
|
||||
|
||||
# it appears that calling setports() with *any* value results in authentication
|
||||
# failing with "Incorrect net address", so return 0 (which prevents
|
||||
# python-krbV from calling setports())
|
||||
# local_port = context.req.connection.local_addr[1]
|
||||
# remote_port = context.req.connection.remote_addr[1]
|
||||
local_port = 0
|
||||
remote_port = 0
|
||||
|
||||
return (local_ip, local_port, remote_ip, remote_port)
|
||||
|
||||
def makeExclusive(self,force=False):
|
||||
"""Make this session exclusive"""
|
||||
c = context.cnx.cursor()
|
||||
if self.master is not None:
|
||||
raise koji.GenericError, "subsessions cannot become exclusive"
|
||||
if self.exclusive:
|
||||
#shouldn't happen
|
||||
raise koji.GenericError, "session is already exclusive"
|
||||
user_id = self.user_id
|
||||
session_id = self.id
|
||||
#acquire a row lock on the user entry
|
||||
q = """SELECT id FROM users WHERE id=%(user_id)s FOR UPDATE"""
|
||||
c.execute(q,locals())
|
||||
# check that no other sessions for this user are exclusive
|
||||
q = """SELECT id FROM sessions WHERE user_id=%(user_id)s
|
||||
AND expired = FALSE AND "exclusive" = TRUE
|
||||
FOR UPDATE"""
|
||||
c.execute(q,locals())
|
||||
row = c.fetchone()
|
||||
if row:
|
||||
if force:
|
||||
#expire the previous exclusive session and try again
|
||||
(excl_id,) = row
|
||||
q = """UPDATE sessions SET expired=TRUE,"exclusive"=NULL WHERE id=%(excl_id)s"""
|
||||
c.execute(q,locals())
|
||||
else:
|
||||
raise koji.AuthLockError, "Cannot get exclusive session"
|
||||
#mark this session exclusive
|
||||
q = """UPDATE sessions SET "exclusive"=TRUE WHERE id=%(session_id)s"""
|
||||
c.execute(q,locals())
|
||||
context.cnx.commit()
|
||||
|
||||
def makeShared(self):
|
||||
"""Drop out of exclusive mode"""
|
||||
c = context.cnx.cursor()
|
||||
session_id = self.id
|
||||
q = """UPDATE sessions SET "exclusive"=NULL WHERE id=%(session_id)s"""
|
||||
c.execute(q,locals())
|
||||
context.cnx.commit()
|
||||
|
||||
def logout(self):
|
||||
"""expire a login session"""
|
||||
if not self.logged_in:
|
||||
#XXX raise an error?
|
||||
raise koji.AuthError, "Not logged in"
|
||||
update = """UPDATE sessions
|
||||
SET expired=TRUE,exclusive=NULL
|
||||
WHERE id = %(id)i OR master = %(id)i"""
|
||||
#note we expire subsessions as well
|
||||
c = context.cnx.cursor()
|
||||
c.execute(update, {'id': self.id})
|
||||
context.cnx.commit()
|
||||
self.logged_in = False
|
||||
|
||||
def logoutChild(self, session_id):
|
||||
"""expire a subsession"""
|
||||
if not self.logged_in:
|
||||
#XXX raise an error?
|
||||
raise koji.AuthError, "Not logged in"
|
||||
update = """UPDATE sessions
|
||||
SET expired=TRUE,exclusive=NULL
|
||||
WHERE id = %(session_id)i AND master = %(master)i"""
|
||||
master = self.id
|
||||
c = context.cnx.cursor()
|
||||
c.execute(update, locals())
|
||||
context.cnx.commit()
|
||||
|
||||
def createSession(self, user_id, hostip, authtype, master=None, locked=False):
|
||||
"""Create a new session for the given user.
|
||||
|
||||
Return a map containing the session-id and session-key.
|
||||
If master is specified, create a subsession
|
||||
"""
|
||||
c = context.cnx.cursor()
|
||||
if not locked:
|
||||
#acquire a row lock on the user entry
|
||||
q = """SELECT id FROM users WHERE id=%(user_id)s"""
|
||||
c.execute(q,locals())
|
||||
|
||||
# generate a random key
|
||||
alnum = string.ascii_letters + string.digits
|
||||
key = "%s-%s" %(user_id,
|
||||
''.join([ random.choice(alnum) for x in range(1,20) ]))
|
||||
# use sha? sha.new(phrase).hexdigest()
|
||||
|
||||
# get a session id
|
||||
q = """SELECT nextval('sessions_id_seq')"""
|
||||
c.execute(q, {})
|
||||
(session_id,) = c.fetchone()
|
||||
|
||||
#add session id to database
|
||||
q = """
|
||||
INSERT INTO sessions (id, user_id, key, hostip, authtype, master)
|
||||
VALUES (%(session_id)i, %(user_id)i, %(key)s, %(hostip)s, %(authtype)i, %(master)s)
|
||||
"""
|
||||
c.execute(q,locals())
|
||||
context.cnx.commit()
|
||||
|
||||
#return session info
|
||||
return {'session-id' : session_id, 'session-key' : key}
|
||||
|
||||
def subsession(self):
|
||||
"Create a subsession"
|
||||
if not self.logged_in:
|
||||
raise koji.AuthError, "Not logged in"
|
||||
master = self.master
|
||||
if master is None:
|
||||
master=self.id
|
||||
return self.createSession(self.user_id, self.hostip, self.authtype,
|
||||
master=master)
|
||||
|
||||
def getPerms(self):
|
||||
if not self.logged_in:
|
||||
return []
|
||||
return self.perms.keys()
|
||||
|
||||
def hasPerm(self, name):
|
||||
if not self.logged_in:
|
||||
return False
|
||||
return self.perms.has_key(name)
|
||||
|
||||
def assertPerm(self, name):
|
||||
if not self.hasPerm(name) and not self.hasPerm('admin'):
|
||||
raise koji.NotAllowed, "%s permission required" % name
|
||||
|
||||
def hasGroup(self, group_id):
|
||||
if not self.logged_in:
|
||||
return False
|
||||
#groups indexed by id
|
||||
return self.groups.has_key(group_id)
|
||||
|
||||
def isUser(self, user_id):
|
||||
if not self.logged_in:
|
||||
return False
|
||||
return ( self.user_id == user_id or self.hasGroup(user_id) )
|
||||
|
||||
def assertUser(self, user_id):
|
||||
if not self.isUser(user_id) and not self.hasPerm('admin'):
|
||||
raise koji.NotAllowed, "not owner"
|
||||
|
||||
def _getHostId(self):
|
||||
'''Using session data, find host id (if there is one)'''
|
||||
if self.user_id is None:
|
||||
return None
|
||||
c=context.cnx.cursor()
|
||||
q="""SELECT id FROM host WHERE user_id = %(uid)d"""
|
||||
c.execute(q,{'uid' : self.user_id })
|
||||
r=c.fetchone()
|
||||
c.close()
|
||||
if r:
|
||||
return r[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def getHostId(self):
|
||||
#for compatibility
|
||||
return self.host_id
|
||||
|
||||
def getUserIdFromKerberos(self, krb_principal):
|
||||
"""Return the user ID associated with a particular Kerberos principal.
|
||||
If no user with the given princpal if found, return None."""
|
||||
c = context.cnx.cursor()
|
||||
q = """SELECT id FROM users WHERE krb_principal = %(krb_principal)s"""
|
||||
c.execute(q,locals())
|
||||
r = c.fetchone()
|
||||
c.close()
|
||||
if r:
|
||||
return r[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def createUserFromKerberos(self, krb_principal):
|
||||
"""Create a new user, based on the Kerberos principal. Their
|
||||
username will be everything before the "@" in the principal.
|
||||
Return the ID of the newly created user."""
|
||||
atidx = krb_principal.find('@')
|
||||
if atidx == -1:
|
||||
raise koji.AuthError, 'invalid Kerberos principal: %s' % krb_principal
|
||||
user_name = krb_principal[:atidx]
|
||||
user_type = koji.USERTYPES['NORMAL']
|
||||
|
||||
c = context.cnx.cursor()
|
||||
select = """SELECT nextval('users_id_seq')"""
|
||||
c.execute(select, locals())
|
||||
user_id = c.fetchone()[0]
|
||||
|
||||
insert = """INSERT INTO users (id, name, password, usertype, krb_principal)
|
||||
VALUES (%(user_id)i, %(user_name)s, null, %(user_type)i, %(krb_principal)s)"""
|
||||
c.execute(insert, locals())
|
||||
context.cnx.commit()
|
||||
|
||||
return user_id
|
||||
|
||||
def get_user_groups(user_id):
|
||||
"""Get user groups
|
||||
|
||||
returns a dictionary where the keys are the group ids and the values
|
||||
are the group names"""
|
||||
c = context.cnx.cursor()
|
||||
t_group = koji.USERTYPES['GROUP']
|
||||
q = """SELECT group_id,name
|
||||
FROM user_groups JOIN users ON group_id = users.id
|
||||
WHERE active = TRUE AND users.usertype=%(t_group)i
|
||||
AND user_id=%(user_id)i"""
|
||||
c.execute(q,locals())
|
||||
return dict(c.fetchall())
|
||||
|
||||
def get_user_perms(user_id):
|
||||
c = context.cnx.cursor()
|
||||
q = """SELECT name
|
||||
FROM user_perms JOIN permissions ON perm_id = permissions.id
|
||||
WHERE active = TRUE AND user_id=%(user_id)s"""
|
||||
c.execute(q,locals())
|
||||
#return a list of permissions by name
|
||||
return [row[0] for row in c.fetchall()]
|
||||
|
||||
def get_user_data(user_id):
|
||||
c = context.cnx.cursor()
|
||||
fields = ('name','status','usertype')
|
||||
q = """SELECT %s FROM users WHERE id=%%(user_id)s""" % ','.join(fields)
|
||||
c.execute(q,locals())
|
||||
row = c.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
return dict(zip(fields,row))
|
||||
|
||||
def login(*args,**opts):
|
||||
return context.session.login(*args,**opts)
|
||||
|
||||
def krbLogin(*args, **opts):
|
||||
return context.session.krbLogin(*args, **opts)
|
||||
|
||||
def logout():
|
||||
return context.session.logout()
|
||||
|
||||
def subsession():
|
||||
return context.session.subsession()
|
||||
|
||||
def logoutChild(session_id):
|
||||
return context.session.logoutChild(session_id)
|
||||
|
||||
def exclusiveSession(*args,**opts):
|
||||
"""Make this session exclusive"""
|
||||
return context.session.makeExclusive(*args,**opts)
|
||||
|
||||
def sharedSession():
|
||||
"""Drop out of exclusive mode"""
|
||||
return context.session.makeShared()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# XXX - testing defaults
|
||||
import db
|
||||
db.setDBopts( database = "test", user = "test")
|
||||
print "Connecting to db"
|
||||
context.cnx = db.connect()
|
||||
print "starting session 1"
|
||||
sess = Session(None,hostip='127.0.0.1')
|
||||
print "Session 1: %s" % sess
|
||||
print "logging in with session 1"
|
||||
session_info = sess.login('host/1','foobar',{'hostip':'127.0.0.1'})
|
||||
#wrap values in lists
|
||||
session_info = dict([ [k,[v]] for k,v in session_info.iteritems()])
|
||||
print "Session 1: %s" % sess
|
||||
print "Session 1 info: %r" % session_info
|
||||
print "Creating session 2"
|
||||
s2 = Session(session_info,'127.0.0.1')
|
||||
print "Session 2: %s " % s2
|
||||
109
koji/context.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/python
|
||||
# Copyright (c) 2005-2007 Red Hat
|
||||
#
|
||||
# Koji 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;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# This software 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 this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
# This modules provides a thread-safe way of passing
|
||||
# request context around in a global way
|
||||
# - db connections
|
||||
# - request data
|
||||
# - auth data
|
||||
|
||||
import thread
|
||||
|
||||
class _data(object):
|
||||
pass
|
||||
|
||||
class ThreadLocal(object):
|
||||
def __init__(self):
|
||||
object.__setattr__(self, '_tdict', {})
|
||||
|
||||
# should probably be getattribute, but easier to debug this way
|
||||
def __getattr__(self, key):
|
||||
id = thread.get_ident()
|
||||
tdict = object.__getattribute__(self, '_tdict')
|
||||
if not tdict.has_key(id):
|
||||
raise AttributeError(key)
|
||||
data = tdict[id]
|
||||
return object.__getattribute__(data, key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
id = thread.get_ident()
|
||||
tdict = object.__getattribute__(self, '_tdict')
|
||||
if not tdict.has_key(id):
|
||||
tdict[id] = _data()
|
||||
data = tdict[id]
|
||||
return object.__setattr__(data,key,value)
|
||||
|
||||
def __delattr__(self, key):
|
||||
id = thread.get_ident()
|
||||
tdict = object.__getattribute__(self, '_tdict')
|
||||
if not tdict.has_key(id):
|
||||
raise AttributeError(key)
|
||||
data = tdict[id]
|
||||
ret = object.__delattr__(data, key)
|
||||
if len(data.__dict__) == 0:
|
||||
del tdict[id]
|
||||
return ret
|
||||
|
||||
def __str__(self):
|
||||
id = thread.get_ident()
|
||||
tdict = object.__getattribute__(self, '_tdict')
|
||||
return "(current thread: %s) {" % id + \
|
||||
", ".join([ "%s : %s" %(k,v.__dict__) for (k,v) in tdict.iteritems() ]) + \
|
||||
"}"
|
||||
|
||||
def _threadclear(self):
|
||||
id = thread.get_ident()
|
||||
tdict = object.__getattribute__(self, '_tdict')
|
||||
if not tdict.has_key(id):
|
||||
return
|
||||
del tdict[id]
|
||||
|
||||
|
||||
context = ThreadLocal()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
#testing
|
||||
|
||||
#context.foo = 1
|
||||
#context.bar = 2
|
||||
print context
|
||||
#del context.bar
|
||||
print context
|
||||
|
||||
import random
|
||||
import time
|
||||
def test():
|
||||
context.foo=random.random()
|
||||
time.sleep(1.5+random.random())
|
||||
context._threadclear()
|
||||
print context
|
||||
|
||||
for x in xrange(1,10):
|
||||
thread.start_new_thread(test,())
|
||||
|
||||
time.sleep(4)
|
||||
print
|
||||
print context
|
||||
|
||||
context.foo = 1
|
||||
context.bar = 2
|
||||
print context.foo,context.bar
|
||||
print context
|
||||
context._threadclear()
|
||||
print context
|
||||
137
koji/db.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# python library
|
||||
|
||||
# db utilities for koji
|
||||
# Copyright (c) 2005-2007 Red Hat
|
||||
#
|
||||
# Koji 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;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# This software 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 this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Mike McLean <mikem@redhat.com>
|
||||
|
||||
|
||||
import sys
|
||||
import pgdb
|
||||
import time
|
||||
from pgdb import _quoteparams
|
||||
assert pgdb.threadsafety >= 1
|
||||
import context
|
||||
|
||||
## Globals ##
|
||||
_DBopts = None
|
||||
# A persistent connection to the database.
|
||||
# A new connection will be created whenever
|
||||
# Apache forks a new worker, and that connection
|
||||
# will be used to service all requests handled
|
||||
# by that worker.
|
||||
# This probably doesn't need to be a ThreadLocal
|
||||
# since Apache is not using threading,
|
||||
# but play it safe anyway.
|
||||
_DBconn = context.ThreadLocal()
|
||||
|
||||
class DBWrapper:
|
||||
def __init__(self, cnx, debug=False):
|
||||
self.cnx = cnx
|
||||
self.debug = debug
|
||||
|
||||
def __getattr__(self, key):
|
||||
if not self.cnx:
|
||||
raise StandardError, 'connection is closed'
|
||||
return getattr(self.cnx, key)
|
||||
|
||||
def cursor(self, *args, **kw):
|
||||
if not self.cnx:
|
||||
raise StandardError, 'connection is closed'
|
||||
return CursorWrapper(self.cnx.cursor(*args, **kw),self.debug)
|
||||
|
||||
def close(self):
|
||||
# Rollback any uncommitted changes and clear the connection so
|
||||
# this DBWrapper is no longer usable after close()
|
||||
if not self.cnx:
|
||||
raise StandardError, 'connection is closed'
|
||||
self.cnx.rollback()
|
||||
self.cnx = None
|
||||
|
||||
class CursorWrapper:
|
||||
def __init__(self, cursor, debug=False):
|
||||
self.cursor = cursor
|
||||
self.debug = debug
|
||||
|
||||
def __getattr__(self, key):
|
||||
return getattr(self.cursor, key)
|
||||
|
||||
def _timed_call(self, method, args, kwargs):
|
||||
if self.debug:
|
||||
start = time.time()
|
||||
ret = getattr(self.cursor,method)(*args,**kwargs)
|
||||
if self.debug:
|
||||
sys.stderr.write("%s operation completed in %.4f seconds\n" %
|
||||
(method, time.time() - start))
|
||||
sys.stderr.flush()
|
||||
return ret
|
||||
|
||||
def fetchone(self,*args,**kwargs):
|
||||
return self._timed_call('fetchone',args,kwargs)
|
||||
|
||||
def fetchall(self,*args,**kwargs):
|
||||
return self._timed_call('fetchall',args,kwargs)
|
||||
|
||||
def execute(self, operation, parameters=()):
|
||||
if self.debug:
|
||||
sys.stderr.write(_quoteparams(operation,parameters))
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.flush()
|
||||
start = time.time()
|
||||
ret = self.cursor.execute(operation, parameters)
|
||||
if self.debug:
|
||||
sys.stderr.write("Execute operation completed in %.4f seconds\n" %
|
||||
(time.time() - start))
|
||||
sys.stderr.flush()
|
||||
return ret
|
||||
|
||||
|
||||
## Functions ##
|
||||
def provideDBopts(**opts):
|
||||
global _DBopts
|
||||
if _DBopts is None:
|
||||
_DBopts = opts
|
||||
|
||||
def setDBopts(**opts):
|
||||
global _DBopts
|
||||
_DBopts = opts
|
||||
|
||||
def getDBopts():
|
||||
return _DBopts
|
||||
|
||||
def connect(debug=False):
|
||||
global _DBconn
|
||||
if hasattr(_DBconn, 'conn'):
|
||||
# Make sure the previous transaction has been
|
||||
# closed. This is safe to call multiple times.
|
||||
conn = _DBconn.conn
|
||||
conn.rollback()
|
||||
else:
|
||||
opts = _DBopts
|
||||
if opts is None:
|
||||
opts = {}
|
||||
conn = pgdb.connect(**opts)
|
||||
# XXX test
|
||||
# return conn
|
||||
_DBconn.conn = conn
|
||||
|
||||
return DBWrapper(conn, debug)
|
||||
|
||||
if __name__ == "__main__":
|
||||
setDBopts( database = "test", user = "test")
|
||||
print "This is a Python library"
|
||||
33
koji/util.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright (c) 2005-2007 Red Hat
|
||||
#
|
||||
# Koji 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;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# This software 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 this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
import time
|
||||
import koji
|
||||
|
||||
def _changelogDate(cldate):
|
||||
return time.strftime('%a %b %d %Y', time.strptime(koji.formatTime(cldate), '%Y-%m-%d %H:%M:%S'))
|
||||
|
||||
def formatChangelog(entries):
|
||||
"""Format a list of changelog entries (dicts)
|
||||
into a string representation."""
|
||||
result = ''
|
||||
for entry in entries:
|
||||
result += """* %s %s
|
||||
%s
|
||||
|
||||
""" % (_changelogDate(entry['date']), entry['author'], entry['text'])
|
||||
|
||||
return result
|
||||
3
tests/.cvsignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*.pyc
|
||||
*.pyo
|
||||
test.py
|
||||
32
tests/runtests.py
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
"""Wrapper script for running unit tests"""
|
||||
|
||||
__version__ = "$Revision: 1.1 $"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
testDir = os.path.dirname(sys.argv[0])
|
||||
|
||||
sys.path.insert(0, os.path.abspath('%s/..' % testDir))
|
||||
|
||||
allTests = unittest.TestSuite()
|
||||
for root, dirs, files in os.walk(testDir):
|
||||
common_path = os.path.commonprefix([os.path.abspath(testDir),
|
||||
os.path.abspath(root)])
|
||||
root_path = os.path.abspath(root).replace(common_path, '').lstrip('/').replace('/', '.')
|
||||
|
||||
for test_file in [item for item in files
|
||||
if item.startswith("test_") and item.endswith(".py")]:
|
||||
if len(sys.argv) == 1 or test_file in sys.argv[1:]:
|
||||
print "adding %s..." % test_file
|
||||
test_file = test_file[:-3]
|
||||
if root_path:
|
||||
test_file = "%s.%s" % (root_path, test_file)
|
||||
suite = unittest.defaultTestLoader.loadTestsFromName(test_file)
|
||||
allTests.addTests(suite._tests)
|
||||
|
||||
unittest.TextTestRunner(verbosity=2).run(allTests)
|
||||
67
tests/test___init__.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
"""Test the __init__.py module"""
|
||||
|
||||
import koji
|
||||
import unittest
|
||||
|
||||
class INITTestCase(unittest.TestCase):
|
||||
"""Main test case container"""
|
||||
|
||||
def test_parse_NVR(self):
|
||||
"""Test the parse_NVR method"""
|
||||
|
||||
self.assertRaises(AttributeError, koji.parse_NVR, None)
|
||||
self.assertRaises(AttributeError, koji.parse_NVR, 1)
|
||||
self.assertRaises(AttributeError, koji.parse_NVR, {})
|
||||
self.assertRaises(AttributeError, koji.parse_NVR, [])
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVR, "")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVR, "foo")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVR, "foo-1")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVR, "foo-1-")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVR, "foo--1")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVR, "--1")
|
||||
ret = koji.parse_NVR("foo-1-2")
|
||||
self.assertEqual(ret['name'], "foo")
|
||||
self.assertEqual(ret['version'], "1")
|
||||
self.assertEqual(ret['release'], "2")
|
||||
self.assertEqual(ret['epoch'], "")
|
||||
ret = koji.parse_NVR("12:foo-1-2")
|
||||
self.assertEqual(ret['name'], "foo")
|
||||
self.assertEqual(ret['version'], "1")
|
||||
self.assertEqual(ret['release'], "2")
|
||||
self.assertEqual(ret['epoch'], "12")
|
||||
|
||||
def test_parse_NVRA(self):
|
||||
"""Test the parse_NVRA method"""
|
||||
|
||||
self.assertRaises(AttributeError, koji.parse_NVRA, None)
|
||||
self.assertRaises(AttributeError, koji.parse_NVRA, 1)
|
||||
self.assertRaises(AttributeError, koji.parse_NVRA, {})
|
||||
self.assertRaises(AttributeError, koji.parse_NVRA, [])
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "foo")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "foo-1")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "foo-1-")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "foo--1")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "--1")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "foo-1-1")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "foo-1-1.")
|
||||
self.assertRaises(koji.GenericError, koji.parse_NVRA, "foo-1.-1")
|
||||
ret = koji.parse_NVRA("foo-1-2.i386")
|
||||
self.assertEqual(ret['name'], "foo")
|
||||
self.assertEqual(ret['version'], "1")
|
||||
self.assertEqual(ret['release'], "2")
|
||||
self.assertEqual(ret['epoch'], "")
|
||||
self.assertEqual(ret['arch'], "i386")
|
||||
self.assertEqual(ret['src'], False)
|
||||
ret = koji.parse_NVRA("12:foo-1-2.src")
|
||||
self.assertEqual(ret['name'], "foo")
|
||||
self.assertEqual(ret['version'], "1")
|
||||
self.assertEqual(ret['release'], "2")
|
||||
self.assertEqual(ret['epoch'], "12")
|
||||
self.assertEqual(ret['arch'], "src")
|
||||
self.assertEqual(ret['src'], True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
24
util/Makefile
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
BINFILES = kojira
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
mkdir -p $(DESTDIR)/usr/sbin
|
||||
install -m 755 $(BINFILES) $(DESTDIR)/usr/sbin
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/rc.d/init.d
|
||||
install -m 755 kojira.init $(DESTDIR)/etc/rc.d/init.d/kojira
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/sysconfig
|
||||
install -m 644 kojira.sysconfig $(DESTDIR)/etc/sysconfig/kojira
|
||||
|
||||
install -m 644 kojira.conf $(DESTDIR)/etc/kojira.conf
|
||||
487
util/kojira
Executable file
|
|
@ -0,0 +1,487 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Koji Repository Administrator (kojira)
|
||||
# Copyright (c) 2005-2007 Red Hat
|
||||
#
|
||||
# Koji 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;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# This software 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 this software; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Mike McLean <mikem@redhat.com>
|
||||
|
||||
try:
|
||||
import krbV
|
||||
except ImportError:
|
||||
pass
|
||||
import sys
|
||||
import os
|
||||
import koji
|
||||
from optparse import OptionParser
|
||||
from ConfigParser import ConfigParser
|
||||
import logging
|
||||
import logging.handlers
|
||||
import pprint
|
||||
import signal
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
||||
|
||||
def safe_rmtree(path, strict=True):
|
||||
logger = logging.getLogger("koji.repo")
|
||||
#safe remove: with -xdev the find cmd will not cross filesystems
|
||||
# (though it will cross bind mounts from the same filesystem)
|
||||
if not os.path.exists(path):
|
||||
logger.debug("No such path: %s" % path)
|
||||
return
|
||||
#first rm -f non-directories
|
||||
logger.debug('Removing files under %s' % path)
|
||||
rv = os.system("find '%s' -xdev \\! -type d -print0 |xargs -0 rm -f" % path)
|
||||
msg = 'file removal failed (code %r) for %s' % (rv,path)
|
||||
if rv != 0:
|
||||
logger.warn(msg)
|
||||
if strict:
|
||||
raise koji.GenericError, msg
|
||||
else:
|
||||
return rv
|
||||
#them rmdir directories
|
||||
#with -depth, we start at the bottom and work up
|
||||
logger.debug('Removing directories under %s' % path)
|
||||
rv = os.system("find '%s' -xdev -depth -type d -print0 |xargs -0 rmdir" % path)
|
||||
msg = 'dir removal failed (code %r) for %s' % (rv,path)
|
||||
if rv != 0:
|
||||
logger.warn(msg)
|
||||
if strict:
|
||||
raise koji.GenericError, msg
|
||||
return rv
|
||||
|
||||
class ManagedRepo(object):
|
||||
|
||||
def __init__(self, data):
|
||||
self.logger = logging.getLogger("koji.repo")
|
||||
self.current = True
|
||||
self.repo_id = data['id']
|
||||
self.event_id = data['create_event']
|
||||
self.event_ts = data['create_ts']
|
||||
self.tag_id = data['tag_id']
|
||||
self.state = data['state']
|
||||
order = session.getFullInheritance(self.tag_id, event=self.event_id)
|
||||
#order may contain same tag more than once
|
||||
tags = {self.tag_id : 1}
|
||||
for x in order:
|
||||
tags[x['parent_id']] = 1
|
||||
self.taglist = tags.keys()
|
||||
|
||||
def expire(self):
|
||||
"""Mark the repo expired"""
|
||||
if self.state == koji.REPO_EXPIRED:
|
||||
return
|
||||
elif self.state == koji.REPO_DELETED:
|
||||
raise GenericError, "Repo already deleted"
|
||||
self.logger.info("Expiring repo %s.." % self.repo_id)
|
||||
session.repoExpire(self.repo_id)
|
||||
self.state = koji.REPO_EXPIRED
|
||||
|
||||
def expired(self):
|
||||
return self.state == koji.REPO_EXPIRED
|
||||
|
||||
def pending(self, timeout=180):
|
||||
"""Determine if repo generation appears to be in progress and not already obsolete"""
|
||||
if self.state != koji.REPO_INIT:
|
||||
return False
|
||||
age = time.time() - self.event_ts
|
||||
return self.isCurrent(ignore_state=True) and age < timeout
|
||||
|
||||
def stale(self):
|
||||
"""Determine if repo seems stale
|
||||
|
||||
By stale, we mean:
|
||||
- state=INIT
|
||||
- timestamp really, really old
|
||||
"""
|
||||
timeout = 36000
|
||||
#XXX - config
|
||||
if self.state != koji.REPO_INIT:
|
||||
return False
|
||||
age = time.time() - self.event_ts
|
||||
return age > timeout
|
||||
|
||||
def tryDelete(self):
|
||||
"""Remove the repo from disk, if possible"""
|
||||
age = time.time() - self.event_ts
|
||||
if age < options.deleted_repo_lifetime:
|
||||
return False
|
||||
self.logger.debug("Attempting to delete repo %s.." % self.repo_id)
|
||||
if self.state != koji.REPO_EXPIRED:
|
||||
raise GenericError, "Repo not expired"
|
||||
if session.repoDelete(self.repo_id) > 0:
|
||||
#cannot delete, we are referenced by a buildroot
|
||||
self.logger.debug("Cannot delete repo %s, still referenced" % self.repo_id)
|
||||
return False
|
||||
self.logger.info("Deleted repo %s" % self.repo_id)
|
||||
self.state = koji.REPO_DELETED
|
||||
tag_name = session.getTag(self.tag_id)['name']
|
||||
path = pathinfo.repo(self.repo_id, tag_name)
|
||||
safe_rmtree(path, strict=False)
|
||||
return True
|
||||
|
||||
def ready(self):
|
||||
return self.state == koji.REPO_READY
|
||||
|
||||
def deleted(self):
|
||||
return self.state == koji.REPO_DELETED
|
||||
|
||||
def problem(self):
|
||||
return self.state == koji.REPO_PROBLEM
|
||||
|
||||
def isCurrent(self, ignore_state=False):
|
||||
if not self.current:
|
||||
# no point in checking again
|
||||
return False
|
||||
if not ignore_state and self.state != koji.REPO_READY:
|
||||
#also no point in checking
|
||||
return False
|
||||
self.logger.debug("Checking for changes: %r" % self.taglist)
|
||||
if session.tagChangedSinceEvent(self.event_id,self.taglist):
|
||||
self.logger.debug("Tag data has changed since event %r" % self.event_id)
|
||||
self.current = False
|
||||
else:
|
||||
self.logger.debug("No tag changes since event %r" % self.event_id)
|
||||
return self.current
|
||||
|
||||
|
||||
class RepoManager(object):
|
||||
|
||||
def __init__(self):
|
||||
self.repos = {}
|
||||
self.tasks = {}
|
||||
self.logger = logging.getLogger("koji.repo.manager")
|
||||
|
||||
def printState(self):
|
||||
for repo in self.repos.itervalues():
|
||||
self.logger.debug("repo %s: tag=%s, state=%s"
|
||||
% (repo.repo_id, repo.tag_id, koji.REPO_STATES[repo.state]))
|
||||
for tag_id, task_id in self.tasks.iteritems():
|
||||
self.logger.debug("task %s for tag %s" % (task_id, tag_id))
|
||||
|
||||
def readCurrentRepos(self):
|
||||
self.logger.debug("Reading current repo data")
|
||||
repodata = session.getActiveRepos()
|
||||
self.logger.debug("Repo data: %r" % repodata)
|
||||
for data in repodata:
|
||||
repo_id = data['id']
|
||||
repo = self.repos.get(repo_id)
|
||||
if repo:
|
||||
#we're already tracking it
|
||||
if repo.state != data['state']:
|
||||
self.logger.info('State changed for repo %s: %s -> %s'
|
||||
%(repo_id, koji.REPO_STATES[repo.state], koji.REPO_STATES[data['state']]))
|
||||
repo.state = data['state']
|
||||
else:
|
||||
self.logger.info('Found repo %s, state=%s'
|
||||
%(repo_id, koji.REPO_STATES[data['state']]))
|
||||
self.repos[repo_id] = ManagedRepo(data)
|
||||
|
||||
def pruneLocalRepos(self):
|
||||
"""Scan filesystem for repos and remove any deleted ones
|
||||
|
||||
Also, warn about any oddities"""
|
||||
self.logger.debug("Scanning filesystem for repos")
|
||||
topdir = "%s/repos" % pathinfo.topdir
|
||||
count = 0
|
||||
for tag in os.listdir(topdir):
|
||||
tagdir = "%s/%s" % (topdir, tag)
|
||||
if not os.path.isdir(tagdir):
|
||||
continue
|
||||
taginfo = session.getTag(tag)
|
||||
if taginfo is None:
|
||||
self.logger.warn("Unexpected directory (no such tag): %s" % tagdir)
|
||||
continue
|
||||
for repo_id in os.listdir(tagdir):
|
||||
if count >= options.prune_batch_size:
|
||||
#this keeps us from spending too much time on this at one time
|
||||
return
|
||||
repodir = "%s/%s" % (tagdir, repo_id)
|
||||
if not os.path.isdir(repodir):
|
||||
continue
|
||||
try:
|
||||
repo_id = int(repo_id)
|
||||
except ValueError:
|
||||
continue
|
||||
if self.repos.has_key(repo_id):
|
||||
#we're already managing it, no need to deal with it here
|
||||
continue
|
||||
rinfo = session.repoInfo(repo_id)
|
||||
if rinfo is None:
|
||||
try:
|
||||
age = time.time() - os.stat(repodir).st_mtime
|
||||
except OSError:
|
||||
#just in case something deletes the repo out from under us
|
||||
continue
|
||||
if age > 36000:
|
||||
self.logger.warn("Unexpected directory (no such repo): %s" % repodir)
|
||||
continue
|
||||
if rinfo['tag_name'] != taginfo['name']:
|
||||
self.logger.warn("Tag name mismatch: %s" % repodir)
|
||||
continue
|
||||
if rinfo['state'] in (koji.REPO_DELETED, koji.REPO_PROBLEM):
|
||||
age = time.time() - rinfo['create_ts']
|
||||
if age > options.deleted_repo_lifetime:
|
||||
count += 1
|
||||
logger.info("Removing stray repo (state=%s): %s" % (koji.REPO_STATES[rinfo['state']], repodir))
|
||||
safe_rmtree(repodir, strict=False)
|
||||
pass
|
||||
|
||||
def updateRepos(self):
|
||||
#check on tasks
|
||||
for tag_id, task_id in self.tasks.items():
|
||||
tinfo = session.getTaskInfo(task_id)
|
||||
tstate = koji.TASK_STATES[tinfo['state']]
|
||||
if tstate == 'CLOSED':
|
||||
self.logger.info("Finished: newRepo task %s for tag %s" % (task_id, tag_id))
|
||||
del self.tasks[tag_id]
|
||||
elif tstate in ('CANCELED', 'FAILED'):
|
||||
self.logger.info("Problem: newRepo task %s for tag %s is %s" % (task_id, tag_id, tstate))
|
||||
del self.tasks[tag_id]
|
||||
#TODO [?] - implement a timeout for active tasks?
|
||||
self.logger.debug("Current tasks: %r" % self.tasks)
|
||||
if len(self.tasks) >= options.max_repo_tasks:
|
||||
self.logger.info("Maximum number of repo tasks reached.")
|
||||
return
|
||||
self.logger.debug("Updating repos")
|
||||
self.readCurrentRepos()
|
||||
#check for stale repos
|
||||
for repo in self.repos.values():
|
||||
if repo.stale():
|
||||
repo.expire()
|
||||
#find out which tags require repos
|
||||
tags = {}
|
||||
for target in session.getBuildTargets():
|
||||
tag_id = target['build_tag']
|
||||
tags[tag_id] = 1
|
||||
#index repos by tag
|
||||
tag_repos = {}
|
||||
for repo in self.repos.values():
|
||||
tag_repos.setdefault(repo.tag_id, []).append(repo)
|
||||
self.logger.debug("Needed tags: %r" % tags.keys())
|
||||
self.logger.debug("Current tags: %r" % tag_repos.keys())
|
||||
|
||||
#we need to determine:
|
||||
# - which tags need a new repo
|
||||
# - if any repos seem to be broken
|
||||
for tag_id in tags.iterkeys():
|
||||
covered = False
|
||||
for repo in tag_repos.get(tag_id,[]):
|
||||
if repo.isCurrent():
|
||||
covered = True
|
||||
break
|
||||
elif repo.pending():
|
||||
#one on the way
|
||||
covered = True
|
||||
break
|
||||
if covered:
|
||||
continue
|
||||
if self.tasks.has_key(tag_id):
|
||||
#repo creation in progress
|
||||
#TODO - implement a timeout
|
||||
continue
|
||||
#tag still appears to be uncovered
|
||||
task_id = session.newRepo(tag_id)
|
||||
self.logger.info("Created newRepo task %s for tag %s" % (task_id, tag_id))
|
||||
self.tasks[tag_id] = task_id
|
||||
#some cleanup
|
||||
for tag_id, repolist in tag_repos.items():
|
||||
if not tags.has_key(tag_id):
|
||||
#repos for these tags are no longer required
|
||||
for repo in repolist:
|
||||
if repo.ready():
|
||||
repo.expire()
|
||||
for repo in repolist:
|
||||
if repo.expired():
|
||||
#try to delete
|
||||
repo.tryDelete()
|
||||
|
||||
|
||||
def main():
|
||||
repomgr = RepoManager()
|
||||
repomgr.readCurrentRepos()
|
||||
repomgr.pruneLocalRepos()
|
||||
logger.info("Entering main loop")
|
||||
while True:
|
||||
try:
|
||||
repomgr.updateRepos()
|
||||
repomgr.printState()
|
||||
repomgr.pruneLocalRepos()
|
||||
except KeyboardInterrupt:
|
||||
logger.warn("User exit")
|
||||
break
|
||||
except koji.AuthExpired:
|
||||
logger.warn("Session expired")
|
||||
break
|
||||
except SystemExit:
|
||||
logger.warn("Shutting down")
|
||||
break
|
||||
except:
|
||||
# log the exception and continue
|
||||
logger.error(''.join(traceback.format_exception(*sys.exc_info())))
|
||||
try:
|
||||
time.sleep(5)
|
||||
except KeyboardInterrupt:
|
||||
logger.warn("User exit")
|
||||
break
|
||||
try:
|
||||
session.logout()
|
||||
finally:
|
||||
sys.exit()
|
||||
|
||||
def _exit_signal_handler(signum, frame):
|
||||
logger.error('Exiting on signal')
|
||||
session.logout()
|
||||
sys.exit(1)
|
||||
|
||||
def get_options():
|
||||
"""process options from command line and config file"""
|
||||
# parse command line args
|
||||
parser = OptionParser("usage: %prog [opts]")
|
||||
parser.add_option("-c", "--config", dest="configFile",
|
||||
help="use alternate configuration file", metavar="FILE",
|
||||
default="/etc/kojira.conf")
|
||||
parser.add_option("--user", help="specify user")
|
||||
parser.add_option("--password", help="specify password")
|
||||
parser.add_option("--principal", help="Kerberos principal")
|
||||
parser.add_option("--keytab", help="Kerberos keytab")
|
||||
parser.add_option("-f", "--fg", dest="daemon",
|
||||
action="store_false", default=True,
|
||||
help="run in foreground")
|
||||
parser.add_option("-d", "--debug", action="store_true",
|
||||
help="show debug output")
|
||||
parser.add_option("-v", "--verbose", action="store_true",
|
||||
help="show verbose output")
|
||||
parser.add_option("--with-src", action="store_true",
|
||||
help="include srpms in repos")
|
||||
parser.add_option("--force-lock", action="store_true", default=False,
|
||||
help="force lock for exclusive session")
|
||||
parser.add_option("--debug-xmlrpc", action="store_true", default=False,
|
||||
help="show xmlrpc debug output")
|
||||
parser.add_option("--skip-main", action="store_true", default=False,
|
||||
help="don't actually run main")
|
||||
parser.add_option("--show-config", action="store_true", default=False,
|
||||
help="Show config and exit")
|
||||
parser.add_option("-s", "--server", help="URL of XMLRPC server")
|
||||
parser.add_option("--topdir", help="Specify topdir")
|
||||
parser.add_option("--logfile", help="Specify logfile")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
config = ConfigParser()
|
||||
config.read(options.configFile)
|
||||
section = 'kojira'
|
||||
for x in config.sections():
|
||||
if x != section:
|
||||
quit('invalid section found in config file: %s' % x)
|
||||
defaults = {'with_src': False,
|
||||
'verbose': False,
|
||||
'debug': False,
|
||||
'topdir': '/mnt/koji',
|
||||
'server': None,
|
||||
'logfile': '/var/log/kojira.log',
|
||||
'principal': None,
|
||||
'keytab': None,
|
||||
'prune_batch_size': 4,
|
||||
'max_repo_tasks' : 10,
|
||||
'deleted_repo_lifetime': 7*24*3600,
|
||||
}
|
||||
if config.has_section(section):
|
||||
int_opts = ('prune_batch_size', 'deleted_repo_lifetime', 'max_repo_tasks')
|
||||
str_opts = ('topdir','server','user','password','logfile', 'principal', 'keytab')
|
||||
bool_opts = ('with_src','verbose','debug')
|
||||
for name in config.options(section):
|
||||
if name in int_opts:
|
||||
defaults[name] = config.getint(section, name)
|
||||
elif name in str_opts:
|
||||
defaults[name] = config.get(section, name)
|
||||
elif name in bool_opts:
|
||||
defaults[name] = config.getboolean(section, name)
|
||||
else:
|
||||
quit("unknown config option: %s" % name)
|
||||
for name, value in defaults.items():
|
||||
if getattr(options, name, None) is None:
|
||||
setattr(options, name, value)
|
||||
if options.logfile in ('','None','none'):
|
||||
options.logfile = None
|
||||
return options
|
||||
|
||||
def quit(msg=None, code=1):
|
||||
if msg:
|
||||
logging.getLogger("koji.repo").error(msg)
|
||||
sys.stderr.write('%s\n' % msg)
|
||||
sys.stderr.flush()
|
||||
sys.exit(code)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
options = get_options()
|
||||
topdir = getattr(options,'topdir',None)
|
||||
pathinfo = koji.PathInfo(topdir)
|
||||
if options.show_config:
|
||||
pprint.pprint(options.__dict__)
|
||||
sys.exit()
|
||||
if options.logfile:
|
||||
if not os.path.exists(options.logfile):
|
||||
try:
|
||||
logfile = open(options.logfile, "w")
|
||||
logfile.close()
|
||||
except:
|
||||
sys.stderr.write("Cannot create logfile: %s\n" % options.logfile)
|
||||
sys.exit(1)
|
||||
if not os.access(options.logfile,os.W_OK):
|
||||
sys.stderr.write("Cannot write to logfile: %s\n" % options.logfile)
|
||||
sys.exit(1)
|
||||
koji.add_file_logger("koji", "/var/log/kojira.log")
|
||||
koji.add_sys_logger("koji")
|
||||
#note we're setting logging for koji.*
|
||||
logger = logging.getLogger("koji")
|
||||
if options.debug:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
elif options.verbose:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
session_opts = {}
|
||||
for k in ('user', 'password', 'debug_xmlrpc', 'debug'):
|
||||
session_opts[k] = getattr(options,k)
|
||||
session = koji.ClientSession(options.server,session_opts)
|
||||
if options.user:
|
||||
#authenticate using user/password
|
||||
session.login()
|
||||
elif sys.modules.has_key('krbV') and options.principal and options.keytab:
|
||||
session.krb_login(options.principal, options.keytab)
|
||||
#get an exclusive session
|
||||
try:
|
||||
session.exclusiveSession(force=options.force_lock)
|
||||
except koji.AuthLockError:
|
||||
quit("Error: Unable to get lock. Trying using --force-lock")
|
||||
if not session.logged_in:
|
||||
quit("Error: Unknown login error")
|
||||
if not session.logged_in:
|
||||
print "Error: unable to log in"
|
||||
sys.exit(1)
|
||||
if options.skip_main:
|
||||
sys.exit()
|
||||
elif options.daemon:
|
||||
koji.daemonize()
|
||||
else:
|
||||
koji.add_stderr_logger("koji")
|
||||
main()
|
||||
|
||||
|
||||
22
util/kojira.conf
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[kojira]
|
||||
; For user/pass authentication
|
||||
; user=kojira
|
||||
; password=kojira
|
||||
|
||||
; For Kerberos authentication
|
||||
; the principal to connect with
|
||||
principal=koji/repo@EXAMPLE.COM
|
||||
; The location of the keytab for the principal above
|
||||
keytab=/etc/kojira.keytab
|
||||
|
||||
; The URL for the koji hub server
|
||||
server=http://hub.example.com/kojihub
|
||||
|
||||
; The directory containing the repos/ directory
|
||||
topdir=/mnt/koji
|
||||
|
||||
; Logfile
|
||||
logfile=/var/log/kojira.log
|
||||
|
||||
; Include srpms in repos? (not needed for normal operation)
|
||||
with_src=no
|
||||
81
util/kojira.init
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#! /bin/sh
|
||||
#
|
||||
# kojira Start/Stop kojira
|
||||
#
|
||||
# chkconfig: 345 99 99
|
||||
# description: koji repo administrator
|
||||
# processname: kojira
|
||||
|
||||
# This is an interactive program, we need the current locale
|
||||
|
||||
# Source function library.
|
||||
. /etc/init.d/functions
|
||||
|
||||
# Check that we're a priviledged user
|
||||
[ `id -u` = 0 ] || exit 0
|
||||
|
||||
[ -f /etc/sysconfig/kojira ] && . /etc/sysconfig/kojira
|
||||
|
||||
prog="kojira"
|
||||
|
||||
# Check that networking is up.
|
||||
if [ "$NETWORKING" = "no" ]
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ -f /usr/sbin/kojira ] || exit 0
|
||||
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n $"Starting $prog: "
|
||||
cd /
|
||||
ARGS=""
|
||||
[ "$FORCE_LOCK" == "Y" ] && ARGS="$ARGS --force-lock"
|
||||
[ "$KOJIRA_DEBUG" == "Y" ] && ARGS="$ARGS --debug"
|
||||
[ "$KOJIRA_VERBOSE" == "Y" ] && ARGS="$ARGS --verbose"
|
||||
daemon /usr/sbin/kojira $ARGS
|
||||
RETVAL=$?
|
||||
echo
|
||||
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/kojira
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping $prog: "
|
||||
killproc kojira
|
||||
RETVAL=$?
|
||||
echo
|
||||
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/kojira
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
# See how we were called.
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status $prog
|
||||
;;
|
||||
restart|reload)
|
||||
restart
|
||||
;;
|
||||
condrestart)
|
||||
[ -f /var/lock/subsys/kojira ] && restart || :
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit $?
|
||||
3
util/kojira.sysconfig
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
FORCE_LOCK=Y
|
||||
KOJIRA_DEBUG=N
|
||||
KOJIRA_VERBOSE=Y
|
||||
20
www/Makefile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
SUBDIRS = kojiweb conf lib static
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
for d in $(SUBDIRS); do make -s -C $$d clean; done
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/var/www/koji-web
|
||||
|
||||
for d in $(SUBDIRS); do make DESTDIR=$(DESTDIR) \
|
||||
-C $$d install; [ $$? = 0 ] || exit 1; done
|
||||
16
www/conf/Makefile
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
for d in $(SUBDIRS); do make -s -C $$d clean; done
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/httpd/conf.d
|
||||
install -m 644 kojiweb.conf $(DESTDIR)/etc/httpd/conf.d/kojiweb.conf
|
||||
45
www/conf/kojiweb.conf
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
Alias /koji "/var/www/koji-web/scripts/"
|
||||
|
||||
<Directory "/var/www/koji-web/scripts/">
|
||||
# Config for the publisher handler
|
||||
SetHandler mod_python
|
||||
PythonHandler mod_python.publisher
|
||||
|
||||
# General settings
|
||||
PythonDebug On
|
||||
PythonOption KojiHubURL http://hub.example.com/kojihub
|
||||
PythonOption KojiWebURL http://www.example.com/koji
|
||||
PythonOption KojiPackagesURL http://server.example.com/mnt/koji/packages
|
||||
PythonOption WebPrincipal koji/web@EXAMPLE.COM
|
||||
PythonOption WebKeytab /etc/httpd.keytab
|
||||
PythonOption WebCCache /var/tmp/kojiweb.ccache
|
||||
PythonOption LoginTimeout 72
|
||||
# This must be changed before deployment
|
||||
PythonOption Secret CHANGE_ME
|
||||
PythonPath "sys.path + ['/var/www/koji-web/lib']"
|
||||
PythonCleanupHandler kojiweb.handlers::cleanup
|
||||
PythonAutoReload Off
|
||||
</Directory>
|
||||
|
||||
# Authentication settings
|
||||
<Location /koji/login>
|
||||
AuthType Kerberos
|
||||
AuthName "Koji Web UI"
|
||||
KrbMethodNegotiate on
|
||||
KrbMethodK5Passwd off
|
||||
KrbServiceName HTTP
|
||||
KrbAuthRealm EXAMPLE.COM
|
||||
Krb5Keytab /etc/httpd.keytab
|
||||
KrbSaveCredentials off
|
||||
Require valid-user
|
||||
ErrorDocument 401 /koji-static/errors/unauthorized.html
|
||||
</Location>
|
||||
|
||||
Alias /koji-static/ "/var/www/koji-web/static/"
|
||||
|
||||
<Directory "/var/www/koji-web/static/">
|
||||
Options None
|
||||
AllowOverride None
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
78
www/docs/negotiate/index.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Configuring Firefox (and Mozilla) for Negotiate Authentication</title>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Configuring Firefox (and Mozilla) for Negotiate Authentication</h3>
|
||||
<p>
|
||||
Before Firefox and Mozilla can authenticate to a server using "Negotiate"
|
||||
authentication, a couple of configuration changes must be made.
|
||||
</p>
|
||||
<p>
|
||||
Type <strong>about:config</strong> into the location bar, to bring
|
||||
up the configuration page. Type <strong>negotiate</strong> into the <em>Filter:</em> box, to restrict
|
||||
the listing to the configuration options we're interested in.
|
||||
<br/>
|
||||
Change <strong>network.negotiate-auth.trusted-uris</strong> to the domain you want to authenticate against,
|
||||
e.g. <code>.example.com</code>. You can leave <strong>network.negotiate-auth.delegation-uris</strong>
|
||||
blank, as it enables Kerberos ticket passing, which is not required. If you do not see those two config
|
||||
options listed, your version of Firefox or Mozilla may be too old to support Negotiate authentication, and
|
||||
you should consider upgrading.
|
||||
<br/>
|
||||
<strong>FC5 Update:</strong> Firefox and Mozilla on FC5 are attempting to load a library by its unversioned name, which is
|
||||
not installed by default. A fix has been checked-in upstream, but in the meantime, the workaround is to set
|
||||
<strong>network.negotiate-auth.gsslib</strong> to <code>libgssapi_krb5.so.2</code>.
|
||||
<br/>
|
||||
<strong>FC5 Update Update:</strong> If you are using the most recent Firefox or Mozilla, this workaround is
|
||||
no longer necessary.
|
||||
</p>
|
||||
<p>
|
||||
Now, make sure you have Kerberos tickets. Typing <em>kinit</em> in a shell should allow you to
|
||||
retrieve Kerberos tickets. <em>klist</em> will show you what tickets you have.
|
||||
<br/>
|
||||
</p>
|
||||
<p>
|
||||
Now, if you visit a Kerberos-authenticated website in the .example.com domain, you should be logged in
|
||||
automatically, without having to type in your password.
|
||||
</p>
|
||||
<p>
|
||||
<h4>Troubleshooting</h4>
|
||||
If you have followed the configuration steps and Negotiate authentication is not working, you can
|
||||
turn on verbose logging of the authentication process, and potentially find the cause of the problem.
|
||||
Exit Firefox or Mozilla. In a shell, type the following commands:
|
||||
<pre>
|
||||
export NSPR_LOG_MODULES=negotiateauth:5
|
||||
export NSPR_LOG_FILE=/tmp/moz.log
|
||||
</pre>
|
||||
Then restart Firefox or Mozilla from that shell, and visit the website you were unable to authenticate
|
||||
to earlier. Information will be logged to <em>/tmp/moz.log</em>, which may give a clue to the problem.
|
||||
For example:
|
||||
<pre>
|
||||
-1208550944[90039d0]: entering nsNegotiateAuth::GetNextToken()
|
||||
-1208550944[90039d0]: gss_init_sec_context() failed: Miscellaneous failure
|
||||
No credentials cache found
|
||||
|
||||
</pre>
|
||||
means that you do not have Kerberos tickets, and need to run <em>kinit</em>.
|
||||
<br/>
|
||||
<br/>
|
||||
If you are able to <em>kinit</em> successfully from your machine but you are unable to authenticate, and you see
|
||||
something like this in your log:
|
||||
<pre>
|
||||
-1208994096[8d683d8]: entering nsAuthGSSAPI::GetNextToken()
|
||||
-1208994096[8d683d8]: gss_init_sec_context() failed: Miscellaneous failure
|
||||
Server not found in Kerberos database
|
||||
</pre>
|
||||
it generally indicates a Kerberos configuration problem. Make sure you have the following in the
|
||||
<code>[domain_realm]</code> section of <em>/etc/krb5.conf</em>:
|
||||
<pre>
|
||||
.example.com = EXAMPLE.COM
|
||||
example.com = EXAMPLE.COM
|
||||
</pre>
|
||||
If nothing is showing up in the log it's possible that you're behind a proxy, and that proxy is stripping off
|
||||
the HTTP headers required for Negotiate authentication. As a workaround, you can try to connect to the
|
||||
server via <code>https</code> instead, which will allow the request to pass through unmodified. Then proceed to
|
||||
debug using the log, as described above.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
24
www/kojiweb/Makefile
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
SUBDIRS = includes
|
||||
|
||||
SERVERDIR = /var/www/koji-web/scripts
|
||||
FILES = $(wildcard *.py *.chtml)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
for d in $(SUBDIRS); do make -s -C $$d clean; done
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/$(SERVERDIR)
|
||||
install -m 644 $(FILES) $(DESTDIR)/$(SERVERDIR)
|
||||
|
||||
for d in $(SUBDIRS); do make DESTDIR=$(DESTDIR)/$(SERVERDIR) \
|
||||
-C $$d install; [ $$? = 0 ] || exit 1; done
|
||||
103
www/kojiweb/buildinfo.chtml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#import koji
|
||||
#import koji.util
|
||||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for build $koji.buildLabel($build)</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th><td>$build.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Package Name</th><td><a href="packageinfo?packageID=$build.package_id">$build.package_name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Version</th><td>$build.version</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Release</th><td>$build.release</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Epoch</th><td>$build.epoch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Built by</th><td><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Started</th><td>$util.formatTimeLong($build.creation_time)</td>
|
||||
</tr>
|
||||
#if $build.state == $koji.BUILD_STATES.BUILDING
|
||||
#if $estCompletion
|
||||
<tr>
|
||||
<th>Est. Completion</th><td>$util.formatTimeLong($estCompletion)</td>
|
||||
</tr>
|
||||
#end if
|
||||
#else
|
||||
<tr>
|
||||
<th>Completed</th><td>$util.formatTimeLong($build.completion_time)</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
<th>State</th>
|
||||
<td class="$stateName">$stateName
|
||||
#if $build.state == $koji.BUILD_STATES.BUILDING
|
||||
#if $currentUser and ('admin' in $perms or $build.owner_id == $currentUser.id)
|
||||
<span class="adminLink">(<a href="cancelbuild?buildID=$build.id">cancel</a>)</span>
|
||||
#end if
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
#if $task
|
||||
<tr>
|
||||
<th>Task</th><td><a href="taskinfo?taskID=$task.id" class="task$util.taskState($task.state)">$koji.taskLabel($task)</a></td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Tags</th>
|
||||
<td class="container">
|
||||
#if $len($tags) > 0
|
||||
<table class="nested">
|
||||
#for $tag in $tags
|
||||
<tr>
|
||||
<td><a href="taginfo?tagID=$tag.id">$tag.name</a></td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No tags
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>RPMs</th>
|
||||
<td class="container">
|
||||
#if $len($rpms) > 0
|
||||
<table class="nested">
|
||||
#for $rpm in $rpms
|
||||
<tr>
|
||||
#set $rpmfile = '%(name)s-%(version)s-%(release)s.%(arch)s.rpm' % $rpm
|
||||
<td>$rpmfile (<a href="rpminfo?rpmID=$rpm.id">info</a>) (<a href="$downloadBase/$rpm.name/$rpm.version/$rpm.release/$rpm.arch/$rpmfile">download</a>)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No RPMs
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
#if $changelog
|
||||
<tr>
|
||||
<th>Changelog</th>
|
||||
<td class="changelog">
|
||||
<pre>
|
||||
#echo $util.escapeHTML($koji.util.formatChangelog($changelog))
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
50
www/kojiweb/buildrootinfo.chtml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for buildroot $buildroot.tag_name-$buildroot.id-$buildroot.repo_id</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Host</th><td><a href="hostinfo?hostID=$buildroot.host_id">$buildroot.host_name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arch</th><td>$buildroot.arch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th><td>$buildroot.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>State</th><td>$util.imageTag($util.brStateName($buildroot.state))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
#set $clean = $buildroot.dirtyness and 'yes' or 'no'
|
||||
<th>Clean?</th><td class="$clean">$util.imageTag($clean)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created</th><td>$util.formatTimeLong($buildroot.create_event_time)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Retired</th><td>$util.formatTimeLong($buildroot.retire_event_time)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Repo ID</th><td>$buildroot.repo_id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Repo Tag</th><td><a href="taginfo?tagID=$buildroot.tag_id">$buildroot.tag_name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Repo State</th><td>$util.imageTag($util.repoStateName($buildroot.repo_state))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Repo Created</th><td>$util.formatTimeLong($buildroot.repo_create_event_time)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2"><a href="rpmlist?buildrootID=$buildroot.id&type=component" title="RPMs that are installed into this buildroot when building packages">Component RPMs</a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2"><a href="rpmlist?buildrootID=$buildroot.id&type=built" title="RPMs that have been built in this buildroot">Built RPMs</a></th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
133
www/kojiweb/builds.chtml
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#import koji
|
||||
#from kojiweb import util
|
||||
|
||||
#def toggleLink($comp, $label, $link)
|
||||
#if $comp
|
||||
<strong>$label</strong>#slurp
|
||||
#else
|
||||
<a href="$link">$label</a>#slurp
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>#if $state != None then $util.stateName($state).capitalize() else ''# Builds#if $prefix then ' starting with "%s"' % $prefix else ''##if $user then ' by <a href="userinfo?userID=%i">%s</a>' % ($user.id, $user.name) else ''##if $tag then ' in tag <a href="taginfo?tagID=%i">%s</a>' % ($tag.id, $tag.name) else ''#</h4>
|
||||
|
||||
<table class="data-list">
|
||||
#if $tag
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
#if $inherited
|
||||
<a href="builds?inherited=0$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix')">Hide inherited builds</a>
|
||||
#else
|
||||
<a href="builds?${util.passthrough($self, 'userID', 'tagID', 'order', 'prefix')[1:]}">Show inherited builds</a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td colspan="#if $tag then '6' else '5'#">
|
||||
<strong>State</strong>:
|
||||
<select name="state" class="filterlist" onchange="javascript: window.location = 'builds?state=' + this.value + '$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited')';">
|
||||
<option value="all">all</option>
|
||||
#for $stateOpt in ['BUILDING', 'COMPLETE', 'FAILED', 'CANCELED']
|
||||
<option value="$koji.BUILD_STATES[$stateOpt]" #if $state == $koji.BUILD_STATES[$stateOpt] then 'selected="selected"' else ''#>$stateOpt.lower()</option>
|
||||
#end for
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="charlist" colspan="#if $tag then '6' else '5'#">
|
||||
#for $char in $chars
|
||||
#if $prefix == $char
|
||||
<strong>$char</strong>
|
||||
#else
|
||||
<a href="builds?prefix=$char$util.passthrough($self, 'userID', 'tagID', 'order', 'inherited', 'state')">$char</a>
|
||||
#end if
|
||||
|
|
||||
#end for
|
||||
#if $prefix
|
||||
<a href="builds?${util.passthrough($self, 'userID', 'tagID', 'order', 'inherited', 'state')[1:]}">all</a>
|
||||
#else
|
||||
<strong>all</strong>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paginate" colspan="#if $tag then '6' else '5'#">
|
||||
#if $len($buildPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'builds?start=' + this.value * $buildRange + '$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited', 'state')';">
|
||||
#for $pageNum in $buildPages
|
||||
<option value="$pageNum"#if $pageNum == $buildCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $buildStart > 0
|
||||
<a href="builds?start=#echo $buildStart - $buildRange #$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited', 'state')"><<<</a>
|
||||
#end if
|
||||
#if $totalBuilds != 0
|
||||
<strong>Builds #echo $buildStart + 1 # through #echo $buildStart + $buildCount # of $totalBuilds</strong>
|
||||
#end if
|
||||
#if $buildStart + $buildCount < $totalBuilds
|
||||
<a href="builds?start=#echo $buildStart + $buildRange#$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited', 'state')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="builds?order=$util.toggleOrder($self, 'build_id')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited', 'state')">ID</a> $util.sortImage($self, 'build_id')</th>
|
||||
<th><a href="builds?order=$util.toggleOrder($self, 'nvr')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited', 'state')">NVR</a> $util.sortImage($self, 'nvr')</th>
|
||||
#if $tag
|
||||
<th><a href="builds?order=$util.toggleOrder($self, 'tag_name')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited', 'state')">Tag</a> $util.sortImage($self, 'tag_name')</th>
|
||||
#end if
|
||||
<th><a href="builds?order=$util.toggleOrder($self, 'owner_name')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited', 'state')">Built by</a> $util.sortImage($self, 'owner_name')</th>
|
||||
<th><a href="builds?order=$util.toggleOrder($self, 'completion_time')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited', 'state')">Finished</a> $util.sortImage($self, 'completion_time')</th>
|
||||
<th><a href="builds?order=$util.toggleOrder($self, 'state')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited', 'state')">State</a> $util.sortImage($self, 'state')</th>
|
||||
</tr>
|
||||
#if $len($builds) > 0
|
||||
#for $build in $builds
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>$build.build_id</td>
|
||||
<td><a href="buildinfo?buildID=$build.build_id">$koji.buildLabel($build)</a></td>
|
||||
#if $tag
|
||||
<td><a href="taginfo?tagID=$build.tag_id">$build.tag_name</a></td>
|
||||
#end if
|
||||
<td><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<td>$util.formatTime($build.completion_time)</td>
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
<td class="$stateName">$util.stateImage($build.state)</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="#if $tag then '6' else '5'#">No builds</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="#if $tag then '6' else '5'#">
|
||||
#if $len($buildPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'builds?start=' + this.value * $buildRange + '$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited', 'state')';">
|
||||
#for $pageNum in $buildPages
|
||||
<option value="$pageNum"#if $pageNum == $buildCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $buildStart > 0
|
||||
<a href="builds?start=#echo $buildStart - $buildRange #$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited', 'state')"><<<</a>
|
||||
#end if
|
||||
#if $totalBuilds != 0
|
||||
<strong>Builds #echo $buildStart + 1 # through #echo $buildStart + $buildCount # of $totalBuilds</strong>
|
||||
#end if
|
||||
#if $buildStart + $buildCount < $totalBuilds
|
||||
<a href="builds?start=#echo $buildStart + $buildRange#$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited', 'state')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
55
www/kojiweb/buildsbystatus.chtml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#def printOption(value, label=None)
|
||||
#if not $label
|
||||
#set $label = $value
|
||||
#end if
|
||||
<option value="$value"#if $value == $days then ' selected' else ''#>$label</option>
|
||||
#end def
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Succeeded/Failed/Canceled Builds#if $days != -1 then ' in the last %i days' % $days else ''#</h4>
|
||||
<table class="data-list">
|
||||
<tr style="text-align: left">
|
||||
<td colspan="3">
|
||||
<form>
|
||||
Show last
|
||||
<select onchange="javascript: window.location = 'buildsbystatus?days=' + this.value;">
|
||||
$printOption(1)
|
||||
$printOption(3)
|
||||
$printOption(5)
|
||||
$printOption(7)
|
||||
$printOption(14)
|
||||
$printOption(30)
|
||||
$printOption(60)
|
||||
$printOption(90)
|
||||
$printOption(120)
|
||||
$printOption(-1, 'all')
|
||||
</select> days
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th>Type</th>
|
||||
<th>Builds</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr class="row-odd taskclosed">
|
||||
<td>Succeeded</td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $numSucceeded#" height="15" class="graphrow"/></td>
|
||||
<td>$numSucceeded</td>
|
||||
</tr>
|
||||
<tr class="row-even taskfailed">
|
||||
<td>Failed</td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $numFailed#" height="15" class="graphrow"/></td>
|
||||
<td>$numFailed</td>
|
||||
</tr>
|
||||
<tr class="row-odd taskcanceled">
|
||||
<td>Canceled</td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $numCanceled#" height="15" class="graphrow"/></td>
|
||||
<td>$numCanceled</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
99
www/kojiweb/buildsbytarget.chtml
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#def printOption(value, label=None)
|
||||
#if not $label
|
||||
#set $label = $value
|
||||
#end if
|
||||
<option value="$value"#if $value == $days then ' selected' else ''#>$label</option>
|
||||
#end def
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Builds by Target#if $days != -1 then ' in the last %i days' % $days else ''#</h4>
|
||||
<table class="data-list">
|
||||
<tr style="text-align: left">
|
||||
<td colspan="3">
|
||||
<form>
|
||||
Show last
|
||||
<select onchange="javascript: window.location = 'buildsbytarget?days=' + this.value + '$util.passthrough($self, 'order')';">
|
||||
$printOption(1)
|
||||
$printOption(3)
|
||||
$printOption(5)
|
||||
$printOption(7)
|
||||
$printOption(14)
|
||||
$printOption(30)
|
||||
$printOption(60)
|
||||
$printOption(90)
|
||||
$printOption(120)
|
||||
$printOption(-1, 'all')
|
||||
</select> days
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($targetPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'buildsbytarget?start=' + this.value * $targetRange + '$util.passthrough($self, 'days', 'order')';">
|
||||
#for $pageNum in $targetPages
|
||||
<option value="$pageNum"#if $pageNum == $targetCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $targetStart > 0
|
||||
<a href="buildsbytarget?start=#echo $targetStart - $targetRange #$util.passthrough($self, 'days', 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalTargets != 0
|
||||
<strong>Build Targets #echo $targetStart + 1 # through #echo $targetStart + $targetCount # of $totalTargets</strong>
|
||||
#end if
|
||||
#if $targetStart + $targetCount < $totalTargets
|
||||
<a href="buildsbytarget?start=#echo $targetStart + $targetRange#$util.passthrough($self, 'days', 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="buildsbytarget?order=$util.toggleOrder($self, 'name')$util.passthrough($self, 'days')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th><a href="buildsbytarget?order=$util.toggleOrder($self, 'builds')$util.passthrough($self, 'days')">Builds</a> $util.sortImage($self, 'builds')</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
#if $len($targets) > 0
|
||||
#for $target in $targets
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="buildtargetinfo?name=$target.name">$target.name</a></td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $target.builds#" height="15" class="graphrow"/></td>
|
||||
<td>$target.builds</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No builds</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($targetPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'buildsbytarget?start=' + this.value * $targetRange + '$util.passthrough($self, 'days', 'order')';">
|
||||
#for $pageNum in $targetPages
|
||||
<option value="$pageNum"#if $pageNum == $targetCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $targetStart > 0
|
||||
<a href="buildsbytarget?start=#echo $targetStart - $targetRange #$util.passthrough($self, 'days', 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalTargets != 0
|
||||
<strong>Build Targets #echo $targetStart + 1 # through #echo $targetStart + $targetCount # of $totalTargets</strong>
|
||||
#end if
|
||||
#if $targetStart + $targetCount < $totalTargets
|
||||
<a href="buildsbytarget?start=#echo $targetStart + $targetRange#$util.passthrough($self, 'days', 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
73
www/kojiweb/buildsbyuser.chtml
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Builds by User</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($userBuildPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'buildsbyuser?start=' + this.value * $userBuildRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userBuildPages
|
||||
<option value="$pageNum"#if $pageNum == $userBuildCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userBuildStart > 0
|
||||
<a href="buildsbyuser?start=#echo $userBuildStart - $userBuildRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUserBuilds != 0
|
||||
<strong>Users #echo $userBuildStart + 1 # through #echo $userBuildStart + $userBuildCount # of $totalUserBuilds</strong>
|
||||
#end if
|
||||
#if $userBuildStart + $userBuildCount < $totalUserBuilds
|
||||
<a href="buildsbyuser?start=#echo $userBuildStart + $userBuildRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="buildsbyuser?order=$util.toggleOrder($self, 'name')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th><a href="buildsbyuser?order=$util.toggleOrder($self, 'builds')">Builds</a> $util.sortImage($self, 'builds')</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
#if $len($userBuilds) > 0
|
||||
#for $userBuild in $userBuilds
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="userinfo?userID=$userBuild.id">$userBuild.name</a></td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $userBuild.builds#" height="15" class="graphrow"/></td>
|
||||
<td>$userBuild.builds</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No users</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($userBuildPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'buildsbyuser?start=' + this.value * $userBuildRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userBuildPages
|
||||
<option value="$pageNum"#if $pageNum == $userBuildCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userBuildStart > 0
|
||||
<a href="buildsbyuser?start=#echo $userBuildStart - $userBuildRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUserBuilds != 0
|
||||
<strong>Users #echo $userBuildStart + 1 # through #echo $userBuildStart + $userBuildCount # of $totalUserBuilds</strong>
|
||||
#end if
|
||||
#if $userBuildStart + $userBuildCount < $totalUserBuilds
|
||||
<a href="buildsbyuser?start=#echo $userBuildStart + $userBuildRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
61
www/kojiweb/buildtargetedit.chtml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
#if $target
|
||||
<h4>Edit target $target.name</h4>
|
||||
#else
|
||||
<h4>Create build target</h4>
|
||||
#end if
|
||||
|
||||
<form action="#if $target then 'buildtargetedit' else 'buildtargetcreate'#">
|
||||
#if $target
|
||||
<input type="hidden" name="targetID" value="$target.id"/>
|
||||
#end if
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>
|
||||
<input type="text" name="name" size="50" value="#if $target then $target.name else ''#"/>
|
||||
</td>
|
||||
</tr>
|
||||
#if $target
|
||||
<tr>
|
||||
<th>ID</th><td>$target.id</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Build Tag</th>
|
||||
<td>
|
||||
<select name="buildTag">
|
||||
<option value="">select tag</option>
|
||||
#for $tag in $tags
|
||||
<option value="$tag.id"#if $target and $target.build_tag == $tag.id then ' selected' else ''#>$tag.name</option>
|
||||
#end for
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Destination Tag</th>
|
||||
<td>
|
||||
<select name="destTag">
|
||||
<option value="">select tag</option>
|
||||
#for $tag in $tags
|
||||
<option value="$tag.id"#if $target and $target.dest_tag == $tag.id then ' selected' else ''#>$tag.name</option>
|
||||
#end for
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
#if $target
|
||||
<button type="submit" name="save" value="Save">Save</button>
|
||||
#else
|
||||
<button type="submit" name="add" value="Add">Add</button>
|
||||
#end if
|
||||
</td>
|
||||
<td><button type="submit" name="cancel" value="Cancel">Cancel</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
30
www/kojiweb/buildtargetinfo.chtml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for target $target.name</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th><td>$target.name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th><td>$target.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Build Tag</th><td><a href="taginfo?tagID=$buildTag.id">$buildTag.name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Destination Tag</th><td><a href="taginfo?tagID=$destTag.id">$destTag.name</a></td>
|
||||
</tr>
|
||||
#if 'admin' in $perms
|
||||
<tr>
|
||||
<th colspan="2"><a href="buildtargetedit?targetID=$target.id">Edit</a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2"><a href="buildtargetdelete?targetID=$target.id">Delete</a></th>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
76
www/kojiweb/buildtargets.chtml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Build Targets</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if $len($targetPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'buildtargets?start=' + this.value * $targetRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $targetPages
|
||||
<option value="$pageNum"#if $pageNum == $targetCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $targetStart > 0
|
||||
<a href="buildtargets?start=#echo $targetStart - $targetRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalTargets != 0
|
||||
<strong>Targets #echo $targetStart + 1 # through #echo $targetStart + $targetCount # of $totalTargets</strong>
|
||||
#end if
|
||||
#if $targetStart + $targetCount < $totalTargets
|
||||
<a href="buildtargets?start=#echo $targetStart + $targetRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="buildtargets?order=$util.toggleOrder($self, 'id')">ID</a> $util.sortImage($self, 'id')</th>
|
||||
<th><a href="buildtargets?order=$util.toggleOrder($self, 'name')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
</tr>
|
||||
#if $len($targets) > 0
|
||||
#for $target in $targets
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>$target.id</td>
|
||||
<td><a href="buildtargetinfo?targetID=$target.id">$target.name</a></td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="2">No build targets</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if $len($targetPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'buildtargets?start=' + this.value * $targetRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $targetPages
|
||||
<option value="$pageNum"#if $pageNum == $targetCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $targetStart > 0
|
||||
<a href="buildtargets?start=#echo $targetStart - $targetRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalTargets != 0
|
||||
<strong>Targets #echo $targetStart + 1 # through #echo $targetStart + $targetCount # of $totalTargets</strong>
|
||||
#end if
|
||||
#if $targetStart + $targetCount < $totalTargets
|
||||
<a href="buildtargets?start=#echo $targetStart + $targetRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#if 'admin' in $perms
|
||||
<br/>
|
||||
<a href="buildtargetcreate">Create new Build Target</a>
|
||||
#end if
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
28
www/kojiweb/channelinfo.chtml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for channel $channel.name</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th><td>$channel.name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th><td>$channel.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Hosts</th>
|
||||
<td>
|
||||
#if $len($hosts) > 0
|
||||
#for $host in $hosts
|
||||
<a href="hostinfo?hostID=$host.id">$host.name</a><br/>
|
||||
#end for
|
||||
#else
|
||||
No hosts
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
30
www/kojiweb/fileinfo.chtml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
<h4>Information for file $file.name</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th><td>$file.name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>MD5 Sum</th><td>$file.md5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Size</th><td>$file.size</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Flags</th>
|
||||
<td>
|
||||
#for flag in $util.formatFileFlags($file.flags)
|
||||
$flag<br/>
|
||||
#end for
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
#set $epoch = ($rpm.epoch != None and $str($rpm.epoch) + ':' or '')
|
||||
<th>RPM</th><td><a href="rpminfo?rpmID=$rpm.id">$rpm.name-$epoch$rpm.version-$rpm.release.${rpm.arch}.rpm</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
75
www/kojiweb/hostinfo.chtml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for host $host.name</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th><td>$host.name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th><td>$host.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arches</th><td>$host.arches</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Capacity</th><td>$host.capacity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Task Load</th><td><a href="tasks?hostID=$host.id">$host.task_load</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
#set $enabled = $host.enabled and 'yes' or 'no'
|
||||
<th>Enabled?</th>
|
||||
<td class="$enabled">
|
||||
$util.imageTag($enabled)
|
||||
#if 'admin' in $perms
|
||||
#if $host.enabled
|
||||
<span class="adminLink">(<a href="disablehost?hostID=$host.id">disable</a>)</span>
|
||||
#else
|
||||
<span class="adminLink">(<a href="enablehost?hostID=$host.id">enable</a>)</span>
|
||||
#end if
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
#set $ready = $host.ready and 'yes' or 'no'
|
||||
<th>Ready?</th><td class="$ready">$util.imageTag($ready)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Last Update</th><td>$util.formatTime($lastUpdate)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Channels</th>
|
||||
<td>
|
||||
#for $channel in $channels
|
||||
<a href="channelinfo?channelID=$channel.id">$channel.name</a><br/>
|
||||
#end for
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Active Buildroots</th>
|
||||
<td class="container">
|
||||
#if $len($buildroots) > 0
|
||||
<table class="nested data-list">
|
||||
<tr class="list-header">
|
||||
<th>Buildroot</th><th>Created</th><th>State</th>
|
||||
</tr>
|
||||
#for $buildroot in $buildroots
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="buildrootinfo?buildrootID=$buildroot.id">$buildroot.tag_name-$buildroot.id-$buildroot.repo_id</a></td>
|
||||
<td>$util.formatTime($buildroot.create_event_time)</td>
|
||||
<td>$util.imageTag($util.brStateName($buildroot.state))</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No buildroots
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
79
www/kojiweb/hosts.chtml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Hosts</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="6">
|
||||
#if $len($hostPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'hosts?start=' + this.value * $hostRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $hostPages
|
||||
<option value="$pageNum"#if $pageNum == $hostCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $hostStart > 0
|
||||
<a href="hosts?start=#echo $hostStart - $hostRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalHosts != 0
|
||||
<strong>Hosts #echo $hostStart + 1 # through #echo $hostStart + $hostCount # of $totalHosts</strong>
|
||||
#end if
|
||||
#if $hostStart + $hostCount < $totalHosts
|
||||
<a href="hosts?start=#echo $hostStart + $hostRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="hosts?order=$util.toggleOrder($self, 'id')">ID</a> $util.sortImage($self, 'id')</th>
|
||||
<th><a href="hosts?order=$util.toggleOrder($self, 'name')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th>Arches</th>
|
||||
<th><a href="hosts?order=$util.toggleOrder($self, 'enabled')">Enabled?</a> $util.sortImage($self, 'enabled')</th>
|
||||
<th><a href="hosts?order=$util.toggleOrder($self, 'ready')">Ready?</a> $util.sortImage($self, 'ready')</th>
|
||||
<th><a href="hosts?order=$util.toggleOrder($self, 'last_update')">Last Update</a> $util.sortImage($self, 'last_update')</th>
|
||||
</tr>
|
||||
#if $len($hosts) > 0
|
||||
#for $host in $hosts
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>$host.id</td>
|
||||
<td><a href="hostinfo?hostID=$host.id">$host.name</a></td>
|
||||
<td>$host.arches</td>
|
||||
<td class="$str($bool($host.enabled)).lower()">#if $host.enabled then $util.imageTag('yes') else $util.imageTag('no')#</td>
|
||||
<td class="$str($bool($host.ready)).lower()">#if $host.ready then $util.imageTag('yes') else $util.imageTag('no')#</td>
|
||||
<td>$util.formatTime($host.last_update)</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="6">No hosts</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="6">
|
||||
#if $len($hostPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'hosts?start=' + this.value * $hostRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $hostPages
|
||||
<option value="$pageNum"#if $pageNum == $hostCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $hostStart > 0
|
||||
<a href="hosts?start=#echo $hostStart - $hostRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalHosts != 0
|
||||
<strong>Hosts #echo $hostStart + 1 # through #echo $hostStart + $hostCount # of $totalHosts</strong>
|
||||
#end if
|
||||
#if $hostStart + $hostCount < $totalHosts
|
||||
<a href="hosts?start=#echo $hostStart + $hostRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
18
www/kojiweb/includes/Makefile
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
SERVERDIR = /includes
|
||||
FILES = $(wildcard *.chtml)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/$(SERVERDIR)
|
||||
install -m 644 $(FILES) $(DESTDIR)/$(SERVERDIR)
|
||||
11
www/kojiweb/includes/footer.chtml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
</div>
|
||||
|
||||
<p id="footer">
|
||||
Copyright © 2006-2007 Red Hat
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
71
www/kojiweb/includes/header.chtml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#import koji
|
||||
#import random
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
|
||||
#def greeting()
|
||||
#set $greetings = ('hello', 'hi', 'yo', "what's up", "g'day", 'back to work',
|
||||
'bonjour',
|
||||
'hallo',
|
||||
'ciao',
|
||||
'hola',
|
||||
'olá',
|
||||
'dobrý den',
|
||||
'zdravstvuite',
|
||||
'góðan daginn',
|
||||
'hej',
|
||||
'grüezi',
|
||||
'céad míle fáilte',
|
||||
'hylô',
|
||||
'你好',
|
||||
'こんにちは',
|
||||
'नमस्कार',
|
||||
'안녕하세요')
|
||||
#echo $random.choice($greetings)##slurp
|
||||
#end def
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<title>$title | Koji</title>
|
||||
<link rel="stylesheet" type="text/css" media="screen" title="Koji Style" href="/koji-static/koji.css"/>
|
||||
<link rel="alternate stylesheet" type="text/css" media="screen" title="Debug" href="/koji-static/debug.css"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="Koji: recent builds" href="/koji/recentbuilds"/>
|
||||
</head>
|
||||
<body id="$pageID">
|
||||
|
||||
<div id="wrap">
|
||||
<div id="innerwrap">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div id="header">
|
||||
<img src="/koji-static/images/koji.png" alt="Koji Logo" width="49" height="40" id="kojiLogo"/>
|
||||
</div><!-- end header -->
|
||||
|
||||
<!-- MAIN NAVIGATION -->
|
||||
<div id="mainNav">
|
||||
<h4 class="hide">Main Site Links:</h4>
|
||||
<ul>
|
||||
<li id="summary"><a href="index">Summary</a></li>
|
||||
<li id="packages"><a href="packages">Packages</a></li>
|
||||
<li id="builds"><a href="builds">Builds</a></li>
|
||||
<li id="tasks"><a href="tasks">Tasks</a></li>
|
||||
<li id="tags"><a href="tags">Tags</a></li>
|
||||
<li id="buildtargets"><a href="buildtargets">Build Targets</a></li>
|
||||
<li id="users"><a href="users">Users</a></li>
|
||||
<li id="hosts"><a href="hosts">Hosts</a></li>
|
||||
<li id="reports"><a href="reports">Reports</a></li>
|
||||
<li id="search"><a href="search">Search</a></li>
|
||||
</ul>
|
||||
</div><!-- end mainNav -->
|
||||
|
||||
<span id="loginInfo">
|
||||
$koji.formatTimeLong($currentDate) |
|
||||
#if $currentUser
|
||||
$greeting(), <a href="userinfo?userID=$currentUser.id">$currentUser.name</a> | <a href="logout">logout</a>
|
||||
#else
|
||||
<a href="login">login</a>
|
||||
#end if
|
||||
</span>
|
||||
|
||||
<div id="content">
|
||||
188
www/kojiweb/index.chtml
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
#import koji
|
||||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<div class="pageHeader">Welcome to Koji Web</div>
|
||||
|
||||
<div class="dataHeader" id="buildlist">#if $user then 'Your ' else ''#Recent Builds</div>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($buildPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'index?buildStart=' + this.value * $buildRange + '$util.passthrough($self, 'buildOrder', 'packageOrder', 'packageStart', 'taskOrder', 'taskStart')#buildlist';">
|
||||
#for $pageNum in $buildPages
|
||||
<option value="$pageNum"#if $pageNum == $buildCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $buildStart > 0
|
||||
<a href="index?buildStart=#echo $buildStart - $buildRange #$util.passthrough($self, 'buildOrder', 'packageOrder', 'packageStart', 'taskOrder', 'taskStart')#buildlist"><<<</a>
|
||||
#end if
|
||||
#if $totalBuilds != 0
|
||||
<strong>Build #echo $buildStart + 1 # through #echo $buildStart + $buildCount # of $totalBuilds</strong>
|
||||
#end if
|
||||
#if $buildStart + $buildCount < $totalBuilds
|
||||
<a href="index?buildStart=#echo $buildStart + $buildRange#$util.passthrough($self, 'buildOrder', 'packageOrder', 'packageStart', 'taskOrder', 'taskStart')#buildlist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="index?buildOrder=$util.toggleOrder($self, 'nvr', 'buildOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'taskOrder', 'taskStart')#buildlist">NVR</a> $util.sortImage($self, 'nvr', 'buildOrder')</th>
|
||||
<th><a href="index?buildOrder=$util.toggleOrder($self, 'completion_time', 'buildOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'taskOrder', 'taskStart')#buildlist">Finished</a> $util.sortImage($self, 'completion_time', 'buildOrder')</th>
|
||||
<th><a href="index?buildOrder=$util.toggleOrder($self, 'state', 'buildOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'taskOrder', 'taskStart')#buildlist">State</a> $util.sortImage($self, 'state', 'buildOrder')</th>
|
||||
</tr>
|
||||
#for $build in $builds
|
||||
<tr class="$util.rowToggle($self)">
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
<td><a href="buildinfo?buildID=$build.build_id">$build.nvr</a></td>
|
||||
<td>$util.formatTime($build.completion_time)</td>
|
||||
<td class="$stateName">$util.stateImage($build.state)</td>
|
||||
</tr>
|
||||
#end for
|
||||
#if $totalBuilds == 0
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No builds</td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="dataHeader" id="tasklist">#if $user then 'Your ' else ''#Recent Tasks</div>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="5">
|
||||
#if $len($taskPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'index?taskStart=' + this.value * $taskRange + '$util.passthrough($self, 'taskOrder', 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist';">
|
||||
#for $pageNum in $taskPages
|
||||
<option value="$pageNum"#if $pageNum == $taskCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $taskStart > 0
|
||||
<a href="index?taskStart=#echo $taskStart - $taskRange #$util.passthrough($self, 'taskOrder', 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist"><<<</a>
|
||||
#end if
|
||||
#if $totalTasks != 0
|
||||
<strong>Tasks #echo $taskStart + 1 # through #echo $taskStart + $taskCount # of $totalTasks</strong>
|
||||
#end if
|
||||
#if $taskStart + $taskCount < $totalTasks
|
||||
<a href="index?taskStart=#echo $taskStart + $taskRange#$util.passthrough($self, 'taskOrder', 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="index?taskOrder=$util.toggleOrder($self, 'id', 'taskOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist">ID</a> $util.sortImage($self, 'id', 'taskOrder')</th>
|
||||
<th><a href="index?taskOrder=$util.toggleOrder($self, 'method', 'taskOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist">Type</a> $util.sortImage($self, 'method', 'taskOrder')</th>
|
||||
<th><a href="index?taskOrder=$util.toggleOrder($self, 'arch', 'taskOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist">Arch</a> $util.sortImage($self, 'arch', 'taskOrder')</th>
|
||||
<th><a href="index?taskOrder=$util.toggleOrder($self, 'completion_time', 'taskOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist">Finished</a> $util.sortImage($self, 'completion_time', 'taskOrder')</th>
|
||||
<th><a href="index?taskOrder=$util.toggleOrder($self, 'state', 'taskOrder')$util.passthrough($self, 'packageOrder', 'packageStart', 'buildOrder', 'buildStart')#tasklist">State</a> $util.sortImage($self, 'state', 'taskOrder')</th>
|
||||
</tr>
|
||||
#for $task in $tasks
|
||||
<tr class="$util.rowToggle($self)">
|
||||
#set $state = $util.taskState($task.state)
|
||||
<td>$task.id</td>
|
||||
<td><a href="taskinfo?taskID=$task.id" class="task$state" title="$state">$koji.taskLabel($task)</a></td>
|
||||
<td>$task.arch</td>
|
||||
<td>$util.formatTime($task.completion_time)</td>
|
||||
<td class="task$state">$util.imageTag($state)</td>
|
||||
</tr>
|
||||
#end for
|
||||
#if $totalTasks == 0
|
||||
<tr class="row-odd">
|
||||
<td colspan="5">No tasks</td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
|
||||
#if $user
|
||||
<br/>
|
||||
|
||||
<div class="dataHeader" id="packagelist">Your Packages</div>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($packagePages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'index?packageStart=' + this.value * $packageRange + '$util.passthrough($self, 'packageOrder', 'buildOrder', 'buildStart', 'taskOrder', 'taskStart')#packagelist';">
|
||||
#for $pageNum in $packagePages
|
||||
<option value="$pageNum"#if $pageNum == $packageCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $packageStart > 0
|
||||
<a href="index?packageStart=#echo $packageStart - $packageRange #$util.passthrough($self, 'packageOrder', 'buildOrder', 'buildStart', 'taskOrder', 'taskStart')#packagelist"><<<</a>
|
||||
#end if
|
||||
#if $totalPackages != 0
|
||||
<strong>Package #echo $packageStart + 1 # through #echo $packageStart + $packageCount # of $totalPackages</strong>
|
||||
#end if
|
||||
#if $packageStart + $packageCount < $totalPackages
|
||||
<a href="index?packageStart=#echo $packageStart + $packageRange#$util.passthrough($self, 'packageOrder', 'buildOrder', 'buildStart', 'taskOrder', 'taskStart')#packagelist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="index?packageOrder=$util.toggleOrder($self, 'package_name', 'packageOrder')$util.passthrough($self, 'buildOrder', 'buildStart', 'taskOrder', 'taskStart')#packagelist">Name</a> $util.sortImage($self, 'package_name', 'packageOrder')</th>
|
||||
<th><a href="index?packageOrder=$util.toggleOrder($self, 'tag_name', 'packageOrder')$util.passthrough($self, 'buildOrder', 'buildStart', 'taskOrder', 'taskStart')#packagelist">Tag</a> $util.sortImage($self, 'tag_name', 'packageOrder')</th>
|
||||
<th><a href="index?packageOrder=$util.toggleOrder($self, 'blocked', 'packageOrder')$util.passthrough($self, 'buildOrder', 'buildStart', 'taskOrder', 'taskStart')#packagelist">Included?</a> $util.sortImage($self, 'blocked', 'packageOrder')</th>
|
||||
</tr>
|
||||
#for $package in $packages
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="packageinfo?packageID=$package.package_id">$package.package_name</a></td>
|
||||
<td><a href="taginfo?tagID=$package.tag_id">$package.tag_name</a></td>
|
||||
#set $included = $package.blocked and 'no' or 'yes'
|
||||
<td>$util.imageTag($included)</td>
|
||||
</tr>
|
||||
#end for
|
||||
#if $totalPackages == 0
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No packages</td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="dataHeader" id="notificationlist">Your Notifications</div>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td colspan="6"></td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th>Package</th>
|
||||
<th>Tag</th>
|
||||
<th>Type</th>
|
||||
<th>Email</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
#for $notif in $notifs
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>#if $notif.package then $notif.package.name else 'all'#</td>
|
||||
<td>#if $notif.tag then $notif.tag.name else 'all'#</td>
|
||||
<td>#if $notif.success_only then 'success only' else 'all'#</td>
|
||||
<td>$notif.email</td>
|
||||
<td><a href="notificationedit?notificationID=$notif.id">edit</a></td>
|
||||
<td><a href="notificationdelete?notificationID=$notif.id">delete</a></td>
|
||||
</tr>
|
||||
#end for
|
||||
#if $len($notifs) == 0
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No notifications</td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<a href="notificationcreate">Add a Notification</a>
|
||||
#end if
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
1554
www/kojiweb/index.py
Normal file
59
www/kojiweb/notificationedit.chtml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
#if $notif
|
||||
<h4>Edit notification</h4>
|
||||
#else
|
||||
<h4>Create notification</h4>
|
||||
#end if
|
||||
|
||||
<form action="#if $notif then 'notificationedit' else 'notificationcreate'#">
|
||||
#if $notif
|
||||
<input type="hidden" name="notificationID" value="$notif.id"/>
|
||||
#end if
|
||||
<table>
|
||||
<tr>
|
||||
<th>Package</th>
|
||||
<td>
|
||||
<select name="package">
|
||||
<option value="all"#if $notif and not $notif.package_id then ' selected' else ''#>all</option>
|
||||
#for $package in $packages
|
||||
<option value="$package.package_id"#if $notif and $notif.package_id == $package.package_id then ' selected' else ''#>$package.package_name</option>
|
||||
#end for
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<td>
|
||||
<select name="tag">
|
||||
<option value="all"#if $notif and not $notif.tag_id then ' selected' else ''#>all</option>
|
||||
#for $tag in $tags
|
||||
<option value="$tag.id"#if $notif and $notif.tag_id == $tag.id then ' selected' else ''#>$tag.name</option>
|
||||
#end for
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Success Only?</th>
|
||||
<td><input type="checkbox" name="success_only" value="yes"#if $notif and $notif.success_only then ' checked' else ''#>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><input type="text" name="email" value="#if $notif then $notif.email else ''#"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
#if $notif
|
||||
<button type="submit" name="save" value="Save">Save</button>
|
||||
#else
|
||||
<button type="submit" name="add" value="Add">Add</button>
|
||||
#end if
|
||||
</td>
|
||||
<td><button type="submit" name="cancel" value="Cancel">Cancel</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
113
www/kojiweb/packageinfo.chtml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for package $package.name</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th><td>$package.name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th><td>$package.id</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th id="buildlist">Builds</th>
|
||||
<td class="container">
|
||||
#if $len($builds) > 0
|
||||
<table class="nested data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="4">
|
||||
#if $len($buildPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'packageinfo?buildStart=' + this.value * $buildRange + '$util.passthrough($self, 'packageID', 'buildOrder', 'tagOrder', 'tagStart')#buildlist';">
|
||||
#for $pageNum in $buildPages
|
||||
<option value="$pageNum"#if $pageNum == $buildCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $buildStart > 0
|
||||
<a href="packageinfo?buildStart=#echo $buildStart - $buildRange#$util.passthrough($self, 'packageID', 'buildOrder', 'tagOrder', 'tagStart')#buildlist"><<<</a>
|
||||
#end if
|
||||
<strong>#echo $buildStart + 1 # through #echo $buildStart + $buildCount # of $totalBuilds</strong>
|
||||
#if $buildStart + $buildCount < $totalBuilds
|
||||
<a href="packageinfo?buildStart=#echo $buildStart + $buildRange#$util.passthrough($self, 'packageID', 'buildOrder', 'tagOrder', 'tagStart')#buildlist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="packageinfo?buildOrder=$util.toggleOrder($self, 'nvr', 'buildOrder')$util.passthrough($self, 'packageID', 'tagOrder', 'tagStart')#buildlist">NVR</a> $util.sortImage($self, 'nvr', 'buildOrder')</th>
|
||||
<th><a href="packageinfo?buildOrder=$util.toggleOrder($self, 'owner_name', 'buildOrder')$util.passthrough($self, 'packageID', 'tagOrder', 'tagStart')#buildlist">Built by</a> $util.sortImage($self, 'owner_name', 'buildOrder')</th>
|
||||
<th><a href="packageinfo?buildOrder=$util.toggleOrder($self, 'completion_time', 'buildOrder')$util.passthrough($self, 'packageID', 'tagOrder', 'tagStart')#buildlist">Finished</a> $util.sortImage($self, 'completion_time', 'buildOrder')</th>
|
||||
<th><a href="packageinfo?buildOrder=$util.toggleOrder($self, 'state', 'buildOrder')$util.passthrough($self, 'packageID', 'tagOrder', 'tagStart')#buildlist">State</a> $util.sortImage($self, 'state', 'buildOrder')</th>
|
||||
</tr>
|
||||
#for $build in $builds
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="buildinfo?buildID=$build.build_id">$build.nvr</a></td>
|
||||
<td><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<td>$util.formatTime($build.completion_time)</td>
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
<td class="$stateName">$util.stateImage($build.state)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No builds
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th id="taglist">Tags</th>
|
||||
<td class="container">
|
||||
#if $len($tags) > 0
|
||||
<table class="nested data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="4">
|
||||
#if $len($tagPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'packageinfo?tagStart=' + this.value * $tagRange + '$util.passthrough($self, 'packageID', 'tagOrder', 'buildOrder', 'buildStart')#taglist';">
|
||||
#for $pageNum in $tagPages
|
||||
<option value="$pageNum"#if $pageNum == $tagCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $tagStart > 0
|
||||
<a href="packageinfo?tagStart=#echo $tagStart - $tagRange#$util.passthrough($self, 'packageID', 'tagOrder', 'buildOrder', 'buildStart')#taglist"><<<</a>
|
||||
#end if
|
||||
<strong>#echo $tagStart + 1 # through #echo $tagStart + $tagCount # of $totalTags</strong>
|
||||
#if $tagStart + $tagCount < $totalTags
|
||||
<a href="packageinfo?tagStart=#echo $tagStart + $tagRange#$util.passthrough($self, 'packageID', 'tagOrder', 'buildOrder', 'buildStart')#taglist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="packageinfo?tagOrder=$util.toggleOrder($self, 'name', 'tagOrder')$util.passthrough($self, 'packageID', 'buildOrder', 'buildStart')#taglist">Name</a> $util.sortImage($self, 'name', 'tagOrder')</th>
|
||||
<th><a href="packageinfo?tagOrder=$util.toggleOrder($self, 'owner_name', 'tagOrder')$util.passthrough($self, 'packageID', 'buildOrder', 'buildStart')#taglist">Owner</a> $util.sortImage($self, 'owner_name', 'tagOrder')</th>
|
||||
<th><a href="packageinfo?tagOrder=$util.toggleOrder($self, 'blocked', 'tagOrder')$util.passthrough($self, 'packageID', 'buildOrder', 'buildStart')#taglist">Included?</a> $util.sortImage($self, 'blocked', 'tagOrder')</th>
|
||||
<th><a href="packageinfo?tagOrder=$util.toggleOrder($self, 'extra_arches', 'tagOrder')$util.passthrough($self, 'packageID', 'buildOrder', 'buildStart')#taglist">Extra Arches</a> $util.sortImage($self, 'extra_arches', 'tagOrder')</th>
|
||||
</tr>
|
||||
#for $tag in $tags
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="taginfo?tagID=$tag.id">$tag.name</a></td>
|
||||
<td><a href="userinfo?userID=$tag.owner_id">$tag.owner_name</a></td>
|
||||
#set $included = $tag.blocked and 'no' or 'yes'
|
||||
<td>$util.imageTag($included)</td>
|
||||
<td>$tag.extra_arches</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No tags
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
110
www/kojiweb/packages.chtml
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Packages#if $prefix then ' starting with "%s"' % $prefix else ''##if $tag then ' in tag <a href="taginfo?tagID=%i">%s</a>' % ($tag.id, $tag.name) else ''##if $user then ' owned by <a href="userinfo?userID=%i">%s</a>' % ($user.id, $user.name) else ''#</h4>
|
||||
|
||||
<table class="data-list">
|
||||
#if $tag
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
#if $inherited
|
||||
<a href="packages?inherited=0$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix')">Hide inherited packages</a>
|
||||
#else
|
||||
<a href="packages?${util.passthrough($self, 'userID', 'tagID', 'order', 'prefix')[1:]}">Show inherited packages</a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="charlist" colspan="#if $tag or $user then '5' else '2'#">
|
||||
#for $char in $chars
|
||||
#if $prefix == $char
|
||||
<strong>$char</strong>
|
||||
#else
|
||||
<a href="packages?prefix=$char$util.passthrough($self, 'userID', 'tagID', 'order', 'inherited')">$char</a>
|
||||
#end if
|
||||
|
|
||||
#end for
|
||||
#if $prefix
|
||||
<a href="packages?${util.passthrough($self, 'userID', 'tagID', 'order', 'inherited')[1:]}">all</a>
|
||||
#else
|
||||
<strong>all</strong>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paginate" colspan="#if $tag or $user then '5' else '2'#">
|
||||
#if $len($packagePages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'packages?start=' + this.value * $packageRange + '$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited')';">
|
||||
#for $pageNum in $packagePages
|
||||
<option value="$pageNum"#if $pageNum == $packageCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $packageStart > 0
|
||||
<a href="packages?start=#echo $packageStart - $packageRange #$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited')"><<<</a>
|
||||
#end if
|
||||
#if $totalPackages != 0
|
||||
<strong>Packages #echo $packageStart + 1 # through #echo $packageStart + $packageCount # of $totalPackages</strong>
|
||||
#end if
|
||||
#if $packageStart + $packageCount < $totalPackages
|
||||
<a href="packages?start=#echo $packageStart + $packageRange#$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="packages?order=$util.toggleOrder($self, 'package_id')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited')">ID</a> $util.sortImage($self, 'package_id')</th>
|
||||
<th><a href="packages?order=$util.toggleOrder($self, 'package_name')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited')">Name</a> $util.sortImage($self, 'package_name')</th>
|
||||
#if $tag or $user
|
||||
<th><a href="packages?order=$util.toggleOrder($self, 'tag_name')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited')">Tag</a> $util.sortImage($self, 'tag_name')</th>
|
||||
<th><a href="packages?order=$util.toggleOrder($self, 'owner_name')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited')">Owner</a> $util.sortImage($self, 'owner_name')</th>
|
||||
<th><a href="packages?order=$util.toggleOrder($self, 'blocked')$util.passthrough($self, 'userID', 'tagID', 'prefix', 'inherited')">Included?</a> $util.sortImage($self, 'blocked')</th>
|
||||
#end if
|
||||
</tr>
|
||||
#if $len($packages) > 0
|
||||
#for $package in $packages
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>$package.package_id</td>
|
||||
<td><a href="packageinfo?packageID=$package.package_id">$package.package_name</a></td>
|
||||
#if $tag or $user
|
||||
<td><a href="taginfo?tagID=$package.tag_id">$package.tag_name</a></td>
|
||||
<td><a href="userinfo?userID=$package.owner_id">$package.owner_name</a></td>
|
||||
<td class="$str(not $package.blocked).lower()">#if $package.blocked then $util.imageTag('no') else $util.imageTag('yes')#</td>
|
||||
#end if
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="#if $tag or $user then '5' else '2'#">No packages</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="#if $tag or $user then '5' else '2'#">
|
||||
#if $len($packagePages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'packages?start=' + this.value * $packageRange + '$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited')';">
|
||||
#for $pageNum in $packagePages
|
||||
<option value="$pageNum"#if $pageNum == $packageCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $packageStart > 0
|
||||
<a href="packages?start=#echo $packageStart - $packageRange #$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited')"><<<</a>
|
||||
#end if
|
||||
#if $totalPackages != 0
|
||||
<strong>Packages #echo $packageStart + 1 # through #echo $packageStart + $packageCount # of $totalPackages</strong>
|
||||
#end if
|
||||
#if $packageStart + $packageCount < $totalPackages
|
||||
<a href="packages?start=#echo $packageStart + $packageRange#$util.passthrough($self, 'userID', 'tagID', 'order', 'prefix', 'inherited')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
73
www/kojiweb/packagesbyuser.chtml
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Packages by User</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($userPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'packagesbyuser?start=' + this.value * $userRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userPages
|
||||
<option value="$pageNum"#if $pageNum == $userCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userStart > 0
|
||||
<a href="packagesbyuser?start=#echo $userStart - $userRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUsers != 0
|
||||
<strong>Users #echo $userStart + 1 # through #echo $userStart + $userCount # of $totalUsers</strong>
|
||||
#end if
|
||||
#if $userStart + $userCount < $totalUsers
|
||||
<a href="packagesbyuser?start=#echo $userStart + $userRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="packagesbyuser?order=$util.toggleOrder($self, 'name')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th><a href="packagesbyuser?order=$util.toggleOrder($self, 'packages')">Packages</a> $util.sortImage($self, 'packages')</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
#if $len($users) > 0
|
||||
#for $user in $users
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="userinfo?userID=$user.id">$user.name</a></td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $user.packages#" height="15" class="graphrow"/></td>
|
||||
<td>$user.packages</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No users</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($userPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'packagesbyuser?start=' + this.value * $userRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userPages
|
||||
<option value="$pageNum"#if $pageNum == $userCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userStart > 0
|
||||
<a href="packagesbyuser?start=#echo $userStart - $userRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUsers != 0
|
||||
<strong>Users #echo $userStart + 1 # through #echo $userStart + $userCount # of $totalUsers</strong>
|
||||
#end if
|
||||
#if $userStart + $userCount < $totalUsers
|
||||
<a href="packagesbyuser?start=#echo $userStart + $userRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
48
www/kojiweb/recentbuilds.chtml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#import koji
|
||||
#import koji.util
|
||||
#from kojiweb import util
|
||||
|
||||
#def linkURL()
|
||||
#set $query = []
|
||||
#if $tag
|
||||
#silent $query.append('tagID=%i' % $tag.id)
|
||||
#end if
|
||||
#if $user
|
||||
#silent $query.append('userID=%i' % $user.id)
|
||||
#end if
|
||||
#if $query
|
||||
#echo '%s/%s?%s' % ($weburl, 'builds', '&'.join($query))
|
||||
#else
|
||||
#echo '%s/%s' % ($weburl, 'builds')
|
||||
#end if
|
||||
#end def
|
||||
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>koji: most recent builds#if $tag then ' into tag ' + $tag.name else ''##if $user then ' by user ' + $user.name else ''#</title>
|
||||
<link>$linkURL()</link>
|
||||
<description>
|
||||
A list of the most recent builds
|
||||
#if $tag
|
||||
into tag $tag.name
|
||||
#end if
|
||||
#if $user
|
||||
by user $user.name
|
||||
#end if
|
||||
in the Koji Build System. The list is sorted in reverse chronological order by build completion time.
|
||||
</description>
|
||||
<pubDate>$util.formatTimeRSS($currentDate)</pubDate>
|
||||
#for $build in $builds
|
||||
<item>
|
||||
<title>$koji.BUILD_STATES[$build.state].lower(): $koji.buildLabel($build)#if $build.task then ', target: ' + $build.task.request[1] else ''#</title>
|
||||
<link>$weburl/buildinfo?buildID=$build.build_id</link>
|
||||
#if $build.completion_time
|
||||
<pubDate>$util.formatTimeRSS($build.completion_time)</pubDate>
|
||||
#end if
|
||||
#if $build.state == $koji.BUILD_STATES['COMPLETE'] and $build.changelog
|
||||
<description><pre>$util.escapeHTML($koji.util.formatChangelog($build.changelog))</pre></description>
|
||||
#end if
|
||||
</item>
|
||||
#end for
|
||||
</channel>
|
||||
</rss>
|
||||
17
www/kojiweb/reports.chtml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Reports</h4>
|
||||
|
||||
<ul>
|
||||
<li><a href="packagesbyuser">Number of packages owned by each user</a></li>
|
||||
<li><a href="buildsbyuser">Number of builds submitted by each user</a></li>
|
||||
<li><a href="rpmsbyhost">RPMs built by each host</a></li>
|
||||
<li><a href="tasksbyuser">Tasks submitted by each user</a></li>
|
||||
<li><a href="tasksbyhost">Tasks completed by each host</a></li>
|
||||
<li><a href="buildsbystatus">Succeeded/failed/canceled builds</a></li>
|
||||
<li><a href="buildsbytarget">Number of builds in each target</a></li>
|
||||
</ul>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
170
www/kojiweb/rpminfo.chtml
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
#from kojiweb import util
|
||||
#import time
|
||||
|
||||
#include "includes/header.chtml"
|
||||
#set $epoch = ($rpm.epoch != None and $str($rpm.epoch) + ':' or '')
|
||||
<h4>Information for RPM $rpm.name-$epoch$rpm.version-$rpm.release.${rpm.arch}.rpm</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th><td>$rpm.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name</th><td><a href="packageinfo?packageID=$build.package_id">$rpm.name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Version</th><td><a href="buildinfo?buildID=$build.id">$rpm.version</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Release</th><td>$rpm.release</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Epoch</th><td>$rpm.epoch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arch</th><td>$rpm.arch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Size</th><td>$rpm.size</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Payload Hash</th><td>$rpm.payloadhash</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Build Time</th><td>$time.strftime('%Y-%m-%d %H:%M:%S', $time.gmtime($rpm.buildtime)) GMT</td>
|
||||
</tr>
|
||||
#if $builtInRoot
|
||||
<tr>
|
||||
<th>Buildroot</th><td><a href="buildrootinfo?buildrootID=$builtInRoot.id">$builtInRoot.tag_name-$builtInRoot.id-$builtInRoot.repo_id</a></td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Provides</th>
|
||||
<td class="container">
|
||||
#if $len($provides) > 0
|
||||
<table class="nested">
|
||||
#for $dep in $provides
|
||||
<tr>
|
||||
<td>$util.formatDep($dep.name, $dep.version, $dep.flags)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No Provides
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Requires</th>
|
||||
<td class="container">
|
||||
#if $len($requires) > 0
|
||||
<table class="nested">
|
||||
#for $dep in $requires
|
||||
<tr>
|
||||
<td>$util.formatDep($dep.name, $dep.version, $dep.flags)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No Requires
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Obsoletes</th>
|
||||
<td class="container">
|
||||
#if $len($obsoletes) > 0
|
||||
<table class="nested">
|
||||
#for $dep in $obsoletes
|
||||
<tr>
|
||||
<td>$util.formatDep($dep.name, $dep.version, $dep.flags)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No Obsoletes
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Conflicts</th>
|
||||
<td class="container">
|
||||
#if $len($conflicts) > 0
|
||||
<table class="nested">
|
||||
#for $dep in $conflicts
|
||||
<tr>
|
||||
<td>$util.formatDep($dep.name, $dep.version, $dep.flags)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No Conflicts
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id="filelist">Files</th>
|
||||
<td class="container">
|
||||
#if $len($files) > 0
|
||||
<table class="nested data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if $len($filePages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'rpminfo?fileStart=' + this.value * $fileRange + '$util.passthrough($self, 'rpmID', 'fileOrder')#filelist';">
|
||||
#for $pageNum in $filePages
|
||||
<option value="$pageNum"#if $pageNum == $fileCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $fileStart > 0
|
||||
<a href="rpminfo?fileStart=#echo $fileStart - $fileRange #$util.passthrough($self, 'rpmID', 'fileOrder')#filelist"><<<</a>
|
||||
#end if
|
||||
<strong>#echo $fileStart + 1 # through #echo $fileStart + $fileCount # of $totalFiles</strong>
|
||||
#if $fileStart + $fileCount < $totalFiles
|
||||
<a href="rpminfo?fileStart=#echo $fileStart + $fileRange#$util.passthrough($self, 'rpmID', 'fileOrder')#filelist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="rpminfo?fileOrder=$util.toggleOrder($self, 'name', 'fileOrder')$util.passthrough($self, 'rpmID')#filelist">Name</a> $util.sortImage($self, 'name', 'fileOrder')</th>
|
||||
<th><a href="rpminfo?fileOrder=$util.toggleOrder($self, 'size', 'fileOrder')$util.passthrough($self, 'rpmID')#filelist">Size</a> $util.sortImage($self, 'size', 'fileOrder')</th>
|
||||
</tr>
|
||||
#for $file in $files
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="fileinfo?rpmID=$rpm.id&filename=$file.name">$file.name</a></td><td>$file.size</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No files
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Component of</th>
|
||||
<td class="container">
|
||||
#if $len($buildroots) > 0
|
||||
<table class="nested data-list">
|
||||
<tr class="list-header">
|
||||
<th>Buildroot</th><th>State</th><th>Update?</th>
|
||||
</tr>
|
||||
#for $buildroot in $buildroots
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="buildrootinfo?buildrootID=$buildroot.id">$buildroot.tag_name-$buildroot.id-$buildroot.repo_id</a></td>
|
||||
<td>$util.imageTag($util.brStateName($buildroot.state))</td>
|
||||
#set $update = $buildroot.is_update and 'yes' or 'no'
|
||||
<td class="$update">$util.imageTag($update)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No Buildroots
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
82
www/kojiweb/rpmlist.chtml
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
#if $type == 'component'
|
||||
<h4>Component RPMs of buildroot $buildroot.tag_name-$buildroot.id-$buildroot.repo_id</h4>
|
||||
#else
|
||||
<h4>RPMs built in buildroot $buildroot.tag_name-$buildroot.id-$buildroot.repo_id</h4>
|
||||
#end if
|
||||
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="#if $type == 'component' then '2' else '1'#">
|
||||
#if $len($rpmPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'rpmlist?buildrootID=$buildroot.id&start=' + this.value * $rpmRange + '$util.passthrough($self, 'order', 'type')';">
|
||||
#for $pageNum in $rpmPages
|
||||
<option value="$pageNum"#if $pageNum == $rpmCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $rpmStart > 0
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart - $rpmRange #$util.passthrough($self, 'order', 'type')"><<<</a>
|
||||
#end if
|
||||
#if $totalRpms != 0
|
||||
<strong>RPMs #echo $rpmStart + 1 # through #echo $rpmStart + $rpmCount # of $totalRpms</strong>
|
||||
#end if
|
||||
#if $rpmStart + $rpmCount < $totalRpms
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart + $rpmRange#$util.passthrough($self, 'order', 'type')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="rpmlist?buildrootID=$buildroot.id&order=$util.toggleOrder($self, 'nvr')$util.passthrough($self, 'type')">NVR</a> $util.sortImage($self, 'nvr')</th>
|
||||
#if $type == 'component'
|
||||
<th><a href="rpmlist?buildrootID=$buildroot.id&order=$util.toggleOrder($self, 'is_update')$util.passthrough($self, 'type')">Update?</a> $util.sortImage($self, 'is_update')</th>
|
||||
#end if
|
||||
</tr>
|
||||
#if $len($rpms) > 0
|
||||
#for $rpm in $rpms
|
||||
<tr class="$util.rowToggle($self)">
|
||||
#set $epoch = ($rpm.epoch != None and $str($rpm.epoch) + ':' or '')
|
||||
<td><a href="rpminfo?rpmID=$rpm.id">$rpm.name-$epoch$rpm.version-$rpm.release.${rpm.arch}.rpm</a></td>
|
||||
#if $type == 'component'
|
||||
#set $update = $rpm.is_update and 'yes' or 'no'
|
||||
<td class="$update">$util.imageTag($update)</td>
|
||||
#end if
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="#if $type == 'component' then '2' else '1'#">No RPMs</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="#if $type == 'component' then '2' else '1'#">
|
||||
#if $len($rpmPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'rpmlist?buildrootID=$buildroot.id&start=' + this.value * $rpmRange + '$util.passthrough($self, 'order', 'type')';">
|
||||
#for $pageNum in $rpmPages
|
||||
<option value="$pageNum"#if $pageNum == $rpmCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $rpmStart > 0
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart - $rpmRange #$util.passthrough($self, 'order', 'type')"><<<</a>
|
||||
#end if
|
||||
#if $totalRpms != 0
|
||||
<strong>RPMs #echo $rpmStart + 1 # through #echo $rpmStart + $rpmCount # of $totalRpms</strong>
|
||||
#end if
|
||||
#if $rpmStart + $rpmCount < $totalRpms
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart + $rpmRange#$util.passthrough($self, 'order', 'type')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
105
www/kojiweb/rpmsbyhost.chtml
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>#if $rpmArch then $rpmArch + ' ' else ''#RPMs by Host#if $hostArch then ' (%s)' % $hostArch else ''#</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="archlist" colspan="3">
|
||||
<strong>Host arch:</strong> #for $arch in $hostArchList
|
||||
#if $arch == $hostArch
|
||||
<strong>$arch</strong> |
|
||||
#else
|
||||
<a href="rpmsbyhost?hostArch=$arch$util.passthrough($self, 'order', 'rpmArch')">$arch</a> |
|
||||
#end if
|
||||
#end for
|
||||
#if $hostArch
|
||||
<a href="rpmsbyhost?${util.passthrough($self, 'order', 'rpmArch')[1:]}">all</a>
|
||||
#else
|
||||
<strong>all</strong>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="archlist" colspan="3">
|
||||
<strong>RPM arch:</strong> #for $arch in $rpmArchList
|
||||
#if $arch == $rpmArch
|
||||
<strong>$arch</strong> |
|
||||
#else
|
||||
<a href="rpmsbyhost?rpmArch=$arch$util.passthrough($self, 'order', 'hostArch')">$arch</a> |
|
||||
#end if
|
||||
#end for
|
||||
#if $rpmArch
|
||||
<a href="rpmsbyhost?${util.passthrough($self, 'order', 'hostArch')[1:]}">all</a>
|
||||
#else
|
||||
<strong>all</strong>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($hostPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'rpmsbyhost?start=' + this.value * $hostRange + '$util.passthrough($self, 'order', 'hostArch', 'rpmArch')';">
|
||||
#for $pageNum in $hostPages
|
||||
<option value="$pageNum"#if $pageNum == $hostCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $hostStart > 0
|
||||
<a href="rpmsbyhost?start=#echo $hostStart - $hostRange #$util.passthrough($self, 'order', 'hostArch', 'rpmArch')"><<<</a>
|
||||
#end if
|
||||
#if $totalHosts != 0
|
||||
<strong>Hosts #echo $hostStart + 1 # through #echo $hostStart + $hostCount # of $totalHosts</strong>
|
||||
#end if
|
||||
#if $hostStart + $hostCount < $totalHosts
|
||||
<a href="rpmsbyhost?start=#echo $hostStart + $hostRange#$util.passthrough($self, 'order', 'hostArch', 'rpmArch')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="rpmsbyhost?order=$util.toggleOrder($self, 'name')$util.passthrough($self, 'hostArch', 'rpmArch')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th><a href="rpmsbyhost?order=$util.toggleOrder($self, 'rpms')$util.passthrough($self, 'hostArch', 'rpmArch')">RPMs</a> $util.sortImage($self, 'rpms')</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
#if $len($hosts) > 0
|
||||
#for $host in $hosts
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="hostinfo?hostID=$host.id">$host.name</a></td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $host.rpms#" height="15" class="graphrow"/></td>
|
||||
<td>$host.rpms</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No hosts</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($hostPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'rpmsbyhost?start=' + this.value * $hostRange + '$util.passthrough($self, 'order', 'hostArch', 'rpmArch')';">
|
||||
#for $pageNum in $hostPages
|
||||
<option value="$pageNum"#if $pageNum == $hostCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $hostStart > 0
|
||||
<a href="rpmsbyhost?start=#echo $hostStart - $hostRange #$util.passthrough($self, 'order', 'hostArch', 'rpmArch')"><<<</a>
|
||||
#end if
|
||||
#if $totalHosts != 0
|
||||
<strong>Hosts #echo $hostStart + 1 # through #echo $hostStart + $hostCount # of $totalHosts</strong>
|
||||
#end if
|
||||
#if $hostStart + $hostCount < $totalHosts
|
||||
<a href="rpmsbyhost?start=#echo $hostStart + $hostRange#$util.passthrough($self, 'order', 'hostArch', 'rpmArch')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
39
www/kojiweb/search.chtml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Search</h4>
|
||||
|
||||
<form action="search">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Search</th>
|
||||
<td><input type="text" name="terms"/></td>
|
||||
<td>
|
||||
<select name="type">
|
||||
<option value="package">Packages</option>
|
||||
<option value="build">Builds</option>
|
||||
<option value="tag">Tags</option>
|
||||
<option value="target">Build Targets</option>
|
||||
<option value="user">Users</option>
|
||||
<option value="host">Hosts</option>
|
||||
<option value="rpm">RPMs</option>
|
||||
<option value="file">Files</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<td colspan="2">
|
||||
<input type="radio" name="match" value="glob" checked="checked"/><abbr title="? will match any single character, * will match any sequence of zero or more characters">glob</abbr>
|
||||
<input type="radio" name="match" value="regexp"/><abbr title="full POSIX regular expressions">regexp</abbr>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<td colspan="2"><input type="submit" value="Search"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
72
www/kojiweb/searchresults.chtml
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Search Results for "$terms"</h4>
|
||||
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if $len($resultPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'search?start=' + this.value * $resultRange + '$util.passthrough($self, 'order', 'terms', 'type', 'match')';">
|
||||
#for $pageNum in $resultPages
|
||||
<option value="$pageNum"#if $pageNum == $resultCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $resultStart > 0
|
||||
<a href="search?start=#echo $resultStart - $resultRange #$util.passthrough($self, 'order', 'terms', 'type', 'match')"><<<</a>
|
||||
#end if
|
||||
#if $totalResults != 0
|
||||
<strong>Results #echo $resultStart + 1 # through #echo $resultStart + $resultCount # of $totalResults</strong>
|
||||
#end if
|
||||
#if $resultStart + $resultCount < $totalResults
|
||||
<a href="search?start=#echo $resultStart + $resultRange#$util.passthrough($self, 'order', 'terms', 'type', 'match')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="search?order=$util.toggleOrder($self, 'id')$util.passthrough($self, 'terms', 'type', 'match')">ID</a> $util.sortImage($self, 'id')</th>
|
||||
<th><a href="search?order=$util.toggleOrder($self, 'name')$util.passthrough($self, 'terms', 'type', 'match')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
</tr>
|
||||
#if $len($results) > 0
|
||||
#for $result in $results
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>$result.id</td>
|
||||
<td><a href="${infoURL % $result}">$result.name</a></td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="2">No search results</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if $len($resultPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'search?start=' + this.value * $resultRange + '$util.passthrough($self, 'order', 'terms', 'type', 'match')';">
|
||||
#for $pageNum in $resultPages
|
||||
<option value="$pageNum"#if $pageNum == $resultCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $resultStart > 0
|
||||
<a href="search?start=#echo $resultStart - $resultRange #$util.passthrough($self, 'order', 'terms', 'type', 'match')"><<<</a>
|
||||
#end if
|
||||
#if $totalResults != 0
|
||||
<strong>Results #echo $resultStart + 1 # through #echo $resultStart + $resultCount # of $totalResults</strong>
|
||||
#end if
|
||||
#if $resultStart + $resultCount < $totalResults
|
||||
<a href="search?start=#echo $resultStart + $resultRange#$util.passthrough($self, 'order', 'terms', 'type', 'match')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
54
www/kojiweb/tagedit.chtml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
#if $tag
|
||||
<h4>Edit tag $tag.name</h4>
|
||||
#else
|
||||
<h4>Create tag</h4>
|
||||
#end if
|
||||
|
||||
<form action="#if $tag then 'tagedit' else 'tagcreate'#">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>
|
||||
<input type="text" name="name" value="#if $tag then $tag.name else ''#"/>
|
||||
#if $tag
|
||||
<input type="hidden" name="tagID" value="$tag.id"/>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arches</th>
|
||||
<td><input type="text" name="arches" value="#if $tag then $tag.arches else ''#"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Locked</th>
|
||||
<td><input type="checkbox" name="locked" value="yes" #if $tag and $tag.locked then 'checked' else ''#>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Permission</th>
|
||||
<td>
|
||||
<select name="permission">
|
||||
<option value="none" #if $tag and not $tag.perm_id then 'selected' else ''#>none</option>
|
||||
#for $permission in $permissions
|
||||
<option value="$permission.id" #if $tag and $tag.perm_id == $permission.id then 'selected' else ''#>$permission.name</option>
|
||||
#end for
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
#if $tag
|
||||
<button type="submit" name="save" value="Save">Save</button>
|
||||
#else
|
||||
<button type="submit" name="add" value="Add">Add</button>
|
||||
#end if
|
||||
</td>
|
||||
<td><button type="submit" name="cancel" value="Cancel">Cancel</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
136
www/kojiweb/taginfo.chtml
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for tag $tag.name</h4>
|
||||
|
||||
<table>
|
||||
#if $child and 'admin' in $perms
|
||||
<tr>
|
||||
<th colspan="2"><a href="tagparent?tagID=$child.id&parentID=$tag.id&action=add">Add $tag.name as parent of $child.name</a></th>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Name</th><td>$tag.name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th><td>$tag.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arches</th><td>$tag.arches</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Locked</th><td class="$str(not $tag.locked).lower()">#if $tag.locked then 'yes' else 'no'#</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Permission</th><td>#if $tag.perm_id then $allPerms[$tag.perm_id] else 'none'#</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Inheritance</th>
|
||||
<td class="tree">
|
||||
<span class="root">$tag.name</span>
|
||||
#set $numParents = $len($inheritance)
|
||||
#set $iter = 0
|
||||
#set $maxDepth = 0
|
||||
#set $TRUNC_DEPTH = 7
|
||||
<ul>
|
||||
#for $parent in $inheritance
|
||||
#set $iter += 1
|
||||
#set $nextDepth = ($iter < $numParents and $inheritance[$iter].currdepth or 1)
|
||||
#set $depth = $parent.currdepth
|
||||
#if $depth > $maxDepth
|
||||
#set $maxDepth = $depth
|
||||
#end if
|
||||
#if $depth == $TRUNC_DEPTH and not $all
|
||||
<li><span class="treeBranch"><span class="treeToggle treeLabel">...</span></span></li>
|
||||
<li class="hidden">
|
||||
#else if $len($tagsByChild[$parent.child_id]) > 1
|
||||
<li class="sibling">
|
||||
#else
|
||||
<li>
|
||||
#end if
|
||||
#silent $tagsByChild[$parent.child_id].pop()
|
||||
<span class="treeBranch">
|
||||
<span class="treeLabel">
|
||||
<a href="taginfo?tagID=$parent.parent_id">$parent.name</a>
|
||||
#if $depth == 1 and 'admin' in $perms
|
||||
<span class="treeLink">(<a href="tagparent?tagID=$tag.id&parentID=$parent.parent_id&action=edit">edit</a>) (<a href="tagparent?tagID=$tag.id&parentID=$parent.parent_id&action=remove">remove</a>)</span>
|
||||
#end if
|
||||
</span>
|
||||
</span>
|
||||
#if $nextDepth > $depth
|
||||
<ul>
|
||||
#else
|
||||
</li>
|
||||
#end if
|
||||
#while $nextDepth < $depth
|
||||
</ul>
|
||||
</li>
|
||||
#set $depth -= 1
|
||||
#end while
|
||||
#end for
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
#if $maxDepth >= $TRUNC_DEPTH
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
#if $all
|
||||
<a href="taginfo?tagID=$tag.id$util.passthrough($self, 'inherited')">Show abbreviated tree</a>
|
||||
#else
|
||||
<a href="taginfo?tagID=$tag.id$util.passthrough($self, 'inherited')&all=1">Show full tree</a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
#end if
|
||||
#if 'admin' in $perms
|
||||
<tr>
|
||||
<td colspan="2"><a href="tags?childID=$tag.id">Add parent</a></td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Repo created</th><td>#if $repo then $util.formatTimeRSS($repo.creation_time) else ''#</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Packages</th>
|
||||
<td><a href="packages?tagID=$tag.id">$numPackages</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Builds</th>
|
||||
<td><a href="builds?tagID=$tag.id">$numBuilds</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Targets building from this tag</th>
|
||||
<td>
|
||||
#if $len($srcTargets)
|
||||
#for $target in $srcTargets
|
||||
<a href="buildtargetinfo?name=$target.name">$target.name</a><br/>
|
||||
#end for
|
||||
#else
|
||||
No build targets
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Targets building to this tag</th>
|
||||
<td>
|
||||
#if $len($destTargets)
|
||||
#for $target in $destTargets
|
||||
<a href="buildtargetinfo?name=$target.name">$target.name</a><br/>
|
||||
#end for
|
||||
#else
|
||||
No build targets
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
#if 'admin' in $perms
|
||||
<tr>
|
||||
<td colspan="2"><a href="tagedit?tagID=$tag.id">Edit tag</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><a href="tagdelete?tagID=$tag.id">Delete tag</a></td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
71
www/kojiweb/tagparent.chtml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
#if $inheritanceData
|
||||
<h4>Edit Parent</h4>
|
||||
#else
|
||||
<h4>Add Parent</h4>
|
||||
#end if
|
||||
|
||||
<form action="tagparent">
|
||||
<input type="hidden" name="action" value="#if $inheritanceData then 'edit' else 'add'#"/>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Tag Name</th>
|
||||
<td>
|
||||
$tag.name
|
||||
<input type="hidden" name="tagID" value="$tag.id"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Parent Tag Name</th>
|
||||
<td>
|
||||
$parent.name
|
||||
<input type="hidden" name="parentID" value="$parent.id"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Priority</th>
|
||||
<td>
|
||||
<input type="text" name="priority" value="#if $inheritanceData then $inheritanceData.priority else $numParents#"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Max Depth</th>
|
||||
<td>
|
||||
<input type="text" name="maxdepth" value="#if $inheritanceData then $inheritanceData.maxdepth else ''#"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Intransitive</th>
|
||||
<td>
|
||||
<input type="checkbox" name="intransitive" value="yes" #if $inheritanceData and $inheritanceData.intransitive then 'checked' else ''#/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Packages Only</th>
|
||||
<td>
|
||||
<input type="checkbox" name="noconfig" value="yes" #if $inheritanceData and $inheritanceData.noconfig then 'checked' else ''#/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Package Filter</th>
|
||||
<td>
|
||||
<input type="text" name="pkg_filter" value="#if $inheritanceData then $inheritanceData.pkg_filter else ''#"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
#if $inheritanceData
|
||||
<button type="submit" name="save" value="Save">Save</button>
|
||||
#else
|
||||
<button type="submit" name="add" value="Add">Add</button>
|
||||
#end if
|
||||
</td>
|
||||
<td><button type="submit" name="cancel" value="Cancel">Cancel</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
76
www/kojiweb/tags.chtml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Tags</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if $len($tagPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tags?start=' + this.value * $tagRange + '$util.passthrough($self, 'userID', 'tagID', 'order', 'childID')';">
|
||||
#for $pageNum in $tagPages
|
||||
<option value="$pageNum"#if $pageNum == $tagCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $tagStart > 0
|
||||
<a href="tags?start=#echo $tagStart - $tagRange #$util.passthrough($self, 'userID', 'tagID', 'order', 'childID')"><<<</a>
|
||||
#end if
|
||||
#if $totalTags != 0
|
||||
<strong>Tags #echo $tagStart + 1 # through #echo $tagStart + $tagCount # of $totalTags</strong>
|
||||
#end if
|
||||
#if $tagStart + $tagCount < $totalTags
|
||||
<a href="tags?start=#echo $tagStart + $tagRange#$util.passthrough($self, 'userID', 'tagID', 'order', 'childID')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="tags?order=$util.toggleOrder($self, 'id')">ID</a> $util.sortImage($self, 'id')</th>
|
||||
<th><a href="tags?order=$util.toggleOrder($self, 'name')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
</tr>
|
||||
#if $len($tags) > 0
|
||||
#for $tag in $tags
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>$tag.id</td>
|
||||
<td><a href="taginfo?tagID=$tag.id$util.passthrough($self, 'childID')">$tag.name</a></td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="2">No tags</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="2">
|
||||
#if $len($tagPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tags?start=' + this.value * $tagRange + '$util.passthrough($self, 'userID', 'tagID', 'order', 'childID')';">
|
||||
#for $pageNum in $tagPages
|
||||
<option value="$pageNum"#if $pageNum == $tagCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $tagStart > 0
|
||||
<a href="tags?start=#echo $tagStart - $tagRange #$util.passthrough($self, 'userID', 'tagID', 'order', 'childID')"><<<</a>
|
||||
#end if
|
||||
#if $totalTags != 0
|
||||
<strong>Tags #echo $tagStart + 1 # through #echo $tagStart + $tagCount # of $totalTags</strong>
|
||||
#end if
|
||||
#if $tagStart + $tagCount < $totalTags
|
||||
<a href="tags?start=#echo $tagStart + $tagRange#$util.passthrough($self, 'userID', 'tagID', 'order', 'childID')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#if 'admin' in $perms
|
||||
<br/>
|
||||
<a href="tagcreate">Create new Tag</a>
|
||||
#end if
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
289
www/kojiweb/taskinfo.chtml
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
#import koji
|
||||
#from kojiweb import util
|
||||
|
||||
#def printValue($key, $value, $sep=', ')
|
||||
#if $key == 'brootid'
|
||||
<a href="buildrootinfo?buildrootID=$value">$value</a>
|
||||
#else
|
||||
#if $isinstance($value, list) then $sep.join([$str($val) for $val in $value]) else $value#
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#def printMap($vals, $prefix='')
|
||||
#for $key, $value in $vals.items()
|
||||
#if $key != '__starstar'
|
||||
$prefix$key = $printValue($key, $value)<br/>
|
||||
#end if
|
||||
#end for
|
||||
#end def
|
||||
|
||||
#def printOpts($opts)
|
||||
#if $opts
|
||||
<strong>Options:</strong><br/>
|
||||
$printMap($opts, ' ')
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#def printChildren($taskID, $childMap)
|
||||
#set $iter = 0
|
||||
#set $children = $childMap[$str($taskID)]
|
||||
#if $children
|
||||
<ul>
|
||||
#for $child in $children
|
||||
#set $iter += 1
|
||||
#if $iter < $len($children)
|
||||
<li class="sibling">
|
||||
#else
|
||||
<li>
|
||||
#end if
|
||||
#set $childState = $util.taskState($child.state)
|
||||
<span class="treeBranch">
|
||||
<span class="treeLabel">
|
||||
<a href="taskinfo?taskID=$child.id" class="task$childState" title="$childState">$koji.taskLabel($child)</a>
|
||||
</span>
|
||||
</span>
|
||||
$printChildren($child.id, $childMap)
|
||||
</li>
|
||||
#end for
|
||||
</ul>
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for task $task.id</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th><td>$task.method</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Parameters</th>
|
||||
<td>
|
||||
#if $task.method == 'buildFromCVS'
|
||||
<strong>CVS URL:</strong> $params[0]<br/>
|
||||
<strong>Build Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a>
|
||||
#elif $task.method == 'buildSRPMFromCVS'
|
||||
<strong>CVS URL:</strong> $params[0]
|
||||
#elif $task.method == 'multiArchBuild'
|
||||
<strong>SRPM:</strong> $params[0]<br/>
|
||||
<strong>Build Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a><br/>
|
||||
$printOpts($params[2])
|
||||
#elif $task.method == 'buildArch'
|
||||
<strong>SRPM:</strong> $params[0]<br/>
|
||||
<strong>Build Tag:</strong> <a href="taginfo?tagID=$buildTag.id">$buildTag.name</a><br/>
|
||||
<strong>Arch:</strong> $params[2]<br/>
|
||||
<strong>Keep SRPM?</strong> #if $params[3] then 'yes' else 'no'#<br/>
|
||||
#if $len($params) > 4
|
||||
$printOpts($params[4])
|
||||
#end if
|
||||
#elif $task.method == 'tagBuild'
|
||||
<strong>Destination Tag:</strong> <a href="taginfo?tagID=$destTag.id">$destTag.name</a><br/>
|
||||
<strong>Build:</strong> <a href="buildinfo?buildID=$build.id">$koji.buildLabel($build)</a>
|
||||
#elif $task.method == 'buildNotification'
|
||||
#set $build = $params[1]
|
||||
#set $buildTarget = $params[2]
|
||||
<strong>Recipients:</strong> $printValue('', $params[0])<br/>
|
||||
<strong>Build:</strong> <a href="buildinfo?buildID=$build.id">$koji.buildLabel($build)</a><br/>
|
||||
<strong>Build Target:</strong> <a href="buildtargetinfo?targetID=$buildTarget.id">$buildTarget.name</a><br/>
|
||||
<strong>Web URL:</strong> <a href="$params[3]">$params[3]</a>
|
||||
#elif $task.method == 'tagNotification'
|
||||
<strong>Recipients:</strong> $printValue('', $params[0])<br/>
|
||||
<strong>Successful?:</strong> #if $params[1] then 'yes' else 'no'#<br/>
|
||||
<strong>Tagged Into:</strong> <a href="taginfo?tagID=$destTag.id">$destTag.name</a><br/>
|
||||
#if $srcTag
|
||||
<strong>Moved From:</strong> <a href="taginfo?tagID=$srcTag.id">$srcTag.name</a><br/>
|
||||
#end if
|
||||
<strong>Build:</strong> <a href="buildinfo?buildID=$build.id">$koji.buildLabel($build)</a><br/>
|
||||
<strong>Tagged By:</strong> <a href="userinfo?userID=$user.id">$user.name</a><br/>
|
||||
<strong>Ignore Success?:</strong> #if $params[6] then 'yes' else 'no'#<br/>
|
||||
#if $params[7]
|
||||
<strong>Failure Message:</strong> $params[7]
|
||||
#end if
|
||||
#elif $task.method == 'build'
|
||||
<strong>Source:</strong> $params[0]<br/>
|
||||
<strong>Build Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a><br/>
|
||||
$printOpts($params[2])
|
||||
#elif $task.method == 'runroot'
|
||||
<strong>Tag:</strong> <a href="taginfo?tagID=$params[0]">$params[0]</a><br/>
|
||||
<strong>Arch:</strong> $params[1]<br/>
|
||||
<strong>Command:</strong> $printValue('', $params[2], ' ')<br/>
|
||||
#if $len($params) > 3
|
||||
$printOpts($params[3])
|
||||
#end if
|
||||
#elif $task.method == 'newRepo'
|
||||
<strong>Tag:</strong> <a href="taginfo?tagID=$tag.id">$tag.name</a>
|
||||
#elif $task.method == 'prepRepo'
|
||||
<strong>Tag:</strong> <a href="taginfo?tagID=$params[0].id">$params[0].name</a>
|
||||
#elif $task.method == 'createrepo'
|
||||
<strong>Repo ID:</strong> $params[0]<br/>
|
||||
<strong>Arch:</strong> $params[1]<br/>
|
||||
#set $oldrepo = $params[2]
|
||||
#if $oldrepo
|
||||
<strong>Old Repo ID:</strong> $oldrepo.id<br/>
|
||||
<strong>Old Repo Creation:</strong> $koji.formatTimeLong($oldrepo.creation_time)
|
||||
#end if
|
||||
#elif $task.method == 'dependantTask'
|
||||
<strong>Dependant Tasks:</strong><br/>
|
||||
#for $dep in $deps
|
||||
<a href="taskinfo?taskID=$dep.id" class="task$util.taskState($dep.state)">$koji.taskLabel($dep)</a><br/>
|
||||
#end for
|
||||
<strong>Subtasks:</strong><br/>
|
||||
#for $subtask in $params[1]
|
||||
<strong>Method:</strong> $subtask[0]<br/>
|
||||
<strong>Parameters:</strong> #echo ', '.join([$str($subparam) for $subparam in $subtask[1]])#<br/>
|
||||
#if $len($subtask) > 2 and $subtask[2]
|
||||
<strong>Options:</strong><br/>
|
||||
$printMap($subtask[2], ' ')
|
||||
#end if
|
||||
<br/>
|
||||
#end for
|
||||
#elif $task.method == 'chainbuild'
|
||||
<strong>Build Groups:</strong><br/>
|
||||
#set $groupNum = 0
|
||||
#for $urls in $params[0]
|
||||
#set $groupNum += 1
|
||||
<strong>$groupNum</strong>: #echo ', '.join($urls)#<br/>
|
||||
#end for
|
||||
<strong>Build Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a><br/>
|
||||
$printOpts($params[2])
|
||||
#elif $task.method == 'waitrepo'
|
||||
<strong>Build Target:</strong> $params[0]<br/>
|
||||
#if $params[1]
|
||||
<strong>Newer Than:</strong> $params[1]<br/>
|
||||
#end if
|
||||
#if $params[2]
|
||||
<strong>NVRs:</strong> $printValue('', $params[2])
|
||||
#end if
|
||||
#else
|
||||
$params
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
#set $state = $util.taskState($task.state)
|
||||
<th>State</th>
|
||||
<td class="task$state">$state
|
||||
#if $currentUser and ('admin' in $perms or $task.owner == $currentUser.id)
|
||||
#if $task.state in ($koji.TASK_STATES.FREE, $koji.TASK_STATES.OPEN, $koji.TASK_STATES.ASSIGNED)
|
||||
<span class="adminLink">(<a href="canceltask?taskID=$task.id">cancel</a>)</span>
|
||||
#elif $task.state in ($koji.TASK_STATES.CANCELED, $koji.TASK_STATES.FAILED) and (not $parent)
|
||||
<span class="adminLink">(<a href="resubmittask?taskID=$task.id">resubmit</a>)</span>
|
||||
#end if
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
#if $taskBuild
|
||||
<tr>
|
||||
<th>Build</th><td><a href="buildinfo?buildID=$taskBuild.build_id">$koji.buildLabel($taskBuild)</a></td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Created</th><td>$util.formatTimeLong($task.create_time)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Completed</th><td>$util.formatTimeLong($task.completion_time)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Owner</th>
|
||||
<td>
|
||||
#if $owner
|
||||
#if $owner.usertype == $koji.USERTYPES['HOST']
|
||||
<a href="hostinfo?userID=$owner.id">$owner.name</a>
|
||||
#else
|
||||
<a href="userinfo?userID=$owner.id">$owner.name</a>
|
||||
#end if
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Channel</th>
|
||||
<td>
|
||||
#if $task.channel_id
|
||||
<a href="channelinfo?channelID=$task.channel_id">$channelName</a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Host</th>
|
||||
<td>
|
||||
#if $task.host_id
|
||||
<a href="hostinfo?hostID=$task.host_id">$hostName</a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arch</th><td>$task.arch</td>
|
||||
</tr>
|
||||
#if $buildroots
|
||||
<tr>
|
||||
<th>Buildroot#if $len($buildroots) > 1 then 's' else ''#</th>
|
||||
<td>
|
||||
#for $buildroot in $buildroots
|
||||
<a href="buildrootinfo?buildrootID=$buildroot.id">/var/lib/mock/$buildroot.tag_name-$buildroot.id-$buildroot.repo_id</a><br/>
|
||||
#end for
|
||||
</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Parent</th>
|
||||
<td>
|
||||
#if $parent
|
||||
<a href="taskinfo?taskID=$parent.id" class="task$util.taskState($parent.state)">$koji.taskLabel($parent)</a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Descendent Tasks</th>
|
||||
<td class="tree">
|
||||
#if $len($descendents[$str($task.id)]) > 0
|
||||
<span class="root">$task.method</span>
|
||||
#end if
|
||||
$printChildren($task.id, $descendents)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Waiting?</th><td>#if $task.waiting then 'yes' else 'no'#</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Awaited?</th><td>#if $task.awaited then 'yes' else 'no'#</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Priority</th><td>$task.priority</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Weight</th><td>$task.weight</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Result</th>
|
||||
<td>
|
||||
#if $excClass
|
||||
#if $hasattr($result, 'faultString')
|
||||
<pre>
|
||||
$result.faultString.strip()
|
||||
</pre>
|
||||
#else
|
||||
${excClass.__name__}: $result
|
||||
#end if
|
||||
#elif $isinstance($result, dict)
|
||||
$printMap($result)
|
||||
#else
|
||||
$printValue('', $result)
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Output</th>
|
||||
<td>
|
||||
#for $filename in $output
|
||||
<a href="getfile?taskID=$task.id&name=$filename">$filename</a><br/>
|
||||
#end for
|
||||
#if $task.state not in ($koji.TASK_STATES.CLOSED, $koji.TASK_STATES.CANCELED, $koji.TASK_STATES.FAILED) and \
|
||||
$task.method in ('buildSRPMFromCVS', 'buildArch', 'runroot')
|
||||
<a href="watchlogs?taskID=$task.id">Watch logs</a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
206
www/kojiweb/tasks.chtml
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#import koji
|
||||
#from kojiweb import util
|
||||
|
||||
#def printChildren($taskID, $childMap)
|
||||
#set $iter = 0
|
||||
#set $children = $childMap[$str($taskID)]
|
||||
#if $children
|
||||
<ul>
|
||||
#for $child in $children
|
||||
#set $iter += 1
|
||||
#if $iter < $len($children)
|
||||
<li class="sibling">
|
||||
#else
|
||||
<li>
|
||||
#end if
|
||||
#set $childState = $util.taskState($child.state)
|
||||
<span class="treeBranch">
|
||||
<span class="treeLabel">
|
||||
<a href="taskinfo?taskID=$child.id" class="task$childState" title="$childState">$koji.taskLabel($child)</a>
|
||||
</span>
|
||||
</span>
|
||||
$printChildren($child.id, $childMap)
|
||||
</li>
|
||||
#end for
|
||||
</ul>
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#def headerPrefix($state)
|
||||
#if $state == 'active'
|
||||
Active
|
||||
#elif $state == 'toplevel'
|
||||
Top-level
|
||||
#elif $state == 'all'
|
||||
All
|
||||
#else
|
||||
#echo $state.capitalize()
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>$headerPrefix($state) #if $method != 'all' then $method else ''# Tasks#if $ownerObj then ' owned by <a href="userinfo?userID=%i">%s</a>' % ($ownerObj.id, $ownerObj.name) else ''##if $host then ' on host <a href="hostinfo?hostID=%i">%s</a>' % ($host.id, $host.name) else ''#</h4>
|
||||
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<form action="tasks">
|
||||
<strong>State</strong>:
|
||||
#if $state == 'active'
|
||||
<strong>active</strong>
|
||||
#else
|
||||
<a href="tasks?${util.passthrough($self, 'owner', 'method', 'hostID', 'order')[1:]}">active</a>
|
||||
#end if
|
||||
|
|
||||
#if $state == 'toplevel'
|
||||
<strong>toplevel</strong>
|
||||
#else
|
||||
<a href="tasks?state=toplevel$util.passthrough($self, 'owner', 'method', 'hostID', 'order')">toplevel</a>
|
||||
#end if
|
||||
|
|
||||
#if $state == 'all'
|
||||
<strong>all</strong>
|
||||
#else
|
||||
<a href="tasks?state=all$util.passthrough($self, 'owner', 'method', 'hostID', 'order')">all</a>
|
||||
#end if
|
||||
|
|
||||
<select name="state" class="filterlist" onchange="javascript: window.location = 'tasks?state=' + this.value + '$util.passthrough($self, 'owner', 'hostID', 'method', 'order')';">
|
||||
<option value="active">(select)</option>
|
||||
<option value="free" #if $state == 'free' then 'selected="selected"' else ''#>free</option>
|
||||
<option value="open" #if $state == 'open' then 'selected="selected"' else ''#>open</option>
|
||||
<option value="closed" #if $state == 'closed' then 'selected="selected"' else ''#>closed</option>
|
||||
<option value="failed" #if $state == 'failed' then 'selected="selected"' else ''#>failed</option>
|
||||
<option value="canceled" #if $state == 'canceled' then 'selected="selected"' else ''#>canceled</option>
|
||||
<option value="assigned" #if $state == 'assigned' then 'selected="selected"' else ''#>assigned</option>
|
||||
</select>
|
||||
<br/>
|
||||
<strong>Owner</strong>:
|
||||
#if $ownerObj
|
||||
<a href="tasks?${util.passthrough($self, 'state', 'method', 'hostID', 'order')[1:]}">everyone</a>
|
||||
#if $loggedInUser
|
||||
#if $ownerObj.id == $loggedInUser.id
|
||||
| <strong>me</strong>
|
||||
#else
|
||||
| <a href="tasks?owner=$loggedInUser.name$util.passthrough($self, 'state', 'method', 'hostID', 'order')">me</a>
|
||||
#end if
|
||||
#end if
|
||||
#else
|
||||
<strong>everyone</strong>
|
||||
#if $loggedInUser
|
||||
| <a href="tasks?owner=$loggedInUser.name$util.passthrough($self, 'state', 'method', 'hostID', 'order')">me</a>
|
||||
#end if
|
||||
#end if
|
||||
|
|
||||
<select name="owner" class="filterlist" onchange="javascript: window.location = 'tasks?owner=' + this.value + '$util.passthrough($self, 'hostID', 'state', 'method', 'order')';">
|
||||
<option value="">(select)</option>
|
||||
#for $user in $users
|
||||
<option value="$user.name" #if $user.name == $owner then 'selected="selected"' else ''#>$user.name</option>
|
||||
#end for
|
||||
</select>
|
||||
<br/>
|
||||
<strong>Method</strong>:
|
||||
<select name="method" class="filterlist" onchange="javascript: window.location = 'tasks?method=' + this.value + '$util.passthrough($self, 'owner', 'hostID', 'state', 'order')';">
|
||||
<option value="all" #if $method == 'all' then 'selected="selected"' else ''#>all</option>
|
||||
<option value="build" #if $method == 'build' then 'selected="selected"' else ''#>build</option>
|
||||
<option value="buildSRPMFromCVS" #if $method == 'buildSRPMFromCVS' then 'selected="selected"' else ''#>buildSRPMFromCVS</option>
|
||||
<option value="buildArch" #if $method == 'buildArch' then 'selected="selected"' else ''#>buildArch</option>
|
||||
<option value="buildNotification" #if $method == 'buildNotification' then 'selected="selected"' else ''#>buildNotification</option>
|
||||
<option value="tagBuild" #if $method == 'tagBuild' then 'selected="selected"' else ''#>tagBuild</option>
|
||||
<option value="tagNotification" #if $method == 'tagNotification' then 'selected="selected"' else ''#>tagNotification</option>
|
||||
<option value="runroot" #if $method == 'runroot' then 'selected="selected"' else ''#>runroot</option>
|
||||
<option value="newRepo" #if $method == 'newRepo' then 'selected="selected"' else ''#>newRepo</option>
|
||||
<option value="prepRepo" #if $method == 'prepRepo' then 'selected="selected"' else ''#>prepRepo</option>
|
||||
<option value="createrepo" #if $method == 'createrepo' then 'selected="selected"' else ''#>createrepo</option>
|
||||
<option value="dependantTask" #if $method == 'dependantTask' then 'selected="selected"' else ''#>dependantTask</option>
|
||||
<option value="chainbuild" #if $method == 'chainbuild' then 'selected="selected"' else ''#>chainbuild</option>
|
||||
<option value="waitrepo" #if $method == 'waitrepo' then 'selected="selected"' else ''#>waitrepo</option>
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paginate" colspan="6">
|
||||
#if $len($taskPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tasks?start=' + this.value * $taskRange + '$util.passthrough($self, 'owner', 'hostID', 'state', 'method', 'order')';">
|
||||
#for $pageNum in $taskPages
|
||||
<option value="$pageNum"#if $pageNum == $taskCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $taskStart > 0
|
||||
<a href="tasks?start=#echo $taskStart - $taskRange #$util.passthrough($self, 'owner', 'hostID', 'state', 'method', 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalTasks != 0
|
||||
<strong>Tasks #echo $taskStart + 1 # through #echo $taskStart + $taskCount # of $totalTasks</strong>
|
||||
#end if
|
||||
#if $taskStart + $taskCount < $totalTasks
|
||||
<a href="tasks?start=#echo $taskStart + $taskRange#$util.passthrough($self, 'owner', 'hostID', 'state', 'method', 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="tasks?order=$util.toggleOrder($self, 'id')$util.passthrough($self, 'owner', 'hostID', 'state', 'method')">ID</a> $util.sortImage($self, 'id')</th>
|
||||
<th><a href="tasks?order=$util.toggleOrder($self, 'method')$util.passthrough($self, 'owner', 'hostID', 'state', 'method')">Type</a> $util.sortImage($self, 'method')</th>
|
||||
<th><a href="tasks?order=$util.toggleOrder($self, 'owner')$util.passthrough($self, 'owner', 'hostID', 'state', 'method')">Owner</a> $util.sortImage($self, 'owner')</th>
|
||||
<th><a href="tasks?order=$util.toggleOrder($self, 'arch')$util.passthrough($self, 'owner', 'hostID', 'state', 'method')">Arch</a> $util.sortImage($self, 'arch')</th>
|
||||
<th><a href="tasks?order=$util.toggleOrder($self, 'completion_time')$util.passthrough($self, 'owner', 'hostID', 'state', 'method')">Finished</a> $util.sortImage($self, 'completion_time')</th>
|
||||
<th><a href="tasks?order=$util.toggleOrder($self, 'state')$util.passthrough($self, 'owner', 'hostID', 'state', 'method')">State</a> $util.sortImage($self, 'state')</th>
|
||||
</tr>
|
||||
#if $len($tasks) > 0
|
||||
#for $task in $tasks
|
||||
<tr class="$util.rowToggle($self)">
|
||||
#set $taskState = $util.taskState($task.state)
|
||||
<td>$task.id</td>
|
||||
<td#if $treeDisplay then ' class="tree"' else ''#>
|
||||
#if $treeDisplay then ' ' else ''#<a href="taskinfo?taskID=$task.id" class="task$taskState" title="$taskState">$koji.taskLabel($task)</a>
|
||||
#if $treeDisplay
|
||||
$printChildren($task.id, $task.descendents)
|
||||
#end if
|
||||
</td>
|
||||
<td>
|
||||
#if $task.owner_type == $koji.USERTYPES['HOST']
|
||||
<a href="hostinfo?userID=$task.owner">$task.owner_name</a>
|
||||
#else
|
||||
<a href="userinfo?userID=$task.owner">$task.owner_name</a>
|
||||
#end if
|
||||
</td>
|
||||
<td>$task.arch</td>
|
||||
<td>$util.formatTime($task.completion_time)</td>
|
||||
<td class="task$state">$util.imageTag($taskState)</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="6">No tasks</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="6">
|
||||
#if $len($taskPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tasks?start=' + this.value * $taskRange + '$util.passthrough($self, 'owner', 'hostID', 'state', 'method', 'order')';">
|
||||
#for $pageNum in $taskPages
|
||||
<option value="$pageNum"#if $pageNum == $taskCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $taskStart > 0
|
||||
<a href="tasks?start=#echo $taskStart - $taskRange #$util.passthrough($self, 'owner', 'hostID', 'state', 'method', 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalTasks != 0
|
||||
<strong>Tasks #echo $taskStart + 1 # through #echo $taskStart + $taskCount # of $totalTasks</strong>
|
||||
#end if
|
||||
#if $taskStart + $taskCount < $totalTasks
|
||||
<a href="tasks?start=#echo $taskStart + $taskRange#$util.passthrough($self, 'owner', 'hostID', 'state', 'method', 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
89
www/kojiweb/tasksbyhost.chtml
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Tasks by Host#if $hostArch then ' (%s)' % $hostArch else ''#</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="archlist" colspan="3">
|
||||
<strong>Host arch:</strong> #for $arch in $hostArchList
|
||||
#if $arch == $hostArch
|
||||
<strong>$arch</strong> |
|
||||
#else
|
||||
<a href="tasksbyhost?hostArch=$arch$util.passthrough($self, 'order')">$arch</a> |
|
||||
#end if
|
||||
#end for
|
||||
#if $hostArch
|
||||
<a href="tasksbyhost?${util.passthrough($self, 'order')[1:]}">all</a>
|
||||
#else
|
||||
<strong>all</strong>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($hostPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tasksbyhost?start=' + this.value * $hostRange + '$util.passthrough($self, 'order', 'hostArch')';">
|
||||
#for $pageNum in $hostPages
|
||||
<option value="$pageNum"#if $pageNum == $hostCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $hostStart > 0
|
||||
<a href="tasksbyhost?start=#echo $hostStart - $hostRange #$util.passthrough($self, 'order', 'hostArch')"><<<</a>
|
||||
#end if
|
||||
#if $totalHosts != 0
|
||||
<strong>Hosts #echo $hostStart + 1 # through #echo $hostStart + $hostCount # of $totalHosts</strong>
|
||||
#end if
|
||||
#if $hostStart + $hostCount < $totalHosts
|
||||
<a href="tasksbyhost?start=#echo $hostStart + $hostRange#$util.passthrough($self, 'order', 'hostArch')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="tasksbyhost?order=$util.toggleOrder($self, 'name')$util.passthrough($self, 'hostArch')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th><a href="tasksbyhost?order=$util.toggleOrder($self, 'tasks')$util.passthrough($self, 'hostArch')">Tasks</a> $util.sortImage($self, 'tasks')</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
#if $len($hosts) > 0
|
||||
#for $host in $hosts
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="hostinfo?hostID=$host.id">$host.name</a></td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $host.tasks#" height="15" class="graphrow"/></td>
|
||||
<td>$host.tasks</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No hosts</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($hostPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tasksbyhost?start=' + this.value * $hostRange + '$util.passthrough($self, 'order', 'hostArch')';">
|
||||
#for $pageNum in $hostPages
|
||||
<option value="$pageNum"#if $pageNum == $hostCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $hostStart > 0
|
||||
<a href="tasksbyhost?start=#echo $hostStart - $hostRange #$util.passthrough($self, 'order', 'hostArch')"><<<</a>
|
||||
#end if
|
||||
#if $totalHosts != 0
|
||||
<strong>Hosts #echo $hostStart + 1 # through #echo $hostStart + $hostCount # of $totalHosts</strong>
|
||||
#end if
|
||||
#if $hostStart + $hostCount < $totalHosts
|
||||
<a href="tasksbyhost?start=#echo $hostStart + $hostRange#$util.passthrough($self, 'order', 'hostArch')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
73
www/kojiweb/tasksbyuser.chtml
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Tasks by User</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($userPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tasksbyuser?start=' + this.value * $userRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userPages
|
||||
<option value="$pageNum"#if $pageNum == $userCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userStart > 0
|
||||
<a href="tasksbyuser?start=#echo $userStart - $userRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUsers != 0
|
||||
<strong>Users #echo $userStart + 1 # through #echo $userStart + $userCount # of $totalUsers</strong>
|
||||
#end if
|
||||
#if $userStart + $userCount < $totalUsers
|
||||
<a href="tasksbyuser?start=#echo $userStart + $userRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="tasksbyuser?order=$util.toggleOrder($self, 'name')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th><a href="tasksbyuser?order=$util.toggleOrder($self, 'tasks')">Tasks</a> $util.sortImage($self, 'tasks')</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
#if $len($users) > 0
|
||||
#for $user in $users
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="userinfo?userID=$user.id">$user.name</a></td>
|
||||
<td width="#echo $graphWidth + 5#"><img src="/koji-static/images/1px.gif" width="#echo $increment * $user.tasks#" height="15" class="graphrow"/></td>
|
||||
<td>$user.tasks</td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="3">No users</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($userPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'tasksbyuser?start=' + this.value * $userRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userPages
|
||||
<option value="$pageNum"#if $pageNum == $userCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userStart > 0
|
||||
<a href="tasksbyuser?start=#echo $userStart - $userRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUsers != 0
|
||||
<strong>Users #echo $userStart + 1 # through #echo $userStart + $userCount # of $totalUsers</strong>
|
||||
#end if
|
||||
#if $userStart + $userCount < $totalUsers
|
||||
<a href="tasksbyuser?start=#echo $userStart + $userRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
108
www/kojiweb/userinfo.chtml
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for user $user.name</h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th><td>$user.name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ID</th><td>$user.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tasks</th><td><a href="tasks?owner=$user.id">view</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id="packagelist">Packages</th>
|
||||
<td class="container">
|
||||
#if $len($packages) > 0
|
||||
<table class="nested data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($packagePages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'userinfo?packageStart=' + this.value * $packageRange + '$util.passthrough($self, 'userID', 'packageOrder', 'buildOrder', 'buildStart')#packagelist';">
|
||||
#for $pageNum in $packagePages
|
||||
<option value="$pageNum"#if $pageNum == $packageCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $packageStart > 0
|
||||
<a href="userinfo?packageStart=#echo $packageStart - $packageRange #$util.passthrough($self, 'userID', 'packageOrder', 'buildOrder', 'buildStart')#packagelist"><<<</a>
|
||||
#end if
|
||||
<strong>#echo $packageStart + 1 # through #echo $packageStart + $packageCount # of $totalPackages</strong>
|
||||
#if $packageStart + $packageCount < $totalPackages
|
||||
<a href="userinfo?packageStart=#echo $packageStart + $packageRange#$util.passthrough($self, 'userID', 'packageOrder', 'buildOrder', 'buildStart')#packagelist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="userinfo?packageOrder=$util.toggleOrder($self, 'package_name', 'packageOrder')$util.passthrough($self, 'userID', 'buildOrder', 'buildStart')#packagelist">Name</a> $util.sortImage($self, 'package_name', 'packageOrder')</th>
|
||||
<th><a href="userinfo?packageOrder=$util.toggleOrder($self, 'tag_name', 'packageOrder')$util.passthrough($self, 'userID', 'buildOrder', 'buildStart')#packagelist">Tag</a> $util.sortImage($self, 'tag_name', 'packageOrder')</th>
|
||||
<th><a href="userinfo?packageOrder=$util.toggleOrder($self, 'blocked', 'packageOrder')$util.passthrough($self, 'userID', 'buildOrder', 'buildStart')#packagelist">Included?</a> $util.sortImage($self, 'blocked', 'packageOrder')</th>
|
||||
</tr>
|
||||
#for $package in $packages
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="packageinfo?packageID=$package.package_id">$package.package_name</a></td>
|
||||
<td><a href="taginfo?tagID=$package.tag_id">$package.tag_name</a></td>
|
||||
<td class="$str(not $package.blocked).lower()">#if $package.blocked then $util.imageTag('no') else $util.imageTag('yes')#</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No packages
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id="buildlist">Builds</th>
|
||||
<td class="container">
|
||||
#if $len($builds) > 0
|
||||
<table class="nested data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="3">
|
||||
#if $len($buildPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'userinfo?buildStart=' + this.value * $buildRange + '$util.passthrough($self, 'userID', 'buildOrder', 'packageOrder', 'packageStart')#buildlist';">
|
||||
#for $pageNum in $buildPages
|
||||
<option value="$pageNum"#if $pageNum == $buildCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $buildStart > 0
|
||||
<a href="userinfo?buildStart=#echo $buildStart - $buildRange #$util.passthrough($self, 'userID', 'buildOrder', 'packageOrder', 'packageStart')#buildlist"><<<</a>
|
||||
#end if
|
||||
<strong>#echo $buildStart + 1 # through #echo $buildStart + $buildCount # of $totalBuilds</strong>
|
||||
#if $buildStart + $buildCount < $totalBuilds
|
||||
<a href="userinfo?buildStart=#echo $buildStart + $buildRange#$util.passthrough($self, 'userID', 'buildOrder', 'packageOrder', 'packageStart')#buildlist">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="userinfo?buildOrder=$util.toggleOrder($self, 'nvr', 'buildOrder')$util.passthrough($self, 'userID', 'packageOrder', 'packageStart')#buildlist">NVR</a> $util.sortImage($self, 'nvr', 'buildOrder')</th>
|
||||
<th><a href="userinfo?buildOrder=$util.toggleOrder($self, 'completion_time', 'buildOrder')$util.passthrough($self, 'userID', 'packageOrder', 'packageStart')#buildlist">Finished</a> $util.sortImage($self, 'completion_time', 'buildOrder')</th>
|
||||
<th><a href="userinfo?buildOrder=$util.toggleOrder($self, 'state', 'buildOrder')$util.passthrough($self, 'userID', 'packageOrder', 'packageStart')#buildlist">State</a> $util.sortImage($self, 'state', 'buildOrder')</th>
|
||||
</tr>
|
||||
#for $build in $builds
|
||||
<tr class="$util.rowToggle($self)">
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
<td><a href="buildinfo?buildID=$build.build_id">$build.nvr</a></td>
|
||||
<td>$util.formatTime($build.completion_time)</td>
|
||||
<td class="$stateName">$util.stateImage($build.state)</td>
|
||||
</tr>
|
||||
#end for
|
||||
</table>
|
||||
#else
|
||||
No builds
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
77
www/kojiweb/users.chtml
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Users</h4>
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="5">
|
||||
#if $len($userPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'users?start=' + this.value * $userRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userPages
|
||||
<option value="$pageNum"#if $pageNum == $userCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userStart > 0
|
||||
<a href="users?start=#echo $userStart - $userRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUsers != 0
|
||||
<strong>Users #echo $userStart + 1 # through #echo $userStart + $userCount # of $totalUsers</strong>
|
||||
#end if
|
||||
#if $userStart + $userCount < $totalUsers
|
||||
<a href="users?start=#echo $userStart + $userRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="users?order=$util.toggleOrder($self, 'id')">ID</a> $util.sortImage($self, 'id')</th>
|
||||
<th><a href="users?order=$util.toggleOrder($self, 'name')">Name</a> $util.sortImage($self, 'name')</th>
|
||||
<th>Packages</th>
|
||||
<th>Builds</th>
|
||||
<th>Tasks</th>
|
||||
</tr>
|
||||
#if $len($users) > 0
|
||||
#for $user in $users
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td>$user.id</td>
|
||||
<td><a href="userinfo?userID=$user.id">$user.name</a></td>
|
||||
<td><a href="packages?userID=$user.id">view</a></td>
|
||||
<td><a href="builds?userID=$user.id">view</a></td>
|
||||
<td><a href="tasks?owner=$user.id">view</a></td>
|
||||
</tr>
|
||||
#end for
|
||||
#else
|
||||
<tr class="row-odd">
|
||||
<td colspan="5">No users</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<td class="paginate" colspan="5">
|
||||
#if $len($userPages) > 1
|
||||
<form class="pageJump">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'users?start=' + this.value * $userRange + '$util.passthrough($self, 'order')';">
|
||||
#for $pageNum in $userPages
|
||||
<option value="$pageNum"#if $pageNum == $userCurrentPage then ' selected' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
</select>
|
||||
</form>
|
||||
#end if
|
||||
#if $userStart > 0
|
||||
<a href="users?start=#echo $userStart - $userRange #$util.passthrough($self, 'order')"><<<</a>
|
||||
#end if
|
||||
#if $totalUsers != 0
|
||||
<strong>Users #echo $userStart + 1 # through #echo $userStart + $userCount # of $totalUsers</strong>
|
||||
#end if
|
||||
#if $userStart + $userCount < $totalUsers
|
||||
<a href="users?start=#echo $userStart + $userRange#$util.passthrough($self, 'order')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
20
www/lib/Makefile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
SUBDIRS = kojiweb
|
||||
|
||||
SERVERDIR = /var/www/koji-web/lib
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
for d in $(SUBDIRS); do make -s -C $$d clean; done
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
for d in $(SUBDIRS); do make DESTDIR=$(DESTDIR)/$(SERVERDIR) \
|
||||
-C $$d install; [ $$? = 0 ] || exit 1; done
|
||||
30
www/lib/kojiweb/Makefile
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
PYTHON=python
|
||||
PACKAGE = $(shell basename `pwd`)
|
||||
PYFILES = $(wildcard *.py)
|
||||
PYVER := $(shell $(PYTHON) -c 'import sys; print "%.3s" %(sys.version)')
|
||||
PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print sys.prefix')
|
||||
PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)
|
||||
PKGDIR = $(PYLIBDIR)/site-packages/$(PACKAGE)
|
||||
|
||||
SERVERDIR = /kojiweb
|
||||
FILES = $(wildcard *.py *.chtml)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
for d in $(SUBDIRS); do make -s -C $$d clean; done
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/$(SERVERDIR)
|
||||
for p in $(PYFILES) ; do \
|
||||
install -m 644 $$p $(DESTDIR)/$(SERVERDIR)/$$p; \
|
||||
done
|
||||
$(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(SERVERDIR)', 1, '$(PYDIR)', 1)"
|
||||
1
www/lib/kojiweb/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# identify this directory as a python module
|
||||
7
www/lib/kojiweb/handlers.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
def cleanup(req):
|
||||
"""Perform any cleanup actions required at the end of a request.
|
||||
At the moment, this logs out the webserver <-> koji session."""
|
||||
if hasattr(req, '_session') and req._session.logged_in:
|
||||
req._session.logout()
|
||||
|
||||
return 0
|
||||
276
www/lib/kojiweb/util.py
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
import time
|
||||
import koji
|
||||
|
||||
def toggleOrder(template, sortKey, orderVar='order'):
|
||||
"""
|
||||
If orderVar equals 'sortKey', return '-sortKey', else
|
||||
return 'sortKey'.
|
||||
"""
|
||||
if template.getVar(orderVar) == sortKey:
|
||||
return '-' + sortKey
|
||||
else:
|
||||
return sortKey
|
||||
|
||||
def sortImage(template, sortKey, orderVar='order'):
|
||||
"""
|
||||
Return an html img tag suitable for inclusion in the sortKey of a sortable table,
|
||||
if the sortValue is "sortKey" or "-sortKey".
|
||||
"""
|
||||
orderVal = template.getVar(orderVar)
|
||||
if orderVal == sortKey:
|
||||
return '<img src="/koji-static/images/gray-triangle-up.gif" width="10" height="9" class="sort"/>'
|
||||
elif orderVal == '-' + sortKey:
|
||||
return '<img src="/koji-static/images/gray-triangle-down.gif" width="10" height="9" class="sort"/>'
|
||||
else:
|
||||
return ''
|
||||
|
||||
def passthrough(template, *vars):
|
||||
"""
|
||||
Construct a string suitable for use as URL
|
||||
parameters. For each variable name in *vars,
|
||||
if the template has a corresponding non-None value,
|
||||
append that name-value pair to the string. The name-value
|
||||
pairs will be separated by ampersands (&), and prefixed by
|
||||
an ampersand if there are any name-value pairs. If there
|
||||
are no name-value pairs, an empty string will be returned.
|
||||
"""
|
||||
result = []
|
||||
for var in vars:
|
||||
value = template.getVar(var, default=None)
|
||||
if value != None:
|
||||
result.append('%s=%s' % (var, value))
|
||||
if result:
|
||||
return '&' + '&'.join(result)
|
||||
else:
|
||||
return ''
|
||||
|
||||
def sortByKeyFunc(key, noneGreatest=False):
|
||||
"""Return a function to sort a list of maps by the given key.
|
||||
If the key starts with '-', sort in reverse order. If noneGreatest
|
||||
is True, None will sort higher than all other values (instead of lower).
|
||||
"""
|
||||
if noneGreatest:
|
||||
# Normally None evaluates to be less than every other value
|
||||
# Invert the comparison so it always evaluates to greater
|
||||
cmpFunc = lambda a, b: (a is None or b is None) and -(cmp(a, b)) or cmp(a, b)
|
||||
else:
|
||||
cmpFunc = cmp
|
||||
|
||||
if key.startswith('-'):
|
||||
key = key[1:]
|
||||
sortFunc = lambda a, b: cmpFunc(b[key], a[key])
|
||||
else:
|
||||
sortFunc = lambda a, b: cmpFunc(a[key], b[key])
|
||||
|
||||
return sortFunc
|
||||
|
||||
def paginateList(values, data, start, dataName, prefix=None, order=None, noneGreatest=False, pageSize=50):
|
||||
"""
|
||||
Slice the 'data' list into one page worth. Start at offset
|
||||
'start' and limit the total number of pages to pageSize
|
||||
(defaults to 50). 'dataName' is the name under which the
|
||||
list will be added to the value map, and prefix is the name
|
||||
under which a number of list-related metadata variables will
|
||||
be added to the value map.
|
||||
"""
|
||||
if order != None:
|
||||
data.sort(sortByKeyFunc(order, noneGreatest))
|
||||
|
||||
totalRows = len(data)
|
||||
|
||||
if start:
|
||||
start = int(start)
|
||||
if not start or start < 0:
|
||||
start = 0
|
||||
|
||||
data = data[start:(start + pageSize)]
|
||||
count = len(data)
|
||||
|
||||
_populateValues(values, dataName, prefix, data, totalRows, start, count, pageSize, order)
|
||||
|
||||
return data
|
||||
|
||||
def paginateMethod(server, values, methodName, args=None, kw=None,
|
||||
start=None, dataName=None, prefix=None, order=None, pageSize=50):
|
||||
"""Paginate the results of the method with the given name when called with the given args and kws.
|
||||
The method must support the queryOpts keyword parameter, and pagination is done in the database."""
|
||||
if args is None:
|
||||
args = []
|
||||
if kw is None:
|
||||
kw = {}
|
||||
if start:
|
||||
start = int(start)
|
||||
if not start or start < 0:
|
||||
start = 0
|
||||
if not dataName:
|
||||
raise StandardError, 'dataName must be specified'
|
||||
|
||||
kw['queryOpts'] = {'countOnly': True}
|
||||
totalRows = getattr(server, methodName)(*args, **kw)
|
||||
|
||||
kw['queryOpts'] = {'order': order,
|
||||
'offset': start,
|
||||
'limit': pageSize}
|
||||
data = getattr(server, methodName)(*args, **kw)
|
||||
count = len(data)
|
||||
|
||||
_populateValues(values, dataName, prefix, data, totalRows, start, count, pageSize, order)
|
||||
|
||||
return data
|
||||
|
||||
def paginateResults(server, values, methodName, args=None, kw=None,
|
||||
start=None, dataName=None, prefix=None, order=None, pageSize=50):
|
||||
"""Paginate the results of the method with the given name when called with the given args and kws.
|
||||
This method should only be used when then method does not support the queryOpts command (because
|
||||
the logic used to generate the result list prevents filtering/ordering from being done in the database).
|
||||
The method must return a list of maps."""
|
||||
if args is None:
|
||||
args = []
|
||||
if kw is None:
|
||||
kw = {}
|
||||
if start:
|
||||
start = int(start)
|
||||
if not start or start < 0:
|
||||
start = 0
|
||||
if not dataName:
|
||||
raise StandardError, 'dataName must be specified'
|
||||
|
||||
totalRows = server.count(methodName, *args, **kw)
|
||||
|
||||
kw['filterOpts'] = {'order': order,
|
||||
'offset': start,
|
||||
'limit': pageSize}
|
||||
data = server.filterResults(methodName, *args, **kw)
|
||||
count = len(data)
|
||||
|
||||
_populateValues(values, dataName, prefix, data, totalRows, start, count, pageSize, order)
|
||||
|
||||
return data
|
||||
|
||||
def _populateValues(values, dataName, prefix, data, totalRows, start, count, pageSize, order):
|
||||
"""Populate the values list with the data about the list provided."""
|
||||
values[dataName] = data
|
||||
# Don't use capitalize() to title() here, they mess up
|
||||
# mixed-case name
|
||||
values['total' + dataName[0].upper() + dataName[1:]] = totalRows
|
||||
# Possibly prepend a prefix to the numeric parameters, to avoid namespace collisions
|
||||
# when there is more than one list on the same page
|
||||
values[(prefix and prefix + 'Start' or 'start')] = start
|
||||
values[(prefix and prefix + 'Count' or 'count')] = count
|
||||
values[(prefix and prefix + 'Range' or 'range')] = pageSize
|
||||
values[(prefix and prefix + 'Order' or 'order')] = order
|
||||
currentPage = start / pageSize
|
||||
values[(prefix and prefix + 'CurrentPage' or 'currentPage')] = currentPage
|
||||
totalPages = totalRows / pageSize
|
||||
if totalRows % pageSize > 0:
|
||||
totalPages += 1
|
||||
pages = [page for page in range(0, totalPages) if (abs(page - currentPage) < 100 or ((page + 1) % 100 == 0))]
|
||||
values[(prefix and prefix + 'Pages') or 'pages'] = pages
|
||||
|
||||
def stateName(stateID):
|
||||
"""Convert a numeric build state into a readable name."""
|
||||
return koji.BUILD_STATES[stateID].lower()
|
||||
|
||||
def imageTag(name):
|
||||
"""Return an img tag that loads an icon with the given name"""
|
||||
return '<img class="stateimg" src="/koji-static/images/%s.png" title="%s" alt="%s"/>' \
|
||||
% (name, name, name)
|
||||
|
||||
def stateImage(stateID):
|
||||
"""Return an IMG tag that loads an icon appropriate for
|
||||
the given state"""
|
||||
name = stateName(stateID)
|
||||
return imageTag(name)
|
||||
|
||||
def brStateName(stateID):
|
||||
"""Convert a numeric buildroot state into a readable name."""
|
||||
return koji.BR_STATES[stateID].lower()
|
||||
|
||||
def repoStateName(stateID):
|
||||
"""Convert a numeric repository state into a readable name."""
|
||||
if stateID == koji.REPO_INIT:
|
||||
return 'initializing'
|
||||
elif stateID == koji.REPO_READY:
|
||||
return 'ready'
|
||||
elif stateID == koji.REPO_EXPIRED:
|
||||
return 'expired'
|
||||
elif stateID == koji.REPO_DELETED:
|
||||
return 'deleted'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
def taskState(stateID):
|
||||
"""Convert a numeric task state into a readable name"""
|
||||
return koji.TASK_STATES[stateID].lower()
|
||||
|
||||
formatTime = koji.formatTime
|
||||
formatTimeRSS = koji.formatTimeLong
|
||||
formatTimeLong = koji.formatTimeLong
|
||||
|
||||
def formatDep(name, version, flags):
|
||||
"""Format dependency information into
|
||||
a human-readable format. Copied from
|
||||
rpmUtils/miscutils.py:formatRequires()"""
|
||||
s = name
|
||||
|
||||
if flags:
|
||||
if flags & (koji.RPMSENSE_LESS | koji.RPMSENSE_GREATER |
|
||||
koji.RPMSENSE_EQUAL):
|
||||
s = s + " "
|
||||
if flags & koji.RPMSENSE_LESS:
|
||||
s = s + "<"
|
||||
if flags & koji.RPMSENSE_GREATER:
|
||||
s = s + ">"
|
||||
if flags & koji.RPMSENSE_EQUAL:
|
||||
s = s + "="
|
||||
if version:
|
||||
s = "%s %s" %(s, version)
|
||||
return s
|
||||
|
||||
def rowToggle(template):
|
||||
"""If the value of template._rowNum is even, return 'row-even';
|
||||
if it is odd, return 'row-odd'. Increment the value before checking it.
|
||||
If the template does not have that value, set it to 0."""
|
||||
if not hasattr(template, '_rowNum'):
|
||||
template._rowNum = 0
|
||||
template._rowNum += 1
|
||||
if template._rowNum % 2:
|
||||
return 'row-odd'
|
||||
else:
|
||||
return 'row-even'
|
||||
|
||||
_fileFlags = {1: 'configuration',
|
||||
2: 'documentation',
|
||||
4: 'icon',
|
||||
8: 'missing ok',
|
||||
16: "don't replace",
|
||||
64: 'ghost',
|
||||
128: 'license',
|
||||
256: 'readme',
|
||||
512: 'exclude',
|
||||
1024: 'unpatched',
|
||||
2048: 'public key'}
|
||||
|
||||
def formatFileFlags(flags):
|
||||
"""Format rpm fileflags for display. Returns
|
||||
a list of human-readable strings specifying the
|
||||
flags set in "flags"."""
|
||||
results = []
|
||||
for flag, desc in _fileFlags.items():
|
||||
if flags & flag:
|
||||
results.append(desc)
|
||||
return results
|
||||
|
||||
def escapeHTML(value):
|
||||
"""Replace special characters to the text can be displayed in
|
||||
an HTML page correctly.
|
||||
< : <
|
||||
> : >
|
||||
& : &
|
||||
"""
|
||||
if not value:
|
||||
return value
|
||||
|
||||
return value.replace('&', '&').\
|
||||
replace('<', '<').\
|
||||
replace('>', '>')
|
||||
24
www/static/Makefile
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
SUBDIRS = images errors js
|
||||
|
||||
SERVERDIR = /var/www/koji-web/static
|
||||
FILES = $(wildcard *.css)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
for d in $(SUBDIRS); do make -s -C $$d clean; done
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/$(SERVERDIR)
|
||||
install -m 644 $(FILES) $(DESTDIR)/$(SERVERDIR)
|
||||
|
||||
for d in $(SUBDIRS); do make DESTDIR=$(DESTDIR)/$(SERVERDIR) \
|
||||
-C $$d install; [ $$? = 0 ] || exit 1; done
|
||||
9
www/static/debug.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* for debugging purposes */
|
||||
|
||||
@import url(koji.css);
|
||||
|
||||
* {
|
||||
border: 1px solid black !IMPORTANT;
|
||||
margin: 1px !IMPORTANT;
|
||||
padding: 1px !IMPORTANT;
|
||||
}
|
||||
18
www/static/errors/Makefile
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
SERVERDIR = /errors
|
||||
FILES = $(wildcard *.html)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/$(SERVERDIR)
|
||||
install -m 644 $(FILES) $(DESTDIR)/$(SERVERDIR)
|
||||
37
www/static/errors/unauthorized.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Authentication Failed | Koji</title>
|
||||
<link rel="stylesheet" type="text/css" media="screen" title="Koji Style" href="/koji-static/koji.css"/>
|
||||
<link rel="alternate stylesheet" type="text/css" media="screen" title="Debug" href="/koji-static/debug.css"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="Koji: recent builds" href="/koji/recentbuilds"/>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="innerwrap">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div id="header">
|
||||
<img src="/koji-static/images/koji.png" alt="Koji Logo" width="49" height="40" id="kojiLogo"/>
|
||||
</div><!-- end header -->
|
||||
|
||||
<div id="content">
|
||||
<h2>Kerberos Authentication Failed</h2>
|
||||
The Koji Web UI was unable to verify your Kerberos credentials. Please make sure that you have valid
|
||||
Kerberos tickets (obtainable via <strong>kinit</strong>), and that you have
|
||||
configured your browser correctly.
|
||||
</div>
|
||||
|
||||
<p id="footer">
|
||||
Copyright © 2007 Red Hat, Inc.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
www/static/images/1px.gif
Normal file
|
After Width: | Height: | Size: 807 B |
18
www/static/images/Makefile
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
SERVERDIR = /images
|
||||
FILES = $(wildcard *.gif *.png)
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)/$(SERVERDIR)
|
||||
install -m 644 $(FILES) $(DESTDIR)/$(SERVERDIR)
|
||||
BIN
www/static/images/assigned.png
Normal file
|
After Width: | Height: | Size: 597 B |
BIN
www/static/images/bkgrnd_greydots.png
Normal file
|
After Width: | Height: | Size: 234 B |
BIN
www/static/images/building.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
www/static/images/canceled.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
www/static/images/closed.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
www/static/images/complete.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
www/static/images/deleted.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
www/static/images/expired.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
www/static/images/failed.png
Normal file
|
After Width: | Height: | Size: 648 B |