File mgr-push-git-0.70e39bf.obscpio of Package mgr-push.23570
07070100000000000041FD000000000000000000000001624467C000000000000000000000000000000000000000000000000900000000mgr-push07070100000001000081B4000000000000000000000001624467C0000046AC000000000000000000000000000000000000001100000000mgr-push/COPYING GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 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.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, 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 or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
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 give any other recipients of the Program a copy of this License
along with the Program.
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 Program or any portion
of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
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 Program, 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 Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) 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; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, 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 executable. However, as a
special exception, the source code 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.
If distribution of executable or 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 counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program 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.
5. 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 Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program 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 to
this License.
7. 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 Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program 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 Program.
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.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program 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.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 Program
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 Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, 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
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
07070100000002000081B4000000000000000000000001624467C000000026000000000000000000000000000000000000001200000000mgr-push/Makefileinclude ../../../rel-eng/Makefile.git
07070100000003000081B4000000000000000000000001624467C000000367000000000000000000000000000000000000001900000000mgr-push/Makefile.pythonTHIS_MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIR := $(dir $(THIS_MAKEFILE))
include $(CURRENT_DIR)../../../rel-eng/Makefile.python
# Docker tests variables
DOCKER_CONTAINER_BASE = devel/galaxy/manager/4.2/docker/containers/suma-4.2
DOCKER_REGISTRY = registry.suse.de
DOCKER_RUN_EXPORT = "PYTHONPATH=$PYTHONPATH"
DOCKER_VOLUMES = -v "$(CURDIR)/../../../:/manager"
__pylint ::
$(call update_pip_env)
pylint --rcfile=pylintrc $(shell find -name '*.py') > reports/pylint.log || true
docker_pylint ::
docker run --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/sh -c "cd /manager/client/tools/mgr-push; make -f Makefile.python __pylint"
docker_shell ::
docker run -t -i --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/bash
07070100000004000081B4000000000000000000000001624467C000000508000000000000000000000000000000000000001A00000000mgr-push/Makefile.rhnpush# Makefile for the rhnpush module
#
INSTALL = install -D -p --verbose
INSTALL_BIN = $(INSTALL) -m 755
INSTALL_DIR = $(INSTALL) -m 755 -d
INSTALL_DATA = $(INSTALL) -m 644
SUBDIR = rhnpush
FILES = rhnpush_main rhnpush_v2 uploadLib __init__ connection \
rpm2mpm utils rhnpush_cache \
rhnpush_config rhnpush_confmanager archive
RHN_CONF_DIR = /etc/sysconfig/rhn
RHNPUSHRC = rhnpushrc
PYFILES := $(addsuffix .py, $(FILES))
OBJECTS := $(PYFILES)
ROOT ?= /usr/share/rhn
PYTHON_VERSION ?= 2
# check if we can build man pages
DOCBOOK = $(wildcard /usr/bin/docbook2man)
SGMLS = $(wildcard *.sgml)
MANS = $(patsubst %.sgml,%.8,$(SGMLS))
BINDIR = /usr/bin
MANDIR ?= /usr/man
EXTRA_DIRS = $(MANDIR)/man8 $(BINDIR) $(RHN_CONF_DIR)
install-conf : $(RHNPUSHRC)
$(INSTALL_DATA) $(RHNPUSHRC) $(PREFIX)$(RHN_CONF_DIR)/$(RHNPUSHRC)
%.inst: rhnpush.inst
all : $(MANS) $(OBJECTS)
install : all install-conf
$(INSTALL_DIR) $(PREFIX)$(MANDIR)/man8
$(INSTALL_DATA) $(MANS) $(PREFIX)$(MANDIR)/man8
$(INSTALL_DIR) $(PREFIX)$(ROOT)/rhnpush
$(INSTALL_DATA) $(PYFILES) $(PREFIX)$(ROOT)/rhnpush
$(INSTALL_BIN) rhnpush $(PREFIX)$(BINDIR)/rhnpush-$(PYTHON_VERSION)
@ln -sfv rhnpush $(PREFIX)$(BINDIR)/rpm2mpm
%.8 : %.sgml
/usr/bin/docbook2man $<
07070100000005000081B4000000000000000000000001624467C000000265000000000000000000000000000000000000001500000000mgr-push/__init__.py#
# Copyright (c) 2008--2015 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
07070100000006000081B4000000000000000000000001624467C0000033C4000000000000000000000000000000000000001400000000mgr-push/archive.py#
# Copyright (c) 2008--2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
"""Archive Parsing module"""
import os
import subprocess
import shutil
import tempfile
import select
import zipfile
import tarfile
import sys
if not hasattr(zipfile, 'ZIP64_LIMIT'):
sys.stderr.write("%s requires zipfile with ZIP64 support.\n" % sys.argv[0])
sys.exit(3)
# exceptions -------------------------------------------------------------
class ArchiveException(Exception):
pass
class DecompressionError(ArchiveException):
pass
class UnknownArchiveError(ArchiveException):
pass
class InvalidArchiveError(ArchiveException):
pass
# base archive parsing class ---------------------------------------------
class ArchiveParser(object):
"""Explode an zip or (compressed) tar archive and parse files and
directories contained therein"""
# constructor --------------------------------------------------------
def __init__(self, archive, tempdir="/tmp/"):
"""Initialize an archive parser"""
assert os.path.exists(archive)
self._archive = archive
self._archive_dir = None
# bug 164756: allow optional working directory
self._parent_dir = tempdir
# bug: 171086: support for older versions of tempfile (ie python 2.2)
tempfile.tempdir = tempdir
self._temp_dir = tempfile.mktemp()
os.mkdir(self._temp_dir, int('0700', 8))
self._explode()
# destructor ---------------------------------------------------------
def __del__(self):
"""Cleanup temporary files and directories"""
if hasattr(self, "_temp_dir") and os.path.isdir(self._temp_dir):
shutil.rmtree(self._temp_dir, ignore_errors=True)
# methods called by constructor --------------------------------------
def _get_archive_dir(self):
"""[internal] find the archive's top level directory name"""
raise NotImplementedError("ArchiveParser: abstract base class method '_get_archive_dir'")
def _explode_cmd(self):
"""[internal] find the appropriate command to open the archive"""
raise NotImplementedError("ArchiveParser: abstract base class method '_explode_cmd'")
def _explode(self):
"""[internal] Explode a archive for neutral parsing"""
cmd = self._explode_cmd()
assert self._archive is not None # assigned in _copy_archive
assert self._archive_dir is not None # assigned in _explode_cmd
if cmd:
_my_popen(cmd)
if os.path.isdir(self._archive_dir):
return
raise InvalidArchiveError("Archive did not expand to %s" % self._archive_dir)
raise InvalidArchiveError("Could not find command to open archive: %s" % self._archive)
# private helper methods ---------------------------------------------
def _find(self, filename):
"""[internal] Returns the relative path to a file in the archive"""
file_path = None
contents = os.listdir(self._archive_dir)
while contents:
entry = contents.pop()
path = os.path.join(self._archive_dir, entry)
if os.path.isdir(path):
p_contents = os.listdir(path)
e_contents = [os.path.join(entry, e) for e in p_contents]
# this really is something of a hack, the newest contents will
# 'prepended' to the queue instead of 'appended' changing the
# search into depth-first when I think breadth-first would be
# the expected behavior
# that's what we get for programming in python which doesn't
# supply a nice way of adding real data-structure support
# I already tried extending e_contents with contents and then
# reassigning the contents reference to e_contents, but the
# damn while loop still had a hold of the original reference
contents.extend(e_contents)
else:
if entry.endswith(filename):
file_path = entry
break
else:
# if __degug__: sys.stderr.write("[_find] '%s' not found\n" % file)
pass
return file_path
# public api ---------------------------------------------------------
def list(self, prefix=""):
"""Return a tuple of directories and files in the archive at the given
directory: prefix"""
dirname = os.path.join(self._archive_dir, prefix)
assert os.path.isdir(dirname)
l = os.listdir(dirname)
d = []
f = []
for i in l:
if os.path.isdir(os.path.join(dirname, i)):
d.append(i)
else:
f.append(i)
return (d, f)
def contains(self, filename):
"""Returns true iff the file is contained in the archive"""
return self._find(filename) is not None
def read(self, filename):
"""Returns the contents of the file, or None on error
First occurence of that file in archive is returned
"""
f = self._find(filename)
if f:
return self.direct_read(f)
return None
def direct_read(self, filename):
""" Returns the contens of the file, file is relative path in archive.
Top most level (_get_archive_dir) is automaticaly added.
"""
# pylint: disable=W0703
f = os.path.join(os.path.abspath(self._archive_dir), filename)
contents = None
if os.path.isfile(f) and os.access(f, os.R_OK):
try:
fd = open(f)
contents = fd.read()
fd.close()
except Exception:
contents = None
return contents
def zip(self, prefix=""):
"""Create a zip archive of a (sub-)directory of the archive"""
dirname = os.path.join(self._archive_dir, prefix)
zip_dir = os.path.basename(dirname)
parent_dir = os.path.dirname(dirname)
cwd = os.getcwd()
os.chdir(parent_dir)
zip_file = os.path.join(self._parent_dir, "%s.zip" % zip_dir)
fd = zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED)
for base, _dirs, files in os.walk(zip_dir):
fd.write(base)
for f in files:
fd.write(os.path.join(base, f))
os.chdir(cwd)
if os.path.isfile(zip_file):
return zip_file
return None
def cpio(self, prefix):
"""Create a cpio archive of a (sub-)directory of the archive"""
cpio_file = os.path.join(self._temp_dir, "%s.pkg" % prefix)
cmd = "pkgtrans -s %s %s %s" % (self._archive_dir, cpio_file, prefix)
_my_popen(cmd)
if os.path.isfile(cpio_file):
return cpio_file
return None
# parser for zip archives ------------------------------------------------
class ZipParser(ArchiveParser):
def __init__(self, archive, tempdir="/tmp/"):
self.zip_file = zipfile.ZipFile(archive, 'r')
ArchiveParser.__init__(self, archive, tempdir)
def _get_archive_dir(self):
return self.zip_file.namelist()[0]
def _explode(self):
"""Explode zip archive"""
self._archive_dir = os.path.join(self._temp_dir,
self._get_archive_dir()).rstrip('/')
try:
self.zip_file.extractall(self._temp_dir)
except Exception:
e = sys.exc_info()[1]
raise InvalidArchiveError("Archive did not expand to %s: %s" %
(self._archive_dir, str(e)))
return
def _explode_cmd(self):
pass
# parser for tar archives ------------------------------------------------
class TarParser(ArchiveParser):
def __init__(self, archive, tempdir="/tmp/"):
self.tar_file = tarfile.open(archive, 'r')
ArchiveParser.__init__(self, archive, tempdir)
def _get_archive_dir(self):
return self.tar_file.getnames()[0]
def _explode(self):
"""Explode tar archive"""
self._archive_dir = os.path.join(self._temp_dir, self._get_archive_dir())
try:
self.tar_file.extractall(path=self._temp_dir)
except Exception:
e = sys.exc_info()[1]
raise InvalidArchiveError("Archive did not expand to %s: %s" %
(self._archive_dir, str(e)))
return
def _explode_cmd(self):
pass
# parser for cpio archives -----------------------------------------------
class CpioParser(ArchiveParser):
def _get_archive_dir(self):
return os.path.basename(self._archive)[0:5] # arbitrary
def _explode_cmd(self):
"""Return the appropriate command for exploding a cpio archive"""
self._archive_dir = os.path.join(self._temp_dir, self._get_archive_dir())
if not _has_executable("pkgtrans"):
raise ArchiveException("cannot open %s, 'pkgtrans' not found" % self._archive)
return "cd %s; mkdir %s; pkgtrans %s %s all" % \
(self._temp_dir, self._archive_dir, self._archive, self._archive_dir)
# internal helper methods ------------------------------------------------
def _has_executable(exc):
"""Return true if the executable is found in the $PATH"""
# flag the error condition, this will evaluate to False
if "PATH" not in os.environ:
return None
# this is posix specific
dirs = os.environ["PATH"].split(':')
for dirname in dirs:
path = os.path.join(dirname, exc)
if os.access(path, os.X_OK):
return True
return False
def _my_popen(cmd):
"""Execute a command as a subprocess and return its exit status"""
# pylint: disable=E1101
popen = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=True, shell=True)
popen.stdin.close()
txt = ""
while 1:
rd, _wr, ex = select.select([popen.stdout, popen.stderr], [], [popen.stdout, popen.stderr], 5)
if ex:
txt += popen.stdout.read()
txt += popen.stderr.read()
break
if rd:
txt += rd[0].read()
break
status = popen.wait()
if status != 0:
raise Exception("%s exited with status %s and error\n%s" % (cmd, status, txt))
return
# NOTE these next two functions rely on file magic to determine the compression
# and archive types. some file magic information can be found here:
# http://www.astro.keele.ac.uk/oldusers/rno/Computing/File_magic.html
def _decompress(archive):
"""[internal] Decompress compressed archives and return the new archive name"""
cmd = ""
sfx_list = None
# determine which type of compression we're dealing with, if any
fd = open(archive, 'r')
magic = fd.read(2)
fd.close()
if magic == "BZ":
cmd = "bunzip2"
sfx_list = (".bz2", ".bz")
elif magic == "\x1F\x9D":
cmd = "uncompress"
sfx_list = (".Z",)
elif magic == "\x1F\x8B":
cmd = "gunzip"
sfx_list = (".gz",)
# decompress the archive if it is compressed
if cmd:
if not _has_executable(cmd):
raise ArchiveException("Cannot decompress %s, '%s' not found" % (archive, cmd))
print("Decompressing archive")
_my_popen("%s %s" % (cmd, archive))
# remove the now invalid suffix from the archive name
for sfx in sfx_list:
if archive[-len(sfx):] == sfx:
archive = archive[:-len(sfx)]
break
return archive
# archive parser factory -------------------------------------------------
def get_archive_parser(archive, tempdir="/tmp/"):
"""Factory function that returns an ArchiveParser object for the given archive"""
# decompress the archive
archive = _decompress(archive)
parserClass = None
fd = open(archive, 'r')
magic = fd.read(4)
if magic == "PK\x03\x04":
parserClass = ZipParser
fd.seek(0)
magic = fd.read(20)
if magic == "# PaCkAgE DaTaStReAm":
parserClass = CpioParser
fd.seek(257)
magic = fd.read(5)
if magic == "ustar":
parserClass = TarParser
# pre-posix tar doesn't have any standard file magic
if archive.endswith(".tar"):
parserClass = TarParser
fd.close()
if parserClass is None:
raise UnknownArchiveError("Wasn't able to identify: '%s'" % archive)
return parserClass(archive, tempdir)
07070100000007000081B4000000000000000000000001624467C0000028FC000000000000000000000000000000000000001700000000mgr-push/connection.py#
# Copyright (c) 2008--2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import socket
import base64
import sys
# pylint: disable=F0401,E0611,W0632
from rhn import connections, rpclib
from uyuni.common.usix import ListType, TupleType, IntType, raise_with_tb
from uyuni.common.rhn_pkg import InvalidPackageError, package_from_filename
from rhnpush.utils import tupleify_urlparse
if sys.version_info[0] == 3:
from urllib.parse import splitport
from urllib.parse import urlparse
else:
from urlparse import urlparse
from urllib import splitport # pylint: disable=C0412
# pylint: disable=W0622
class ConnectionError(Exception):
pass
# pylint: disable=R0902
class BaseConnection:
def __init__(self, uri, proxy=None):
self._scheme, (self._host, self._port), self._path = parse_url(uri)[:3]
if proxy:
arr = rpclib.get_proxy_info(proxy)
self._proxy_host = arr[0]
self._proxy_port = arr[1]
self._proxy_username = arr[2]
self._proxy_password = arr[3]
else:
self._proxy_host = None
self._trusted_certs = None
self._connection = None
self._timeout = None
def set_timeout(self, timeout):
self._timeout = timeout
def get_connection(self):
if self._scheme not in ['http', 'https']:
raise ValueError("Unsupported scheme", self._scheme)
params = {}
if self._timeout is not None:
params['timeout'] = self._timeout
if self._proxy_host:
params.update({
'host': self._host,
'port': self._port,
'proxy': "%s:%s" % (self._proxy_host, self._proxy_port),
'username': self._proxy_username,
'password': self._proxy_password,
})
if self._scheme == 'http':
return connections.HTTPProxyConnection(**params)
params['trusted_certs'] = self._trusted_certs
return connections.HTTPSProxyConnection(**params)
else:
if self._scheme == 'http':
return connections.HTTPConnection(self._host, self._port, **params)
params['trusted_certs'] = self._trusted_certs
return connections.HTTPSConnection(self._host, self._port, **params)
def connect(self):
self._connection = self.get_connection()
self._connection.connect()
def putrequest(self, method, url=None, skip_host=0):
if url is None:
url = self._path
return self._connection.putrequest(method, url=url,
skip_host=skip_host)
def __getattr__(self, name):
return getattr(self._connection, name)
class PackageUpload:
header_prefix = "X-RHN-Upload"
user_agent = "rhn-package-upload"
def __init__(self, url, proxy=None):
self.connection = BaseConnection(url, proxy)
self.headers = {}
self.package_name = None
self.package_epoch = None
self.package_version = None
self.package_release = None
self.package_arch = None
self.checksum = None
self.checksum_type = None
self.nvra = None
self._resp_headers = None
self.packaging = None
self._response = None
def set_header(self, name, value):
if name not in self.headers:
vlist = self.headers[name] = []
else:
vlist = self.headers[name]
if not isinstance(vlist, (ListType, TupleType)):
vlist = [vlist]
vlist.append(value)
def send_http_headers(self, method, content_length=None):
try:
self.connection.connect()
except socket.error:
e = sys.exc_info()[1]
raise_with_tb(ConnectionError("Error connecting", str(e)), sys.exc_info()[2])
# Add content_length
if 'Content-Length' not in self.headers and \
content_length is not None:
self.set_header('Content-Length', content_length)
self.connection.putrequest(method)
# Additional headers
for hname, hval in self.headers.items():
if not isinstance(hval, (ListType, TupleType)):
hval = [hval]
for v in hval:
self.connection.putheader(str(hname), str(v))
self.connection.endheaders()
def send_http_body(self, stream_body):
if stream_body is None:
return
stream_body.seek(0, 0)
buffer_size = 16384
while 1:
buf = stream_body.read(buffer_size)
if not buf:
break
try:
self.connection.send(buf)
except IOError:
e = sys.exc_info()[1]
raise_with_tb(ConnectionError("Error sending body", str(e)), sys.exc_info()[2])
def send_http(self, method, stream_body=None):
if stream_body is None:
content_length = 0
else:
stream_body.seek(0, 2)
content_length = stream_body.tell()
self.send_http_headers(method, content_length=content_length)
self.send_http_body(stream_body)
self._response = self.connection.getresponse()
self._resp_headers = self._response.msg
return self._response
def upload(self, filename, fileChecksumType, fileChecksum):
"""
Uploads a file.
Returns (http_error_code, error_message)
Sets:
self.package_name
self.package_epoch
self.package_version
self.package_release
self.package_arch
"""
try:
a_pkg = package_from_filename(filename)
a_pkg.read_header()
except InvalidPackageError:
return -1, "Not an RPM: %s" % filename
# Set some package data members
self.package_name = a_pkg.header['name']
self.package_epoch = a_pkg.header['epoch']
self.package_version = a_pkg.header['version']
self.package_release = a_pkg.header['release']
if a_pkg.header.is_source:
if 1051 in a_pkg.header.keys():
self.package_arch = 'nosrc'
else:
self.package_arch = 'src'
else:
self.package_arch = a_pkg.header['arch']
self.packaging = a_pkg.header.packaging
nvra = [self.package_name, self.package_version, self.package_release,
self.package_arch]
if isinstance(nvra[3], IntType):
# Old rpm format
return -1, "Deprecated RPM format: %s" % filename
self.nvra = nvra
# use the precomputed passed checksum
self.checksum_type = fileChecksumType
self.checksum = fileChecksum
# Set headers
self.set_header("Content-Type", "application/x-rpm")
self.set_header("User-Agent", self.user_agent)
# Custom RHN headers
prefix = self.header_prefix
self.set_header("%s-%s" % (prefix, "Package-Name"), nvra[0])
self.set_header("%s-%s" % (prefix, "Package-Version"), nvra[1])
self.set_header("%s-%s" % (prefix, "Package-Release"), nvra[2])
self.set_header("%s-%s" % (prefix, "Package-Arch"), nvra[3])
self.set_header("%s-%s" % (prefix, "Packaging"), self.packaging)
if self.checksum_type == 'md5':
self.set_header("%s-%s" % (prefix, "File-MD5sum"), self.checksum)
else:
self.set_header("%s-%s" % (prefix, "File-Checksum-Type"), self.checksum_type)
self.set_header("%s-%s" % (prefix, "File-Checksum"), self.checksum)
a_pkg.input_stream.seek(0, 0)
self._response = self.send_http('POST', stream_body=a_pkg.input_stream)
a_pkg.input_stream.close()
retval = self.process_response()
self.connection.close()
return retval
def process_response(self):
status = self._response.status
reason = self._response.reason
if status == 200:
# OK
return status, "OK"
if status == 201:
# Created
return (status, "%s %s: %s-%s-%s.%s.rpm already uploaded" % (
self.checksum_type, self.checksum,
self.nvra[0], self.nvra[1], self.nvra[2], self.nvra[3]))
if status in (404, 409):
# Conflict
errstring = self.get_error_message(self._resp_headers)
return status, errstring
data = self._response.read()
if status == 403:
# In this case Authentication is no longer valid on server
# client needs to re-authenticate itself.
errstring = self.get_error_message(self._resp_headers)
return status, errstring
if status == 500:
print("Internal server error", status, reason)
errstring = self.get_error_message(self._resp_headers)
return status, data + errstring
return status, data
def get_error_message(self, headers):
prefix = self.header_prefix + '-Error'
text = [x[1] for x in headers.getaddrlist(prefix + '-String')]
# text is a list now, convert it to a string
text = '\n'.join(text)
# pylint: disable=W1505
text = base64.decodestring(text)
return text
def parse_url(url, scheme="http", path='/'):
_scheme, netloc, _path, params, query, fragment = tupleify_urlparse(
urlparse(url))
if not netloc:
# No scheme - trying to patch it up ourselves?
url = scheme + "://" + url
_scheme, netloc, _path, params, query, fragment = tupleify_urlparse(
urlparse(url))
if not netloc:
# XXX
raise Exception()
(host, port) = splitport(netloc)
if not _path:
_path = path
return (_scheme, (host, port), _path, params, query, fragment)
07070100000008000081B4000000000000000000000001624467C00000003C000000000000000000000000000000000000001C00000000mgr-push/mgr-push-rpmlintrcaddFilter("suse-filelist-forbidden-sysconfig .*/sysconfig")
07070100000009000081B4000000000000000000000001624467C000002592000000000000000000000000000000000000001A00000000mgr-push/mgr-push.changes-------------------------------------------------------------------
Wed Mar 30 16:22:09 CEST 2022 - jmassaguerpla@suse.de
- version 4.2.5-1
* Fix the condition for preventing building python 2 subpackage
for SLE15
-------------------------------------------------------------------
Wed Feb 02 13:45:55 CET 2022 - jmassaguerpla@suse.de
- version 4.2.4-1
* Do not build python 2 package for SLE15SP4 and higher
-------------------------------------------------------------------
Fri Jul 16 14:06:48 CEST 2021 - jgonzalez@suse.com
- version 4.2.3-1
- Adapt the tests to the new images
-------------------------------------------------------------------
Thu Dec 03 13:38:22 CET 2020 - jgonzalez@suse.com
- version 4.2.2-1
- defined __python for python2.
- Excluded RHEL8 for Python 2 build.
-------------------------------------------------------------------
Fri Sep 18 11:14:23 CEST 2020 - jgonzalez@suse.com
- version 4.2.1-1
- Update package version to 4.2.0
-------------------------------------------------------------------
Wed Nov 27 16:42:09 CET 2019 - jgonzalez@suse.com
- version 4.1.1-1
- replace spacewalk-usix and spacewalk-backend-libs with uyuni-common-libs
- Bump version to 4.1.0 (bsc#1154940)
-------------------------------------------------------------------
Wed May 15 20:08:24 CEST 2019 - jgonzalez@suse.com
- version 4.0.6-1
- Obsolete newer versions of rhnpush
-------------------------------------------------------------------
Wed May 15 15:00:24 CEST 2019 - jgonzalez@suse.com
- version 4.0.5-1
- SPEC cleanup
- Fix wrong bugzilla entry at changelog
-------------------------------------------------------------------
Mon Apr 22 12:02:26 CEST 2019 - jgonzalez@suse.com
- version 4.0.4-1
- add makefile and configuration for pylint
-------------------------------------------------------------------
Wed Jan 16 12:16:04 CET 2019 - jgonzalez@suse.com
- version 4.0.3-1
- Fix dependencies to spacewalk-backend-libs
-------------------------------------------------------------------
Fri Oct 26 09:56:12 CEST 2018 - jgonzalez@suse.com
- version 4.0.2-1
- Add Uyuni URL to package
-------------------------------------------------------------------
Fri Aug 10 16:02:45 CEST 2018 - jgonzalez@suse.com
- version 4.0.1-1
- Bump version to 4.0.0 (bsc#1104034)
- Rename package to mgr-push to allow version 4.0.0 (bsc#1104034)
- Fix copyright for the package specfile (bsc#1103696)
-------------------------------------------------------------------
Mon Apr 23 08:53:06 CEST 2018 - jgonzalez@suse.com
- version 5.5.113.2-1
- Sync with upstream (bsc#1083294)
- rhnpush is needed on python2 due to spacewalk-proxy
-------------------------------------------------------------------
Mon Mar 26 08:40:29 CEST 2018 - jgonzalez@suse.com
- version 5.5.113.1-1
- Sync with upstream (bsc#1083294)
- Build python2 on SUSE
-------------------------------------------------------------------
Mon Mar 05 09:16:14 CET 2018 - jgonzalez@suse.com
- version 5.5.111.2-1
- remove empty clean section from spec (bsc#1083294)
-------------------------------------------------------------------
Fri Feb 23 10:48:18 CET 2018 - jgonzalez@suse.com
- version 5.5.111.1-1
- Sync with upstream
-------------------------------------------------------------------
Thu Oct 26 17:16:51 CEST 2017 - mc@suse.de
- version 5.5.108.1-1
- move rhnpush files into proper python2/python3 subpackages
-------------------------------------------------------------------
Wed Aug 09 12:11:07 CEST 2017 - fkobzik@suse.de
- version 5.5.104.3-1
- disable pylint check during build also for RHEL7
-------------------------------------------------------------------
Mon May 29 15:40:35 CEST 2017 - mc@suse.de
- version 5.5.104.2-1
- fixed pylint warnings disabled python3 pylint on Fedora 26+ for now
- removed outdated solaris2mpm script
-------------------------------------------------------------------
Fri Mar 31 09:59:14 CEST 2017 - mc@suse.de
- version 5.5.104.1-1
- Pylint fixes in rhnpush
-------------------------------------------------------------------
Tue Mar 07 16:00:37 CET 2017 - mc@suse.de
- version 5.5.102.1-1
- Updated links to github in spec files
- require spacewalk-usix indead of spacewalk-backend-usix
-------------------------------------------------------------------
Wed Jan 11 15:34:37 CET 2017 - michele.bologna@suse.com
- version 5.5.101.1-1
- Version 5.5.101-1
-------------------------------------------------------------------
Wed Apr 06 08:49:39 CEST 2016 - mc@suse.de
- version 5.5.91.2-1
- don't count on having newest rhn-client-tools
-------------------------------------------------------------------
Sat Jan 16 11:29:10 CET 2016 - mc@suse.de
- version 5.5.91.1-1
- alow to use existing rpcServer when creating RhnServer
-------------------------------------------------------------------
Mon Nov 30 10:59:38 CET 2015 - mc@suse.de
- version 5.5.90.1-1
- rhn-satellite-activate: manual references removed
-------------------------------------------------------------------
Wed Oct 07 15:56:48 CEST 2015 - mc@suse.de
- version 5.5.89.1-1
- Wire in timeout for rhnpush
-------------------------------------------------------------------
Mon Jun 22 15:30:09 CEST 2015 - jrenner@suse.de
- version 5.5.71.8-1
- fix --ca-chain option for rhnpush (bsc#931503, bsc#895869)
-------------------------------------------------------------------
Tue Feb 03 13:08:35 CET 2015 - mc@suse.de
- version 5.5.71.7-1
- Getting rid of Tabs and trailing spaces
-------------------------------------------------------------------
Thu Dec 18 13:33:42 CET 2014 - mc@suse.de
- version 5.5.71.6-1
- rhnpush: fix pylint checks and code quality
-------------------------------------------------------------------
Wed Sep 3 01:41:38 CEST 2014 - ro@suse.de
- sanitize release line in specfile
-------------------------------------------------------------------
Fri Jun 13 16:19:07 CEST 2014 - jrenner@suse.de
- version 5.5.71.5-1
- Add default path structure to proxy lookaside that avoids collisions
- Make rhnpush backwards-compatible with old spacewalk-proxy
-------------------------------------------------------------------
Thu Apr 10 17:32:20 CEST 2014 - mc@suse.de
- version 5.5.71.4-1
-------------------------------------------------------------------
Thu Apr 10 17:32:04 CEST 2014 - mc@suse.de
- fix release in specfile for SLE12 (bnc#872970)
-------------------------------------------------------------------
Thu Mar 27 10:07:33 CET 2014 - fcastelli@suse.com
- version 5.5.71.3-1
- correcting exception type
-------------------------------------------------------------------
Thu Feb 13 15:03:40 CET 2014 - mc@suse.de
- version 5.5.71.2-1
- use SUSE package name in requires
- pylint check only on SLE11
-------------------------------------------------------------------
Tue Dec 10 14:11:22 CET 2013 - mc@suse.de
- version 5.5.71.1-1
- switch to 2.1
-------------------------------------------------------------------
Thu Aug 02 16:23:54 CEST 2012 - mc@suse.de
- version 5.5.42.7-1
- code cleanup
- fixed man page
- removed dead --no-cache option
- fixed --no-session-caching option
-------------------------------------------------------------------
Mon Jul 16 15:17:44 CEST 2012 - ug@suse.de
- version 5.5.42.6-1
- solaris2mpm on RHEL5 is not supported
- solaris2mpm needs zipfile with ZIP64 extension
- remove trailing '/' from from archive dir
- reuse UploadError from uploadLib
- simplified authentication code
-------------------------------------------------------------------
Mon Jun 25 10:22:27 CEST 2012 - mc@suse.de
- version 5.5.42.5-1
- removed commented out code and obsoleted comments
-------------------------------------------------------------------
Thu Jun 21 10:52:17 CEST 2012 - jrenner@suse.de
- version 5.5.42.4-1
- add COPYING file (bnc#764854)
-------------------------------------------------------------------
Thu May 31 10:55:28 CEST 2012 - mc@suse.de
- version 5.5.42.3-1
- Use the correct a_pkg variable.
-------------------------------------------------------------------
Fri Apr 27 16:54:01 CEST 2012 - mc@suse.de
- version 5.5.42.2-1
- removed unused get_header_struct_size()
- removed unused function get_header_byte_range()
-------------------------------------------------------------------
Wed Mar 21 17:35:17 CET 2012 - mc@suse.de
- version 5.5.42.1-1
- Bumping package version
-------------------------------------------------------------------
Tue Feb 22 16:23:06 CET 2011 - mantel@suse.de
- add missing "import string" (bnc#672643)
-------------------------------------------------------------------
Thu Feb 17 14:06:01 CET 2011 - mantel@suse.de
- fix build: create symlink only after binary has been installed
-------------------------------------------------------------------
Thu Feb 17 13:39:50 CET 2011 - mantel@suse.de
- rename "Red Hat Network" to "SUSE Manager" for SUSE (bnc#672643)
- add mgrpush symlink (bnc#672643)
-------------------------------------------------------------------
Tue Feb 1 16:54:39 CET 2011 - mc@suse.de
- fix build for RH4
-------------------------------------------------------------------
Tue Jan 11 16:39:04 CET 2011 - mc@suse.de
- tag strings which needs changes
- fix Requires line in spec
-------------------------------------------------------------------
Wed Sep 15 12:16:32 CEST 2010 - mantel@suse.de
- fix post-build-checks
-------------------------------------------------------------------
Tue Sep 14 17:59:47 CEST 2010 - mantel@suse.de
- Initial release of rhnpush
-------------------------------------------------------------------
0707010000000A000081B4000000000000000000000001624467C0000015B4000000000000000000000000000000000000001700000000mgr-push/mgr-push.spec#
# spec file for package mgr-push
#
# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
# Copyright (c) 2008-2018 Red Hat, Inc.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
# Old name and version+1 before renaming to mgr-push
%define oldname rhnpush
%define oldversion 5.5.114
%{!?pylint_check: %global pylint_check 0}
%global __python /usr/bin/python2
%if 0%{?fedora} || 0%{?suse_version} > 1320 || 0%{?rhel} >= 8
%global build_py3 1
%global default_py3 1
%endif
%if !( 0%{?rhel} >= 8 || 0%{?sle_version} >= 150000 )
%global build_py2 1
%endif
%define pythonX %{?default_py3: python3}%{!?default_py3: python2}
Name: mgr-push
Summary: Package uploader for the Spacewalk
License: GPL-2.0-only
Group: Applications/System
Url: https://github.com/uyuni-project/uyuni
Version: 4.2.5
Provides: %{oldname} = %{oldversion}
Obsoletes: %{oldname} < %{oldversion}
Release: 1%{?dist}
Source0: https://github.com/spacewalkproject/spacewalk/archive/%{name}-%{version}.tar.gz
Source1: %{name}-rpmlintrc
BuildRoot: %{_tmppath}/%{name}-%{version}-build
%if 0%{?fedora} || 0%{?rhel} || 0%{?suse_version} >= 1210
BuildArch: noarch
%endif
Requires: %{pythonX}-%{name} = %{version}-%{release}
BuildRequires: docbook-utils
BuildRequires: gettext
%if 0%{?pylint_check}
%if 0%{?build_py2}
BuildRequires: spacewalk-python2-pylint
%endif
%if 0%{?build_py3}
BuildRequires: spacewalk-python3-pylint
%endif
%endif
%description
rhnpush uploads package headers to the Spacewalk
servers into specified channels and allows for several other channel
management operations relevant to controlling what packages are available
per channel.
%if 0%{?build_py2}
%package -n python2-%{name}
Summary: Package uploader for the Spacewalk or Red Hat Satellite Server
Group: Applications/System
Provides: python2-%{oldname} = %{oldversion}
Obsoletes: python2-%{oldname} < %{oldversion}
Requires: %{name} = %{version}-%{release}
%if 0%{?fedora} >= 28
Requires: python2-rpm
BuildRequires: python2-devel
%else
Requires: rpm-python
BuildRequires: python-devel
%endif
Requires: python2-rhn-client-tools
Requires: rhnlib >= 2.8.3
Requires: python2-uyuni-common-libs
BuildRequires: python2-rhn-client-tools
BuildRequires: python2-uyuni-common-libs
%description -n python2-%{name}
Python 2 specific files for rhnpush.
%endif
%if 0%{?build_py3}
%package -n python3-%{name}
Summary: Package uploader for the Spacewalk or Red Hat Satellite Server
Group: Applications/System
Provides: python3-%{oldname} = %{oldversion}
Obsoletes: python3-%{oldname} < %{oldversion}
Requires: %{name} = %{version}-%{release}
%if 0%{?suse_version}
Requires: python3-rpm
%else
Requires: rpm-python3
%endif
Requires: python3-rhn-client-tools
Requires: python3-rhnlib >= 2.8.3
Requires: python3-uyuni-common-libs
BuildRequires: python3-devel
BuildRequires: python3-rhn-client-tools
BuildRequires: python3-rpm-macros
BuildRequires: python3-uyuni-common-libs
%description -n python3-%{name}
Python 3 specific files for rhnpush.
%endif
%prep
%setup -q
%build
make -f Makefile.rhnpush all
%install
install -d $RPM_BUILD_ROOT/%{python_sitelib}
%if 0%{?build_py2}
make -f Makefile.rhnpush install PREFIX=$RPM_BUILD_ROOT ROOT=%{python_sitelib} \
MANDIR=%{_mandir} PYTHON_VERSION=%{python_version}
%endif
%if 0%{?build_py3}
sed -i 's|#!/usr/bin/python|#!/usr/bin/python3|' rhnpush
install -d $RPM_BUILD_ROOT/%{python3_sitelib}
make -f Makefile.rhnpush install PREFIX=$RPM_BUILD_ROOT ROOT=%{python3_sitelib} \
MANDIR=%{_mandir} PYTHON_VERSION=%{python3_version}
%endif
%define default_suffix %{?default_py3:-%{python3_version}}%{!?default_py3:-%{python_version}}
ln -s rhnpush%{default_suffix} $RPM_BUILD_ROOT%{_bindir}/rhnpush
%if 0%{?suse_version}
ln -s rhnpush $RPM_BUILD_ROOT/%{_bindir}/mgrpush
%endif
%check
%if 0%{?pylint_check}
# check coding style
%if 0%{?build_py2}
export PYTHONPATH=$RPM_BUILD_ROOT%{python_sitelib}
spacewalk-python2-pylint $RPM_BUILD_ROOT%{_bindir} $RPM_BUILD_ROOT%{python_sitelib}
%endif
%if 0%{?build_py3}
export PYTHONPATH=$RPM_BUILD_ROOT%{python3_sitelib}
spacewalk-python3-pylint $RPM_BUILD_ROOT%{_bindir} $RPM_BUILD_ROOT%{python3_sitelib}
%endif
%endif
%files
%defattr(-,root,root)
%{_bindir}/rhnpush
%{_bindir}/rpm2mpm
%dir %{_sysconfdir}/sysconfig/rhn
%if 0%{?suse_version}
%{_bindir}/mgrpush
%endif
%config(noreplace) %attr(644,root,root) %{_sysconfdir}/sysconfig/rhn/rhnpushrc
%{_mandir}/man8/rhnpush.8*
%doc COPYING
%if 0%{?build_py2}
%files -n python2-%{name}
%defattr(-,root,root)
%attr(755,root,root) %{_bindir}/rhnpush-%{python_version}
%{python_sitelib}/rhnpush/
%endif
%if 0%{?build_py3}
%files -n python3-%{name}
%defattr(-,root,root)
%attr(755,root,root) %{_bindir}/rhnpush-%{python3_version}
%{python3_sitelib}/rhnpush/
%endif
%changelog
0707010000000B000041FD000000000000000000000001624467C000000000000000000000000000000000000000000000001100000000mgr-push/patches0707010000000C000081B4000000000000000000000001624467C0000001FB000000000000000000000000000000000000002200000000mgr-push/patches/rhel4-static.dif--- solaris2mpm.py
+++ solaris2mpm.py
@@ -30,7 +30,6 @@ try:
except ImportError:
import md5
class hashlib:
- @staticmethod
def new(checksum):
# Add sha1 if needed.
if checksum == 'md5':
@@ -39,6 +38,8 @@ except ImportError:
if checksum not in ['md5', 'sha1']:
raise ValueError, "Incompatible checksum type"
+ new = staticmethod(new)
+
from spacewalk.common import rhn_mpm
from archive import get_archive_parser
0707010000000D000081B4000000000000000000000001624467C000001396000000000000000000000000000000000000001200000000mgr-push/pylintrc# mgr-push package pylint configuration
[MASTER]
# Profiled execution.
profile=no
# Pickle collected data for later comparisons.
persistent=no
[MESSAGES CONTROL]
# Disable the message(s) with the given id(s).
disable=I0011,
C0302,
C0111,
R0801,
R0902,
R0903,
R0904,
R0912,
R0913,
R0914,
R0915,
R0921,
R0922,
W0142,
W0403,
W0603,
C1001,
W0121,
useless-else-on-loop,
bad-whitespace,
unpacking-non-sequence,
superfluous-parens,
cyclic-import,
redefined-variable-type,
no-else-return,
# Uyuni disabled
E0203,
E0611,
E1101,
E1102
# list of disabled messages:
#I0011: 62: Locally disabling R0201
#C0302: 1: Too many lines in module (2425)
#C0111: 1: Missing docstring
#R0902: 19:RequestedChannels: Too many instance attributes (9/7)
#R0903: Too few public methods
#R0904: 26:Transport: Too many public methods (22/20)
#R0912:171:set_slots_from_cert: Too many branches (59/20)
#R0913:101:GETServer.__init__: Too many arguments (11/10)
#R0914:171:set_slots_from_cert: Too many local variables (38/20)
#R0915:171:set_slots_from_cert: Too many statements (169/50)
#W0142:228:MPM_Package.write: Used * or ** magic
#W0403: 28: Relative import 'rhnLog', should be 'backend.common.rhnLog'
#W0603: 72:initLOG: Using the global statement
# for pylint-1.0 we also disable
#C1001: 46, 0: Old-style class defined. (old-style-class)
#W0121: 33,16: Use raise ErrorClass(args) instead of raise ErrorClass, args. (old-raise-syntax)
#W:243, 8: Else clause on loop without a break statement (useless-else-on-loop)
# pylint-1.1 checks
#C:334, 0: No space allowed after bracket (bad-whitespace)
#W:162, 8: Attempting to unpack a non-sequence defined at line 6 of (unpacking-non-sequence)
#C: 37, 0: Unnecessary parens after 'not' keyword (superfluous-parens)
#C:301, 0: Unnecessary parens after 'if' keyword (superfluous-parens)
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=parseable
# Include message's id in output
include-ids=yes
# Tells whether to display a full report or only the messages
reports=yes
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}"
[VARIABLES]
# A regular expression matching names used for dummy variables (i.e. not used).
dummy-variables-rgx=_|dummy
[BASIC]
# Regular expression which should only match correct module names
#module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
module-rgx=([a-zA-Z_][a-zA-Z0-9_]+)$
# Regular expression which should only match correct module level names
const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[a-zA-Z_][a-zA-Z0-9_]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-zA-Z0-9_]{,42}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-zA-Z0-9_]{,42}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-zA-Z0-9_]{,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-zA-Z0-9_]{,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-zA-Z0-9_]{,30}$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression which should only match correct class sttribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,42}|(__.*__))$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# List of builtins function names that should not be used, separated by a comma
bad-functions=apply,input
[DESIGN]
# Maximum number of arguments for function / method
max-args=10
# Maximum number of locals for function / method body
max-locals=20
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branchs=20
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=1
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[CLASSES]
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=120
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=
0707010000000E000081FD000000000000000000000001624467C0000002E6000000000000000000000000000000000000001100000000mgr-push/rhnpush#!/usr/bin/python
#
# Wrapper for rhnpush_main.py
#
import sys
try:
from rhnpush import rhnpush_main
except ImportError:
e = sys.exc_info()[1]
sys.stderr.write("Unable to load module rhnpush_main\n")
sys.stderr.write(str(e) + "\n")
sys.exit(1)
if __name__ == '__main__':
try:
sys.exit(rhnpush_main.main() or 0)
except KeyboardInterrupt:
e = sys.exc_info()[1]
sys.stderr.write("\nUser interrupted process.\n")
sys.exit(0)
except SystemExit:
e = sys.exc_info()[1]
sys.exit(e.code)
# pylint: disable=W0703
except Exception:
e = sys.exc_info()[1]
sys.stderr.write("\nERROR: unhandled exception occurred: (%s).\n" % e)
sys.exit(-1)
0707010000000F000081B4000000000000000000000001624467C00000329F000000000000000000000000000000000000001600000000mgr-push/rhnpush.sgml<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [
<!ENTITY RHNSAT "Spacewalk Server" >
<!ENTITY RHNPUSH "Spacewalk Package Pusher" >
]>
<refentry>
<RefMeta>
<RefEntryTitle>rhnpush</RefEntryTitle><manvolnum>8</manvolnum>
<RefMiscInfo>Version 2.0</RefMiscInfo>
</RefMeta>
<RefNameDiv>
<RefName><command>rhnpush</command></RefName>
<RefPurpose>
Utility to push binary and source RPMs into an RHN channel.
</RefPurpose>
</RefNameDiv>
<RefSynopsisDiv>
<Synopsis>
<cmdsynopsis>
<command>rhnpush</command>
<arg>options <replaceable>...</replaceable></arg>
<arg>-v</arg> <arg>--verbose</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-d<replaceable>DIRECTORY</replaceable></arg>
<arg>--dir=<replaceable>DIRECTORY</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-c<replaceable>CHANNEL_LABEL</replaceable></arg>
<arg>--channel=<replaceable>CHANNEL_LABEL</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-n<replaceable>N_HEADERS_PER_CALL</replaceable></arg>
<arg>--count=<replaceable>N_HEADERS_PER_CALL</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-l</arg> <arg>--list</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-r<replaceable>RELATIVE_DIRECTORY</replaceable></arg>
<arg>--reldir=<replaceable>RELATIVE_DIRECTORY</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-o<replaceable>ORGANIZATION_ID</replaceable></arg>
<arg>--orgid=<replaceable>ORGANIZATION_ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-u<replaceable>USERNAME</replaceable></arg>
<arg>--username=<replaceable>USERNAME</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-p<replaceable>PASSWORD</replaceable></arg>
<arg>--password=<replaceable>PASSWORD</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-s</arg> <arg>--stdin</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-X<replaceable>glob</replaceable></arg>
<arg>--exclude=<replaceable>GLOB</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--force</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--nosig</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--newest</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--nullorg</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--header</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--source</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--server=<replaceable>SERVER</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--proxy=<replaceable>PROXY:PORT</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-N</arg> <arg>--new-cache</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--no-session-caching</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--extended-test</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--test</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--tolerant</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>--timeout=<replaceable>SECONDS</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-h</arg> <arg>--help</arg>
</cmdsynopsis>
<cmdsynopsis>
<arg>-?</arg> <arg>--usage</arg>
</cmdsynopsis>
</Synopsis>
</RefSynopsisDiv>
<RefSect1><Title>Description</Title>
<para>
The &RHNPUSH; (<emphasis>rhnpush</emphasis>) pushes RPMs into locally
managed channels on a &RHNSAT;. Refer to the Red Hat Satellite documentation
for further reference.
</para>
</RefSect1>
<RefSect1><Title>Options</Title>
<variablelist>
<varlistentry>
<term>-v --verbose</term>
<listitem>
<para>Increase verbosity (can use multiple times).</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-d<replaceable>DIRECTORY</replaceable>
--dir=<replaceable>DIRECTORY</replaceable></term>
<listitem>
<para>process packages from this directory.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-c<replaceable>CHANNEL_LABEL</replaceable>,
--channel=<replaceable>CHANNEL_LABEL</replaceable></term>
<listitem>
<para>process data for this specific channel (specified by label)
only.
NOTE: the channel's *label* is NOT the same as the channel's
*name*.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-n<replaceable>N_HEADERS_PER_CALL</replaceable>
--count=<replaceable>N_HEADERS_PER_CALL</replaceable></term>
<listitem>
<para>process this number of headers per call. Current default is
25. Must be an integer.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-l --list</term>
<listitem>
<para>only list the specified channels.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-r<replaceable>RELATIVE_DIRECTORY</replaceable>
--reldir=<replaceable>RELATIVE_DIRECTORY</replaceable></term>
<listitem>
<para>relative directory to associate with each file.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-o<replaceable>ORGANIZATION_ID</replaceable>
--orgid=<replaceable>ORGANIZATION_ID</replaceable></term>
<listitem>
<para>Your organization's ID number. Must be an integer.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-u<replaceable>USERNAME</replaceable>
--username=<replaceable>USERNAME</replaceable></term>
<listitem>
<para>username of user that has administrative access to the
specified channel.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-p<replaceable>PASSWORD</replaceable>
--password=<replaceable>PASSWORD</replaceable></term>
<listitem>
<para>see --username.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-s --stdin</term>
<listitem>
<para>read the package names from standard-in (allows piping).</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-X<replaceable>glob</replaceable>
--exclude=<replaceable>GLOB</replaceable></term>
<listitem>
<para>exclude packages that matches this glob expression.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--force</term>
<listitem>
<para>force the package upload (overwrites if package is already
uploaded).</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--tolerant</term>
<listitem>
<para>If rhnpush errors while uploading a package, continue attempting
to push the rest of the packages.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--nosig</term>
<listitem>
<para>don't fail if packages are unsigned.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--newest</term>
<listitem>
<para>push only the packages that are newer than those on the
server.</para>
<para>Please note that source RPMs are special in that their
versions are never compared. Therefore, using this option with
&RHNPUSH; will upload source RPMs for the specified channels only
if they were not previously uploaded, and binaries built from
them exist in those channels.</para>
<para>In other words, you have to upload a binary rpm before being
able to use <emphasis>--newest</emphasis> with its source rpm.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--timeout=<replaceable>SECONDS</replaceable></term>
<listitem>
<para>Change default connection timeout.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--nullorg</term>
<listitem>
<para>use the null org ID (most often the case).</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--header</term>
<listitem>
<para>only upload the headers.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--source</term>
<listitem>
<para>the packages indicated are source packages (we treat them differently).</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--server=<replaceable>SERVER</replaceable></term>
<listitem>
<para>push to this server. The format is http[s]://<hostname>/APP</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--proxy=<replaceable>PROXY:PORT</replaceable></term>
<listitem>
<para>Use proxy server (<server>:<port>)</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--test</term>
<listitem>
<para>only print the packages to be pushed, don't actually push
them.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-N --new-cache</term>
<listitem>
<para>create a new username/password cache</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--no-session-caching</term>
<listitem>
<para>This option disabled session token authentication. Useful if you want to push to two or more different servers.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>--extended-test</term>
<listitem>
<para>perform a more verbose test.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-h --help</term>
<listitem>
<para>briefly describe the options.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-? --usage</term>
<listitem>
<para>usage summary.</para>
</listitem>
</varlistentry>
</variablelist>
</RefSect1>
<RefSect1><Title>Notes on using rhnpush</Title>
<simplelist>
<member>
Rhnpush has three configuration files called /etc/sysconfig/rhn/rhnpushrc, ~/.rhnpushrc, and ./.rhnpushrc.
</member>
<member>
/etc/sysconfig/rhn/rhnpushrc is the system-wide default settings for rhnpush.
</member>
<member>
~/.rhnpushrc is the user-specific settings that override the system-wide settings.
</member>
<member>
./.rhnpushrc controls the directory specific settings that override the user-specific and system-specific settings.
</member>
<member>
/etc/sysconfig/rhn/rhnpushrc must be present for rhnpush to function correctly. If it is missing, rhnpush will attempt to use a series of default settings stored internally as a replacement. ~/.rhnpushrc and ./.rhnpushrc are not required to be present, but will be used if they are present. They are not created automatically by rhnpush.
</member>
<member>
Rhnpush uses a cache, stored at ~/.rhnpushcache, to temporarily hold the username and password for a user.
</member>
<member>
If the cache is missing, it will be created by rhnpush.
</member>
<member>
If the cache is present and not too old, the usename-password combo will be used as a convenience for the user.
</member>
<member>
The amount of time a cache lasts is configurable in any of the three configuration files.
</member>
<member>
If your username/password combination gets messed up you have two options. One, you can wait until the cache expires, which takes minutes by default. Two, you can use the --new_cache option to force rhnpush to let you reenter your username/password.
</member>
<member>
Using the --stdin and --dir options at the same time works as follows: rhnpush will let you type in rpm names, each rpm name on a separate line. When you have finished entering in rpm names, hit Ctrl-D. Rhnpush will then grab the files from directory you specified with --dir, put them in a list with the rpms you listed through standard input, and send them to the channel that was listed on the command-line or in the configuration files.
</member>
</simplelist>
</RefSect1>
<RefSect1><Title>See Also</Title>
<simplelist>
<member>db-control(1) - embedded database environment only!</member>
<member>rhn-charsets(8)</member>
<member>rhn-schema-version(8)</member>
<member>rhn-ssl-dbstore(8)</member>
<member>satellite-debug(8)</member>
<member>satellite-sync(8)</member>
</simplelist>
</RefSect1>
<RefSect1><Title>Authors</Title>
<simplelist>
<member>Todd Warner <email>taw@redhat.com</email></member>
<member>Mihai Ibanescu <email>misa@redhat.com</email></member>
</simplelist>
</RefSect1>
</RefEntry>
07070100000010000081B4000000000000000000000001624467C000000633000000000000000000000000000000000000001A00000000mgr-push/rhnpush_cache.py#
# Copyright (c) 2008--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
# rhnpush_cache.py
#
# Classes that control the caching of usernames and passwords,
# along with the retrieval of the username and password.
#
# UserInfo - Instantiations of this class are pickled.
# Cache won't be valid after a certain amount of time.
#
# CacheManager - Controls access to the cache.
import os
from rhnpush import utils
# This is the class that contains the session.
class RHNPushSession:
def __init__(self):
self.location = os.path.join(utils.get_home_dir(), ".rhnpushcache")
self.session = None
def setSessionString(self, session):
self.session = session
def getSessionString(self):
return self.session
def readSession(self):
sessionfile = open(self.location, "r")
self.session = sessionfile.read()
sessionfile.close()
def writeSession(self):
sessionfile = open(self.location, "w")
sessionfile.write(self.session)
sessionfile.close()
07070100000011000081B4000000000000000000000001624467C00000149C000000000000000000000000000000000000001B00000000mgr-push/rhnpush_config.py#
# Copyright (c) 2008--2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
#
# The configuration file parser for the rhnpush utility.
# The majority of this code is taken from rhncfg/config_common/local_config.py
#
# 11/11/2004 John Wregglesworth
#
import sys
# pylint: disable=F0401
if sys.version_info[0] == 3:
import configparser as ConfigParser
else:
import ConfigParser
# Class that contains the options read in from the config file.
# Uses a ConfigParser to create a dictionary of the configuration options.
# That dictionary is then used to add instance variables to the object dynamically.
class rhnpushConfigParser:
# pylint: disable=W0201
_instance = None
def __init__(self, filename=None, ensure_consistency=False):
# Defaults that are used if the ensure_consistency parameter of the constructor is true
# and the config file that is being read is missing some values.
self.options_defaults = {
'newest': '0',
'usage': '0',
'header': '0',
'test': '0',
'nullorg': '0',
'source': '0',
'stdin': '0',
'verbose': '0',
'force': '0',
'nosig': '0',
'list': '0',
'exclude': '',
'files': '',
'orgid': '',
'reldir': '',
'count': '',
'dir': '',
'server': 'http://rhn.redhat.com/APP',
'channel': '',
'cache_lifetime': '600',
'new_cache': '0',
'extended_test': '0',
'no_session_caching': '0',
'proxy': '',
'tolerant': '0',
'ca_chain': '/usr/share/rhn/RHN-ORG-TRUSTED-SSL-CERT',
'timeout': None
}
# Used to parse the config file.
self.settings = ConfigParser.ConfigParser()
# use options from the rhnpush section.
self.section = "rhnpush"
self.username = None
self.password = None
if filename:
self.filename = filename
self._read_config_files()
# Take all of the options read from the configuration file and add them as attributes
#(instance variables, member variables, whatever) of this object.
self._add_config_as_attr(ensure_consistency=ensure_consistency)
# Use the ConfigParser to read in the configuration file.
def _read_config_files(self):
try:
self.settings.read([self.filename])
except IOError:
e = sys.exc_info()[1]
print(("Config File Error: line %s, file %s: %s" % (e.lineno, e.filename, e)))
sys.exit(1)
def write(self, fileobj):
try:
self.settings.write(fileobj)
except IOError:
e = sys.exc_info()[1]
print(("Config File Error: line %s, file %s: %s" % (e.lineno, e.filename, e)))
sys.exit(1)
# Returns an option read in from the configuration files and specified by the string variable option.
# This function can probably be safely removed, since all configuration options become attributes
# of an instantiation of this class.
def get_option(self, option):
try:
return self.settings.get(self.section, option)
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
e = sys.exc_info()[1]
print("Option/Section Error: line %s, file %s: %s" % (e.lineno, e.filename, e))
sys.exit(1)
# Returns the keys of the attributes of the object.
def keys(self):
return list(self.__dict__.keys())
# Returns the keys of the options read in from the configuration files.
def _keys(self):
if self.settings.has_section(self.section):
return self.settings.options(self.section)
return ()
# Returns an option read in from the configuration files.
def __getitem__(self, item):
return self.get_option(item)
def __delitem__(self, item):
pass
def __len__(self):
pass
def __setitem__(self, key, value):
pass
# Takes all of the configuration options read in by the ConfigParser and makes them attributes of the object.
def _add_config_as_attr(self, ensure_consistency=False):
for k in self._keys():
self.__dict__[k] = self.settings.get(self.section, k)
# ensuring consistency only checks for missing configuration option.
if ensure_consistency:
for thiskey in self.options_defaults:
if thiskey not in self.__dict__:
self.__dict__[thiskey] = self.options_defaults[thiskey]
07070100000012000081B4000000000000000000000001624467C000001367000000000000000000000000000000000000002000000000mgr-push/rhnpush_confmanager.py#
# Copyright (c) 2008--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import sys
import os
from rhnpush import rhnpush_config
from rhnpush import utils
class ConfManager:
def __init__(self, optionparser, store_true_list):
sysdir = '/etc/sysconfig/rhn'
homedir = utils.get_home_dir()
default = 'rhnpushrc'
regular = '.rhnpushrc'
deffile = os.path.join(sysdir, default)
regfile = os.path.join(homedir, regular)
cwdfile = os.path.join(os.getcwd(), regular)
self.cfgFileList = [deffile, regfile, cwdfile]
self.defaultconfig = rhnpush_config.rhnpushConfigParser(ensure_consistency=True)
# Get a reference to the object containing command-line options
self.cmdconfig = optionparser
self.store_true_list = store_true_list
# Change the files options of the self.userconfig
# Change the exclude options of the self.userconfig
def _files_to_list(self):
# Change the files options to lists.
if ('files' in self.defaultconfig.__dict__ and
not isinstance(self.defaultconfig.files, type([]))):
self.defaultconfig.files = [x.strip() for x in
self.defaultconfig.files.split(',')]
# Change the exclude options to list.
if ('exclude' in self.defaultconfig.__dict__ and
not isinstance(self.defaultconfig.__dict__['exclude'], type([]))):
self.defaultconfig.exclude = [x.strip() for x in
self.defaultconfig.exclude.split(',')]
def get_config(self):
for f in self.cfgFileList:
if os.access(f, os.F_OK):
if not os.access(f, os.R_OK):
print(("rhnpush does not have read permission on %s" % f))
sys.exit(1)
config2 = rhnpush_config.rhnpushConfigParser(f)
self.defaultconfig, config2 = utils.make_common_attr_equal(self.defaultconfig, config2)
self._files_to_list()
# Change the channel string into a list of strings.
# pylint: disable=E1103
if not self.defaultconfig.channel:
# if no channel then make it null array instead of
# an empty string array from of size 1 [''] .
self.defaultconfig.channel = []
else:
self.defaultconfig.channel = [x.strip() for x in
self.defaultconfig.channel.split(',')]
# Get the command line arguments. These take precedence over the other settings
argoptions, files = self.cmdconfig.parse_args()
# Makes self.defaultconfig compatible with argoptions by changing all '0' value attributes to None.
_zero_to_none(self.defaultconfig, self.store_true_list)
# If verbose isn't set at the command-line, it automatically gets set to zero. If it's at zero, change it to
# None so the settings in the config files take precedence.
if argoptions.verbose == 0:
argoptions.verbose = None
# Orgid, count, cache_lifetime, and verbose all need to be integers, just like in argoptions.
if self.defaultconfig.orgid:
self.defaultconfig.orgid = int(self.defaultconfig.orgid)
if self.defaultconfig.count:
self.defaultconfig.count = int(self.defaultconfig.count)
if self.defaultconfig.cache_lifetime:
self.defaultconfig.cache_lifetime = int(self.defaultconfig.cache_lifetime)
if self.defaultconfig.verbose:
self.defaultconfig.verbose = int(self.defaultconfig.verbose)
if self.defaultconfig.timeout:
self.defaultconfig.timeout = int(self.defaultconfig.timeout)
# Copy the settings in argoptions into self.defaultconfig.
self.defaultconfig, argoptions = utils.make_common_attr_equal(self.defaultconfig, argoptions)
# Make sure files is in the correct format.
if self.defaultconfig.files != files:
self.defaultconfig.files = files
return self.defaultconfig
# Changes every option in config that is also in store_true_list that is set to '0' to None
def _zero_to_none(config, store_true_list):
for opt in config.keys():
for cmd in store_true_list:
if str(opt) == cmd and config.__dict__[opt] == '0':
config.__dict__[opt] = None
07070100000013000081FD000000000000000000000001624467C00000689D000000000000000000000000000000000000001900000000mgr-push/rhnpush_main.py#
# Copyright (c) 2008--2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
#
"""
Management tool for the Spacewalk Proxy.
This script performs various management operations on the Spacewalk Proxy:
- Creates the local directory structure needed to store local packages
- Uploads packages from a given directory to the RHN servers
- Optionally, once the packages are uploaded, they can be linked to (one or
more) channels, and copied in the local directories for these channels.
- Lists the RHN server's vision on a certain channel
- Checks if the local image of the channel (the local directory) is in sync
with the server's image, and prints the missing packages (or the extra
ones)
"""
import os
import random
import sys
import time
# pylint: disable=W0402
from optparse import Option, OptionParser
# pylint: disable=F0401,E0611
from rhn.connections import idn_ascii_to_puny
from rhn import rpclib
from rhn.i18n import sstr
from uyuni.common.rhn_pkg import InvalidPackageError, package_from_filename
from uyuni.common.usix import raise_with_tb
from rhnpush.utils import tupleify_urlparse
from rhnpush import rhnpush_confmanager, uploadLib, rhnpush_v2
if sys.version_info[0] == 3:
import urllib.parse as urlparse
else:
import urlparse
# Global settings
BUFFER_SIZE = 65536
HEADERS_PER_CALL = 10
DEBUG = 0
RPMTAG_NOSOURCE = 1051
def main():
# Initialize a command-line processing object with a table of options
optionsTable = [
Option('-v', '--verbose', action='count', help='Increase verbosity',
default=0),
Option('-d', '--dir', action='store',
help='Process packages from this directory'),
Option('-c', '--channel', action='append',
help='Manage this channel (specified by label)'),
Option('-n', '--count', action='store',
help='Process this number of headers per call', type='int'),
Option('-l', '--list', action='store_true',
help='Only list the specified channels'),
Option('-r', '--reldir', action='store',
help='Relative dir to associate with the file'),
Option('-o', '--orgid', action='store',
help='Org ID', type='int'),
Option('-u', '--username', action='store',
help='Use this username to connect to RHN/Satellite'),
Option('-p', '--password', action='store',
help='Use this password to connect to RHN/Satellite'),
Option('-s', '--stdin', action='store_true',
help='Read the package names from stdin'),
Option('-X', '--exclude', action='append',
help='Exclude packages that match this glob expression'),
Option('--force', action='store_true',
help='Force the package upload (overwrites if already uploaded)'),
Option('--nosig', action='store_true', help='Push unsigned packages'),
Option('--newest', action='store_true',
help='Only push the packages that are newer than the server ones'),
Option('--nullorg', action='store_true', help='Use the null org id'),
Option('--header', action='store_true',
help='Upload only the header(s)'),
Option('--source', action='store_true',
help='Upload source package information'),
Option('--server', action='store',
help='Push to this server (http[s]://<hostname>/APP)'),
Option('--proxy', action='store',
help='Use proxy server (<server>:<port>)'),
Option('--test', action='store_true',
help='Only print the packages to be pushed'),
Option('-?', '--usage', action='store_true',
help='Briefly describe the options'),
Option('-N', '--new-cache', action='store_true',
help='Create a new username/password cache'),
Option('--extended-test', action='store_true',
help='Perform a more verbose test'),
Option('--no-session-caching', action='store_true',
help='Disables session-token authentication.'),
Option('--tolerant', action='store_true',
help='If rhnpush errors while uploading a package, continue uploading the rest of the packages.'),
Option('--ca-chain', action='store', help='alternative SSL CA Cert'),
Option('--timeout', action='store', type='int', metavar='SECONDS',
help='Change default connection timeout.')
]
# Having to maintain a store_true list is ugly. I'm trying to get rid of this.
true_list = ['usage', 'test', 'source', 'header', 'nullorg', 'newest',
'nosig', 'force', 'list', 'stdin', 'new_cache',
'extended_test', 'no_session_caching', 'tolerant']
# pylint: disable=E1101,E1103
optionParser = OptionParser(option_list=optionsTable, usage="%prog [OPTION] [<package>]")
manager = rhnpush_confmanager.ConfManager(optionParser, true_list)
options = manager.get_config()
upload = UploadClass(options, files=options.files)
if options.usage:
optionParser.print_usage()
sys.exit(0)
if options.proxy:
options.proxy = idn_ascii_to_puny(options.proxy)
if options.list:
if not options.channel:
upload.die(1, "Must specify a channel for --list to work")
upload.list()
return
if options.dir and not options.stdin:
upload.directory()
elif options.stdin and not options.dir:
upload.readStdin()
elif options.dir and options.stdin:
upload.readStdin()
upload.directory()
if options.exclude:
upload.filter_excludes()
if options.newest:
if not options.channel:
upload.die(1, "Must specify a channel for --newest to work")
upload.newest()
if not upload.files:
if upload.newest:
print("No new files to upload; exiting")
else:
print("Nothing to do (try --help for more options)")
sys.exit(0)
if options.test:
upload.test()
return
if options.extended_test:
upload.extended_test()
return
if options.header:
upload.uploadHeaders()
return
ret = upload.packages()
if ret != 0:
return 1
class UploadClass(uploadLib.UploadClass):
# pylint: disable=E1101,W0201,W0632
def __init__(self, options, files=None):
uploadLib.UploadClass.__init__(self, options, files)
self.url_v2 = None
def setURL(self):
server = sstr(idn_ascii_to_puny(self.options.server))
if server is None:
self.die(1, "Required parameter --server not supplied")
scheme, netloc, path, params, query, fragment = tupleify_urlparse(
urlparse.urlparse(server))
if not netloc:
# No schema - trying to patch it up ourselves?
server = "http://%s" % server
scheme, netloc, path, params, query, fragment = tupleify_urlparse(
urlparse.urlparse(server))
if not netloc:
self.die(2, "Invalid URL %s" % server)
if path == '':
path = '/APP'
if scheme.lower() not in ('http', 'https'):
self.die(3, "Unknown URL scheme %s" % scheme)
self.url = urlparse.urlunparse((scheme, netloc, path, params, query,
fragment))
self.url_v2 = urlparse.urlunparse((scheme, netloc, "/PACKAGE-PUSH",
params, query, fragment))
def setOrg(self):
if self.options.nullorg:
if self.options.force:
self.die(1, "ERROR: You cannot force a package to a nullorg channel.")
else:
# They push things to the None org id
self.orgId = ''
else:
self.orgId = self.options.orgid or -1
def setForce(self):
if self.options.force:
self.force = 4
else:
self.force = None
def setRelativeDir(self):
self.relativeDir = self.options.reldir
def setChannels(self):
self.channels = self.options.channel or []
# pylint: disable=W0702
def _test_force(self):
test_force_str = "Setting force flag: %s"
test_force = "Passed"
try:
self.setForce()
except:
test_force = "Failed"
print(test_force_str % test_force)
def _test_set_org(self):
test_set_org_str = "Setting the org: %s"
test_set_org = "Passed"
try:
self.setOrg()
except:
test_set_org = "Failed"
print(test_set_org_str % test_set_org)
def _test_set_url(self):
test_set_url_str = "Setting the URL: %s"
test_set_url = "Passed"
try:
self.setURL()
except:
test_set_url = "Failed"
print(test_set_url_str % test_set_url)
def _test_set_channels(self):
test_set_channels_str = "Setting the channels: %s"
test_set_channels = "Passed"
try:
self.setChannels()
except:
test_set_channels = "Failed"
print(test_set_channels_str % test_set_channels)
def _test_username_password(self):
test_user_pass_str = "Setting the username and password: %s"
test_user_pass = "Passed"
try:
self.setUsernamePassword()
except:
test_user_pass = "Failed"
print(test_user_pass_str % test_user_pass)
def _test_set_server(self):
test_set_server_str = "Setting the server: %s"
test_set_server = "Passed"
try:
self.setServer()
except:
test_set_server = "Failed"
print(test_set_server_str % test_set_server)
def _test_connect(self):
auth_ret = uploadLib.call(self.server.packages.test_login,
self.username, self.password)
if auth_ret == 1:
test_auth = "Passed"
else:
test_auth = "Failed"
print("Testing connection and authentication: %s" % test_auth)
def _test_access(self):
access_ret = callable(self.server.packages.channelPackageSubscriptionBySession)
if access_ret == 1:
test_access = "Passed"
else:
test_access = "Failed"
print("Testing access to upload functionality on server: %s" % test_access)
# 12/22/05 wregglej 173287 Added a this funtion to test the new session authentication stuff.
# It still needs work.
def _test_authenticate(self):
self.authenticate()
def extended_test(self):
self._test_force()
self._test_set_org()
self._test_set_url()
self._test_set_channels()
self._test_username_password()
self._test_set_server()
self._test_connect()
self._test_access()
print("The files that would have been pushed:")
self.test()
def packages(self):
self.setForce()
# set the org
self.setOrg()
# set the URL
self.setURL()
# set the channels
self.setChannels()
# set the server
self.setServer()
# 12/22/05 wregglej 173287 authenticate the session.
self.authenticate()
# Do we have the new-style handler available?
# ping the server for status
self.warn(2, "url is", self.url_v2)
ping = rhnpush_v2.PingPackageUpload(self.url_v2, self.options.proxy)
ping_status, errmsg, headerinfo = ping.ping()
self.warn(2, "Result codes:", ping_status, errmsg)
# move patch clusters to the end because all the patches in the cluster
# have to be pushed before the cluster itself
files1 = []
files2 = []
for filename in self.files:
if filename.startswith('patch-cluster-'):
files2.append(filename)
else:
files1.append(filename)
self.files = files1 + files2
channel_packages = []
# a little fault tolarence is in order
random.seed()
tries = 3
# satellites < 4.1.0 are no more supported
if sys.version_info[0] == 3:
pack_exist_check = headerinfo.get('X-RHN-Check-Package-Exists')
else:
pack_exist_check = headerinfo.getheader('X-RHN-Check-Package-Exists')
if not pack_exist_check:
self.die(-1, "Pushing to Satellite < 4.1.0 is not supported.")
(server_digest_hash, pkgs_info, digest_hash) = self.check_package_exists()
for pkg in self.files:
ret = None # pkilambi:errors off as not initialized.this fixes it.
# temporary fix for picking pkgs instead of full paths
pkg_key = (pkg.strip()).split('/')[-1]
if pkg_key not in server_digest_hash:
continue
checksum_type, checksum = digest = digest_hash[pkg_key]
server_digest = tuple(server_digest_hash[pkg_key])
# compare checksums for existance check
if server_digest == digest and not self.options.force:
channel_packages.append(pkgs_info[pkg_key])
self.warn(1, "Package %s already exists on the SUSE Manager Server-- Skipping Upload...." % pkg)
continue
elif server_digest == ():
self.warn(1,"Package %s Not Found on SUSE Manager Server -- Uploading" % pkg)
elif server_digest == "on-disk" and not self.options.force:
channel_packages.append(pkgs_info[pkg_key])
self.warn(0, "Package on disk but not on db -- Skipping Upload " % pkg)
continue
elif server_digest != digest:
if self.options.force:
self.warn(1, "Package checksum %s mismatch -- Forcing Upload" % pkg)
else:
msg = "Error: Package %s already exists on the server with" \
" a different checksum. Skipping upload to prevent" \
" overwriting existing package. (You may use rhnpush with" \
" the --force option to force this upload if the" \
" force_upload option is enabled on your server.)\n" % pkg
if not self.options.tolerant:
self.die(-1, msg)
self.warn(0, msg)
continue
for _t in range(0, tries):
try:
ret = self.package(pkg, checksum_type, checksum)
if ret is None:
raise uploadLib.UploadError()
# TODO: Revisit this. We throw this error all over the place,
# but doing so will cause us to skip the --tolerant logic
# below. I don't think we really want this behavior.
# There are some cases where we don't want to retry 3
# times, but not at the expense of disabling the tolerant
# flag, IMHO. This loop needs some lovin'. -- pav
# FIX: it checks for tolerant flag and aborts only if the flag is
#not specified
except uploadLib.UploadError:
ue = sys.exc_info()[1]
if not self.options.tolerant:
self.die(1, ue)
self.warn(2, ue)
except AuthenticationRequired:
# session expired so we re-authenticate for the process to complete
# this uses the username and password from memory if available
# else it prompts for one.
self.authenticate()
except:
self.warn(2, sys.exc_info()[1])
wait = random.randint(1, 5)
self.warn(0, "Waiting %d seconds and trying again..." % wait)
time.sleep(wait)
# The else clause gets executed in the stuff in the try-except block *succeeds*.
else:
break
# if the preceeding for-loop exits without a call to break, then this else clause gets called.
# What's kind of weird is that if the preceeding for-loop doesn't call break then an error occurred
# and all of retry attempts failed. If the for-loop *does* call break then everything is hunky-dory.
# In short, this else clause only get's called if something is F.U.B.A.R and the retry attempts don't
# fix anything.
else:
if not self.options.tolerant:
# pkilambi:bug#176358:this exits with a error code of 1
self.die(1, "Giving up after %d attempts" % tries)
else:
print("Giving up after %d attempts and continuing on..." % (tries,))
# 5/13/05 wregglej - 154248 ?? we still want to add the packages if they're source.
if ret and self.channels: # and ret['arch'] != 'src':
# Don't bother to add the package if
# no channel was specified or a source rpm was passed
channel_packages.append(ret)
# self.channels is never None, it always has at least one entry with an empty string.
if len(self.channels) == 1 and self.channels[0] == '':
return
info = {
'packages': channel_packages,
'channels': self.channels
}
if self.orgId == '' or self.orgId > 0:
info['orgId'] = self.orgId
# 2/3/06 wregglej 173287 Added check to see if we can use session tokens.
if channel_packages:
self.authenticate()
uploadLib.call(self.server.packages.channelPackageSubscriptionBySession,
self.session.getSessionString(), info)
return 0
# does an existance check of the packages to be uploaded and returns their checksum and other info
def check_package_exists(self):
self.warn(2, "Computing checksum and package info. This may take some time ...")
pkg_hash = {}
digest_hash = {}
for pkg in self.files:
pkg_info = {}
pkg_key = (pkg.strip()).split('/')[-1]
if not os.access(pkg, os.R_OK):
if not self.options.tolerant:
self.die(-1, "Could not read file %s" % pkg)
self.warn(-1, "Could not read file %s" % pkg)
continue
try:
a_pkg = package_from_filename(pkg)
a_pkg.read_header()
a_pkg.payload_checksum()
except InvalidPackageError:
if not self.options.tolerant:
self.die(-1, "ERROR: %s: This file doesn't appear to be a package" % pkg)
self.warn(2, "ERROR: %s: This file doesn't appear to be a package" % pkg)
continue
except IOError:
if not self.options.tolerant:
self.die(-1, "ERROR: %s: No such file or directory available" % pkg)
self.warn(2, "ERROR: %s: No such file or directory available" % pkg)
continue
digest_hash[pkg_key] = (a_pkg.checksum_type, a_pkg.checksum)
a_pkg.input_stream.close()
for tag in ('name', 'version', 'release', 'epoch', 'arch'):
val = a_pkg.header[tag]
if val is None:
val = ''
pkg_info[tag] = val
# b195903:the arch for srpms should be obtained by is_source check
# instead of checking arch in header
if a_pkg.header.is_source:
if not self.options.source:
self.die(-1, "ERROR: Trying to Push src rpm, Please re-try with --source.")
if RPMTAG_NOSOURCE in a_pkg.header.keys():
pkg_info['arch'] = 'nosrc'
else:
pkg_info['arch'] = 'src'
pkg_info['checksum_type'] = a_pkg.checksum_type
pkg_info['checksum'] = a_pkg.checksum
pkg_hash[pkg_key] = pkg_info
if self.options.nullorg:
# to satisfy xmlrpc from None values.
orgid = 'null'
else:
orgid = ''
info = {
'packages': pkg_hash,
'channels': self.channels,
'org_id': orgid,
'force': self.options.force or 0
}
# rpc call to get checksum info for all the packages to be uploaded
if not self.options.source:
# computing checksum and other info is expensive process and session
# could have expired.Make sure its re-authenticated.
self.authenticate()
if uploadLib.exists_getPackageChecksumBySession(self.server):
checksum_data = uploadLib.getPackageChecksumBySession(self.server,
self.session.getSessionString(), info)
else:
# old server only md5 capable
checksum_data = uploadLib.getPackageMD5sumBySession(self.server,
self.session.getSessionString(), info)
else:
# computing checksum and other info is expensive process and session
# could have expired.Make sure its re-authenticated.
self.authenticate()
if uploadLib.exists_getPackageChecksumBySession(self.server):
checksum_data = uploadLib.getSourcePackageChecksumBySession(self.server,
self.session.getSessionString(), info)
else:
# old server only md5 capable
checksum_data = uploadLib.getSourcePackageMD5sumBySession(self.server,
self.session.getSessionString(), info)
return (checksum_data, pkg_hash, digest_hash)
def package(self, package, fileChecksumType, fileChecksum):
self.warn(1, "Uploading package %s" % package)
if not os.access(package, os.R_OK):
self.die(-1, "Could not read file %s" % package)
try:
h = uploadLib.get_header(package, source=self.options.source)
except uploadLib.UploadError:
e = sys.exc_info()[1]
# GS: MALFORMED PACKAGE
print("Unable to load package", package, ":", e)
return None
if hasattr(h, 'packaging'):
packaging = h.packaging
else:
packaging = 'rpm'
if packaging == 'rpm' and self.options.nosig is None and not h.is_signed():
# pkilambi:bug#173886:force exit to check for sig if --nosig
raise uploadLib.UploadError("ERROR: %s: unsigned rpm (use --nosig to force)" % package)
try:
ret = self._push_package_v2(package, fileChecksumType, fileChecksum)
except uploadLib.UploadError:
e = sys.exc_info()[1]
ret, diff_level, pdict = e.args[:3]
severities = {
1: 'path changed',
2: 'package resigned',
3: 'differing build times or hosts',
4: 'package recompiled',
}
if diff_level in severities:
strmsg = \
"Error: Package with same name already exists on " + \
"server but contents differ (" + \
severities[diff_level] + \
"). Use --force or remove old package before " + \
"uploading the newer version."
else:
strmsg = "Error: severity %s" % diff_level
self.warn(-1, "Uploading failed for %s\n%s\n\tDiff: %s" %
(package, strmsg, pdict['diff']['diff']))
if diff_level != 1:
# This will prevent us from annoyingly retrying when there is
# no reason to.
raise uploadLib.UploadError()
return ret
return ret
def _push_package_v2(self, package, fileChecksumType, fileChecksum):
self.warn(1, "Using POST request")
pu = rhnpush_v2.PackageUpload(self.url_v2, self.options.proxy)
pu.set_session(self.session.getSessionString())
pu.set_force(self.options.force)
pu.set_null_org(self.options.nullorg)
pu.set_timeout(self.options.timeout)
status, msgstr = pu.upload(package, fileChecksumType, fileChecksum)
ret = {}
for tag in ('name', 'version', 'release', 'epoch', 'arch'):
val = getattr(pu, "package_%s" % tag)
if val is None:
val = ''
ret[tag] = val
ret['checksum_type'] = fileChecksumType
ret['checksum'] = fileChecksum
if status == 400:
# Bad request - something bad happened
try:
data = rpclib.xmlrpclib.loads(msgstr)
except:
# Raise the exception instead of silently dying
raise_with_tb(uploadLib.UploadError("Error pushing %s: %s (%s)" %
(package, msgstr, status)), sys.exc_info()[2])
(diff_dict, ), methodname = data
del methodname
diff_level = diff_dict['level']
pdict = diff_dict['diff']
raise uploadLib.UploadError(ret, diff_level, pdict)
if status == 403:
# auth expired raise an exception to grab one
raise AuthenticationRequired()
if status != 200:
self.die(1, "Error pushing %s: %s (%s)" % (package, msgstr, status))
return ret
class AuthenticationRequired(Exception):
pass
if __name__ == '__main__':
# test code
sys.exit(main() or 0)
07070100000014000081B4000000000000000000000001624467C000000912000000000000000000000000000000000000001700000000mgr-push/rhnpush_v2.py#
# Copyright (c) 2008--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
#
# Package uploading tool
#
import base64
from rhnpush import connection
class PackageUpload(connection.PackageUpload):
user_agent = "rhnpush"
def set_auth(self, username, password):
auth_vals = self.encode_values([username, password])
self.headers["%s-%s" % (self.header_prefix, "Auth")] = auth_vals
def set_session(self, session_string):
self.headers["%s-%s" % (self.header_prefix, "Auth-Session")] = session_string
def set_force(self, force):
if force:
force = 1
else:
force = 0
self.headers["%s-%s" % (self.header_prefix, "Force")] = str(force)
def set_null_org(self, null_org):
if null_org:
self.headers["%s-%s" % (self.header_prefix, "Null-Org")] = "1"
def set_timeout(self, timeout):
self.connection.set_timeout(timeout)
# Encodes an array of variables into Base64 (column-separated)
@staticmethod
def encode_values(arr):
val = ':'.join([x.strip() for x in map(base64.encodestring, arr)])
# Get rid of the newlines
val = val.replace('\n', '')
# And split the result into lines of fixed size
line_len = 80
result = []
start = 0
while 1:
if start >= len(val):
break
result.append(val[start:start + line_len])
start = start + line_len
return result
class PingPackageUpload(connection.PackageUpload):
user_agent = "rhnpush-ping"
def ping(self):
self.send_http("GET")
# return the header info as well to check for capabilities.
return self._response.status, self._response.reason, self._response.msg
07070100000015000081B4000000000000000000000001624467C00000060D000000000000000000000000000000000000001300000000mgr-push/rhnpushrc#This is the default config file for rhnpush. Place it in your home directory.
[rhnpush]
#Only push the packages that are newer than those on the server
newest = 0
#Briefly describe the options
usage = 0
#Upload only the header(s)
header = 0
#Only print the packages that would otherwise have been pushed
test = 0
#Use the null org id
nullorg = 0
#Upload source package information
source = 0
#Read the package names from stdin
stdin = 0
#Increase verbosity
verbose = 0
#Force the package upload (overwrites if already uploaded)
force = 0
#Push unsigned packages
nosig = 0
#Only list the specified channels
list = 0
#Exclude the packages that match this glob expression
exclude =
files =
#Organization ID
orgid =
#Relative directory to associate with the package
reldir =
#Process this number of headers per call
count =
#Process packages from this directory
dir =
#Push to this server (http[s]://<hostname>/APP)
server = https://localhost/APP
#Manage this channel(s)
channel =
#Determines how long the cache will last, in seconds
cache_lifetime = 600
#Reset the cache
new_cache = 0
#Run the extended version of the test
extended_test = 0
#Disable the session-token support
no_session_caching = 0
#Have rhnpush be tolerant of errors when pushing a large number of packages.
tolerant = 0
#The CA cert used to verify the ssl server
ca_chain = /usr/share/rhn/RHN-ORG-TRUSTED-SSL-CERT
#Default connection timeout, (no value for default)
timeout = 300
07070100000016000081B4000000000000000000000001624467C0000015AF000000000000000000000000000000000000001400000000mgr-push/rpm2mpm.py#
# Copyright (c) 2008--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import sys
import time
# pylint: disable=F0401
from uyuni.common import rhn_rpm, rhn_mpm
from uyuni.common.usix import ListType, TupleType
def main():
packages = sys.argv[1:]
if not packages:
return
for pkgfile in packages:
# Try to open the package as a patch first
with open(pkgfile) as f:
header = rhn_rpm.get_package_header(file_obj=f)
p = rpm_to_mpm(header, f)
dest_filename = _compute_filename(p.header)
print("Writing out the package to %s" % dest_filename)
with open(dest_filename, "w+") as dest_file:
p.write(dest_file)
def _compute_filename(hdr):
return '%s-%s.%s.mpm' % (hdr['name'], hdr['version'], hdr['arch'])
def rpm_to_mpm(header, file_stream):
tag_map = {
'package_group': 'group',
'rpm_version': 'rpmversion',
'payload_size': 'archivesize',
'payload_format': 'payloadformat',
'build_host': 'buildhost',
'build_time': 'buildtime',
'source_rpm': 'sourcerpm',
}
tags = [
'name',
'epoch',
'version',
'release',
'arch',
'description',
'summary',
'license',
'package_group',
'rpm_version',
'payload_size',
'payload_format',
'build_host',
'build_time',
'cookie',
'vendor',
'source_rpm',
'sigmd5',
'sigpgp',
'siggpg',
'sigsize',
]
result = {}
for t in tags:
tt = tag_map.get(t, t)
result[t] = header[tt]
# Add files
result['files'] = _extract_files(header)
# Dependency
result['provides'] = _extract_rpm_requires(header)
result['requires'] = _extract_rpm_provides(header)
result['conflicts'] = _extract_rpm_conflicts(header)
result['obsoletes'] = _extract_rpm_obsoletes(header)
result['changelog'] = _extract_rpm_changelog(header)
# md5sum, package_size
file_stream.seek(0, 2)
file_size = file_stream.tell()
result['package_size'] = file_size
is_source = 0
if header.is_source:
is_source = 1
result['is_source'] = is_source
result['package_type'] = 'rpm'
h = rhn_mpm.MPM_Header(result)
p = rhn_mpm.MPM_Package()
p.header = h
p.payload_stream = file_stream
return p
def _extract_files(header):
tag_maps = {
'name': 'filenames',
'device': 'filedevices',
'inode': 'fileinodes',
'file_mode': 'filemodes',
'username': 'fileusername',
'groupname': 'filegroupname',
'rdev': 'filerdevs',
'file_size': 'filesizes',
'mtime': 'filemtimes',
'md5': 'filemd5s',
'linkto': 'filelinktos',
'flags': 'fileflags',
'verifyflags': 'fileverifyflags',
'lang': 'filelangs',
}
files = _extract_array_fields(header, tag_maps)
# Munge the mtime
for f in files:
f['mtime'] = gmtime(f['mtime'])
return files
def _extract_rpm_provides(header):
tag_maps = {
'name': 'provides',
'version': 'provideversion',
'flags': 'provideflags',
}
return _extract_array_fields(header, tag_maps)
def _extract_rpm_requires(header):
tag_maps = {
'name': 'requirename',
'version': 'requireversion',
'flags': 'requireflags',
}
return _extract_array_fields(header, tag_maps)
def _extract_rpm_conflicts(header):
tag_maps = {
'name': 'conflictname',
'version': 'conflictversion',
'flags': 'conflictflags',
}
return _extract_array_fields(header, tag_maps)
def _extract_rpm_obsoletes(header):
tag_maps = {
'name': 'obsoletename',
'version': 'obsoleteversion',
'flags': 'obsoleteflags',
}
return _extract_array_fields(header, tag_maps)
def _extract_rpm_changelog(header):
tag_maps = {
'name': 'changelogname',
'text': 'changelogtext',
'time': 'changelogtime',
}
cl = _extract_array_fields(header, tag_maps)
# Munge the changelog time
for c in cl:
c['time'] = gmtime(c['time'])
return cl
def _extract_array_fields(header, tag_maps):
# First determine the number of entries
key = list(tag_maps.keys())[0]
rpmtag = tag_maps.get(key)
arr = header[rpmtag]
if arr is None:
# nothing to do
return []
count = len(arr)
result = []
for i in range(count):
tag_dict = {}
for key, rpmtag in tag_maps.items():
arr = header[rpmtag]
if not isinstance(arr, (ListType, TupleType)):
arr = [arr]
tag_dict[key] = arr[i]
result.append(tag_dict)
return result
def gmtime(timestamp):
ttuple = time.gmtime(timestamp)
return "%d-%02d-%02d %02d:%02d:%02d" % ttuple[:6]
if __name__ == '__main__':
sys.exit(main() or 0)
07070100000017000041FD000000000000000000000001624467C000000000000000000000000000000000000000000000000E00000000mgr-push/test07070100000018000081B4000000000000000000000001624467C0000010E7000000000000000000000000000000000000002200000000mgr-push/test/testRhnpushCache.py#
# Copyright (c) 2008--2015 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import unittest
import rhnpush_cache
import time
class UserInfoTestCase(unittest.TestCase):
def setUp(self):
self.userinfo = rhnpush_cache.UserInfo(5, username='wregglej', password='password')
def tearDown(self):
self.userinfo = None
def testCheckCacheTrue(self):
assert self.userinfo.checkCache() == True
def testCheckCacheFalse(self):
time.sleep(7)
assert self.userinfo.checkCache() == False
def testSetUsernamePassword(self):
self.userinfo = rhnpush_cache.UserInfo(5, username='wregglej', password='password')
self.userinfo.setUsernamePassword('aaaa', 'bbbb')
assert self.userinfo.username != 'wregglej' and self.userinfo.password != 'password'
def testSetUsernamePassword3(self):
self.userinfo = rhnpush_cache.UserInfo(5, username='wregglej', password='password')
self.userinfo.setUsernamePassword('aaaa', 'bbbb')
assert self.userinfo.username == 'aaaa' and self.userinfo.password == 'bbbb'
def testGetUsernamePassword(self):
self.userinfo = rhnpush_cache.UserInfo(5, username='wregglej', password='password')
assert self.userinfo.username == 'wregglej' and self.userinfo.password == 'password'
def testIsFresh(self):
self.userinfo = rhnpush_cache.UserInfo(5, username='wregglej', password='password')
assert self.userinfo.isFresh() == True
def testIsntFresh(self):
time.sleep(6)
assert self.userinfo.isFresh() == False
def testSetCacheLifetime(self):
self.userinfo = rhnpush_cache.UserInfo(5, username='wregglej', password='password')
self.userinfo.setCacheLifetime(6667)
assert self.userinfo.cache_lifetime != 5 and self.userinfo.cache_lifetime == 6667
def testGetTimeLeft(self):
self.userinfo = rhnpush_cache.UserInfo(10, username='wregglej', password='password')
time.sleep(2.0)
assert self.userinfo.getTimeLeft() >= 7.98 and self.userinfo.getTimeLeft() <= 8.002
class CacheManagerTestCase(unittest.TestCase):
def setUp(self):
self.cache = rhnpush_cache.CacheManager(5)
def tearDown(self):
self.cache = None
def testIsFresh(self):
self.cache = rhnpush_cache.CacheManager(5)
self.cache.setUsernamePassword('a', 'b')
assert self.cache.isFresh() == True
def testIsntFresh(self):
self.cache = rhnpush_cache.CacheManager(5)
time.sleep(7)
assert self.cache.isFresh() == False
def testSetUsernamePassword(self):
self.cache = rhnpush_cache.CacheManager(5)
self.cache.setUsernamePassword('wregglej', 'password')
# print self.cache.cache.username
assert self.cache.cache.username == 'wregglej' and self.cache.cache.password == 'password'
def testSetUsernamePassword2(self):
self.cache = rhnpush_cache.CacheManager(5)
self.cache.setUsernamePassword('wregglej', 'password')
self.cache.setUsernamePassword('aaaa', 'bbbb')
assert self.cache.cache.username == 'aaaa' and self.cache.cache.password == 'bbbb'
def testGetUsernamePassword(self):
self.cache = rhnpush_cache.CacheManager(5)
self.cache.setUsernamePassword('wregglej', 'password')
u, p = self.cache.getUsernamePassword()
assert u == 'wregglej' and p == 'password'
def testSetCacheLifetime(self):
self.cache = rhnpush_cache.CacheManager(5)
self.cache.setCacheLifetime(10)
assert self.cache.cache.cache_lifetime == 10 and self.cache.cache.cache_lifetime != 5
def testWriteCache(self):
pass
def testGetTimeLeft(self):
pass
if __name__ == "__main__":
unittest.main()
07070100000019000081B4000000000000000000000001624467C0000007E0000000000000000000000000000000000000002300000000mgr-push/test/testRhnpushConfig.py#
# Copyright (c) 2008--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import rhnpush_config
import unittest
# pylint: disable=W0212,E1101,R0904
class RhnConfigTestCase(unittest.TestCase):
def setUp(self):
self.userconfig = rhnpush_config.rhnpushConfigParser('.rhnpushrc')
self.defaultconfig = rhnpush_config.rhnpushConfigParser('/etc/sysconfig/rhn/rhnpushrc')
def tearDown(self):
self.userconfig = None
self.defaultconfig = None
def testReadConfigFiles(self):
self.userconfig._read_config_files()
self.defaultconfig._read_config_files()
assert self.userconfig.settings != None and self.defaultconfig.settings != None
def testGetOption(self):
a = self.userconfig.get_option('usage')
b = self.defaultconfig.get_option('usage')
assert a != None and b != None and a == '0' and b == '0'
def testKeys(self):
a = list(self.userconfig.keys())
b = list(self.defaultconfig.keys())
assert a != None and b != None
def test_keys(self):
a = self.userconfig._keys()
b = self.defaultconfig._keys()
assert a != None and b != None
def testGetItem(self):
pass
def testAddConfigAsAttr(self):
self.userconfig._add_config_as_attr()
self.userconfig._add_config_as_attr()
assert self.userconfig.usage != None and self.defaultconfig.usage != None
if __name__ == "__main__":
unittest.main()
0707010000001A000081B4000000000000000000000001624467C0000007C9000000000000000000000000000000000000001B00000000mgr-push/test/testUtils.py#
# Copyright (c) 2008--2016 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import unittest
import utils
class TestObj1:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
self._d = '1'
self._e = '2'
@staticmethod
def _private_function():
print("This is privatei to TestObj1 instances")
@staticmethod
def public_function():
print("This is public and belongs to TestObj1")
class TestObj2:
def __init__(self):
self.a = 4
self.b = 5
self.c = 6
self._d = '4'
self._e = '5'
self.f = 'aaa'
@staticmethod
def _private_function():
print("This is private to TestObj2 instances")
@staticmethod
def public_function():
print("This is public and belongs to TestObj2")
class UtilsTestCase(unittest.TestCase):
def setUp(self):
self.obj1 = TestObj1()
self.obj2 = TestObj2()
def tearDown(self):
self.obj1 = None
self.obj2 = None
# pylint: disable=W0212
def testMakeCommonAttrEqual(self):
self.obj1, self.obj2 = utils.make_common_attr_equal(self.obj1, self.obj2)
assert (self.obj1._d == '1'
and self.obj2._d == '4'
and self.obj1.a == 4
and self.obj1.b == 5
and self.obj1.c == 6
and self.obj2.f == 'aaa')
if __name__ == "__main__":
unittest.main()
0707010000001B000081B4000000000000000000000001624467C000000A9F000000000000000000000000000000000000001E00000000mgr-push/test/test_archive.py#
# Copyright (c) 2008--2015 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import os
import unittest
import zipfile
# test import
import archive
# globals ----------------------------------------------------------------
TEST_ARCHIVE = "/tmp/test_archive.zip"
TEST_DIR = "./test_archive/foo/bar"
TEST_FILE = "test_file"
TEST_FILE_PATH = os.path.join(TEST_DIR, TEST_FILE)
TEST_CONTENTS = """
ONE: foo
TWO: you
THREE: too
FOUR: foo
"""
# test case --------------------------------------------------------------
class ArchiveTest(unittest.TestCase):
def setUp(self):
if not os.path.isdir(TEST_DIR):
os.makedirs(TEST_DIR)
fd = open(TEST_FILE_PATH, 'w')
fd.write(TEST_CONTENTS)
fd.close()
fzip = zipfile.ZipFile(TEST_ARCHIVE, 'w')
fzip.write(TEST_FILE_PATH)
fzip.close()
def tearDown(self):
if os.path.isfile(TEST_FILE_PATH):
os.unlink(TEST_FILE_PATH)
if os.path.isdir(TEST_DIR):
os.removedirs(TEST_DIR)
if os.path.isfile(TEST_ARCHIVE):
os.unlink(TEST_ARCHIVE)
# test methods -------------------------------------------------------
@staticmethod
def testInstantiation():
"test the instantiation of an archive parser object"
p = archive.get_archive_parser(TEST_ARCHIVE)
assert isinstance(p, archive.ArchiveParser)
@staticmethod
def testFind():
"test the ability of the parser to find a file in the archive"
p = archive.get_archive_parser(TEST_ARCHIVE)
assert p.contains(TEST_FILE)
@staticmethod
def testFindPath():
"test the ability of the parser to find a subpath in the archive"
p = archive.get_archive_parser(TEST_ARCHIVE)
assert p.contains("foo/bar/" + TEST_FILE)
@staticmethod
def testRead():
"test the ability of the parser to read a file in the archive"
p = archive.get_archive_parser(TEST_ARCHIVE)
contents = p.read(TEST_FILE)
assert contents == TEST_CONTENTS
# run the tests ----------------------------------------------------------
if __name__ == "__main__":
unittest.main()
0707010000001C000081B4000000000000000000000001624467C00000687F000000000000000000000000000000000000001600000000mgr-push/uploadLib.py#
# Copyright (c) 2008--2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
# system imports
import os
import sys
import fnmatch
import getpass
# imports
# pylint: disable=F0401,E0611
# exceptions
# pylint: disable=W0702,W0703
import inspect
from uyuni.common import rhn_mpm
from uyuni.common.rhn_pkg import package_from_filename, get_package_header
from uyuni.common.usix import raise_with_tb
from up2date_client import rhnserver
from rhn.i18n import sstr
from rhnpush import rhnpush_cache
if sys.version_info[0] == 3:
import xmlrpc.client as xmlrpclib
else:
import xmlrpclib
try:
from rhn import rpclib # pylint: disable=C0412
Binary = rpclib.xmlrpclib.Binary
Output = rpclib.transports.Output
except ImportError:
# old-style xmlrpclib library
rpclib = xmlrpclib
Binary = rpclib.Binary
# pylint: disable=F0401
import cgiwrap
Output = cgiwrap.Output
# Buffer size we use for copying
BUFFER_SIZE = 65536
HEADERS_PER_CALL = 25
# Exception class
class UploadError(Exception):
pass
class ServerFault(Exception):
def __init__(self, faultCode=None, faultString="", faultExplanation=""):
Exception.__init__(self)
self.faultCode = faultCode
self.faultString = faultString
self.faultExplanation = faultExplanation
class UploadClass:
"""Functionality for an uploading tool
"""
def __init__(self, options, files=None):
self.options = options
self.username = None
self.password = None
self.proxy = None
self.proxyUsername = None
self.proxyPassword = None
self.ca_chain = None
self.force = None
self.files = files or []
self.new_sat = None
self.url = None
self.channels = None
self.count = None
self.server = None
self.session = None
self.orgId = None
self.relativeDir = None
self.use_session = True
self.use_checksum_paths = False
def warn(self, verbose, *args):
if self.options.verbose >= verbose:
ReportError(*args)
@staticmethod
def die(errcode, *args):
ReportError(*args)
# pkilambi:bug#176358:this should exit with error code
sys.exit(errcode)
def setURL(self):
# Redefine this in derived classes
self.url = None
def setUsernamePassword(self):
# Use the stored values, if available
username = self.username or self.options.username
password = self.password or self.options.password
self.username, self.password = getUsernamePassword(username, password)
def setProxyUsernamePassword(self):
self.proxyUsername = None
self.proxyPassword = None
def setCAchain(self):
self.ca_chain = self.options.ca_chain
def setProxy(self):
if self.options.proxy is None or self.options.proxy == '':
self.proxy = None
else:
self.proxy = "http://%s" % self.options.proxy
def setForce(self):
self.force = None
def setServer(self):
# set the proxy
self.setProxy()
if self.proxy is None:
self.warn(1, "Connecting to %s" % self.url)
else:
self.warn(1, "Connecting to %s (via proxy '%s')" % (self.url, self.proxy))
# set the CA chain
self.setCAchain()
# set the proxy username and password
self.setProxyUsernamePassword()
self.server = getServer(self.url, self.proxy, self.proxyUsername,
self.proxyPassword, self.ca_chain)
# Compress the output, just to be fast
self.server.set_transport_flags(
transfer=Output.TRANSFER_BINARY,
encoding=Output.ENCODE_GZIP)
def setChannels(self):
if not self.options.channel:
self.die(-1, "No channel was specified")
self.channels = self.options.channel
self.warn(1, "Channels: %s" % ' '.join(self.channels))
setNoChannels = setChannels
def setOrg(self):
self.orgId = -1
def setCount(self):
if not self.options.count:
self.count = HEADERS_PER_CALL
else:
self.count = self.options.count
def setRelativeDir(self):
self.relativeDir = None
def directory(self):
self.warn(2, "Uploading files from directory", self.options.dir)
for filename in listdir(self.options.dir):
# only add packages
if filename[-3:] in ("rpm", "mpm"):
self.files.append(filename)
def filter_excludes(self):
if not self.options.exclude:
return self
for f in self.files[:]:
bf = os.path.basename(f)
for pattern in self.options.exclude:
if fnmatch.fnmatch(bf, pattern):
self.warn(1, "Ignoring %s" % f)
self.files.remove(f)
return self
def readStdin(self):
self.warn(1, "Reading package names from stdin")
self.files = self.files + readStdin()
def _listChannelSource(self):
if self.use_session:
return listChannelSourceBySession(self.server,
self.session.getSessionString(),
self.channels)
return listChannelSource(self.server,
self.username, self.password,
self.channels)
def _listChannel(self):
if self.use_session:
if self.use_checksum_paths:
return listChannelChecksumBySession(self.server,
self.session.getSessionString(), self.channels)
return listChannelBySession(self.server,
self.session.getSessionString(),
self.channels)
if self.use_checksum_paths:
return listChannelChecksum(self.server,
self.username, self.password,
self.channels)
return listChannel(self.server,
self.username, self.password,
self.channels)
def list(self):
# set the URL
self.setURL()
# set the channels
self.setChannels()
# set the server
self.setServer()
self.authenticate()
if self.options.source:
channel_list = self._listChannelSource()
else:
# List the channel's contents
channel_list = self._listChannel()
for p in channel_list:
print(p[:6])
def newest(self):
# set the URL
self.setURL()
# set the channels
self.setChannels()
# set the server
self.setServer()
self.authenticate()
sources = self.options.source
if sources:
return self.get_missing_source_packages()
return self.get_newest_binary_packages()
def get_newest_binary_packages(self):
# Loop through the args and only keep the newest ones
localPackagesHash = {}
for filename in self.files:
nvrea = self._processFile(filename, nosig=1)['nvrea']
name = nvrea[0]
if name not in localPackagesHash:
localPackagesHash[name] = {nvrea: filename}
continue
same_names_hash = localPackagesHash[name]
# Already saw this name
if nvrea in same_names_hash:
# Already seen this nvrea
continue
skip_rpm = 0
for local_nvrea in same_names_hash.keys():
# XXX is_mpm should be set accordingly
ret = packageCompare(local_nvrea, nvrea,
is_mpm=0)
if ret == 0 and local_nvrea[4] == nvrea[4]:
# Weird case, we've already compared the two
skip_rpm = 1
break
if ret > 0:
# nvrea is older than local_nvrea
skip_rpm = 1
break
if ret < 0:
# nvrea is newer than local_nvrea
del same_names_hash[local_nvrea]
# Different arches - go on
if skip_rpm:
# Older
continue
same_names_hash[nvrea] = filename
# Now get the list from the server
pkglist = self._listChannel()
for p in pkglist:
name = p[0]
if name not in localPackagesHash:
# Not in the local list
continue
same_names_hash = localPackagesHash[name]
remote_nvrea = tuple(p[:5])
if remote_nvrea in same_names_hash:
# The same package is already uploaded
del same_names_hash[remote_nvrea]
continue
for local_nvrea in list(same_names_hash.keys()):
# XXX is_mpm sould be set accordingly
ret = packageCompare(local_nvrea, remote_nvrea,
is_mpm=0)
if ret < 0:
# The remote package is newer than the local one
del same_names_hash[local_nvrea]
continue
if ret == 0 and local_nvrea[4] == remote_nvrea[4]:
# Same arch
del same_names_hash[local_nvrea]
continue
# This means local is newer
# Return the list of files to push
l = []
for fhash in localPackagesHash.values():
for filename in fhash.values():
l.append(filename)
l.sort()
self.files = l
def _listMissingSourcePackages(self):
if self.use_session:
return listMissingSourcePackagesBySession(self.server,
self.session.getSessionString(), self.channels)
return listMissingSourcePackages(self.server,
self.username, self.password, self.channels)
def get_missing_source_packages(self):
localPackagesHash = {}
for filename in self.files:
localPackagesHash[os.path.basename(filename)] = filename
# Now get the list from the server
pkglist = self._listMissingSourcePackages()
to_push = []
for pkg in pkglist:
pkg_name, _pkg_channel = pkg[:2]
if pkg_name not in localPackagesHash:
# We don't have it
continue
to_push.append(localPackagesHash[pkg_name])
to_push.sort()
self.files = to_push
return self.files
def test(self):
# Test only
for p in self.files:
print(p)
def _get_files(self):
return self.files[:]
def _uploadSourcePackageInfo(self, info):
if self.use_session:
return call(self.server.packages.uploadSourcePackageInfoBySession,
self.session.getSessionString(), info)
return call(self.server.packages.uploadSourcePackageInfo,
self.username, self.password, info)
def _uploadPackageInfo(self, info):
if self.use_session:
return call(self.server.packages.uploadPackageInfoBySession,
self.session.getSessionString(), info)
return call(self.server.packages.uploadPackageInfo,
self.username, self.password, info)
def uploadHeaders(self):
# Set the forcing factor
self.setForce()
# Relative directory
self.setRelativeDir()
# Set the count
self.setCount()
# set the org
self.setOrg()
# set the URL
self.setURL()
# set the channels
self.setNoChannels()
# set the server
self.setServer()
self.authenticate()
source = self.options.source
file_list = self._get_files()
while file_list:
chunk = file_list[:self.count]
del file_list[:self.count]
uploadedPackages, headersList = self._processBatch(chunk,
relativeDir=self.relativeDir, source=self.options.source,
verbose=self.options.verbose, nosig=self.options.nosig)
if not headersList:
# Nothing to do here...
continue
# Send the big hash
info = {'packages': headersList}
if self.orgId > 0 or self.orgId == '':
info['orgId'] = self.orgId
if self.force:
info['force'] = self.force
if self.channels:
info['channels'] = self.channels
# Some feedback
if self.options.verbose:
ReportError("Uploading batch:")
for p in list(uploadedPackages.values())[0]:
ReportError("\t\t%s" % p)
if source:
ret = self._uploadSourcePackageInfo(info)
else:
ret = self._uploadPackageInfo(info)
if ret is None:
self.die(-1, "Upload attempt failed")
# Append the package information
alreadyUploaded, newPackages = ret
pkglists = (alreadyUploaded, newPackages)
for idx, item in enumerate(pkglists):
for p in item:
key = tuple(p[:5])
if key not in uploadedPackages:
# XXX Hmm
self.warn(1, "XXX XXX %s" % str(p))
filename, checksum = uploadedPackages[key]
# Some debugging
if self.options.verbose:
if idx == 0:
pattern = "Already uploaded: %s"
else:
pattern = "Uploaded: %s"
print(pattern % filename)
# Per-package post actions
# For backwards-compatibility with old spacewalk-proxy
try:
self.processPackage(p, filename, checksum)
except TypeError:
self.processPackage(p, filename)
def processPackage(self, package, filename, checksum=None):
pass
def checkSession(self, session):
return call(self.server.packages.check_session, session)
def readSession(self):
# pylint: disable=W0703
try:
self.session = rhnpush_cache.RHNPushSession()
self.session.readSession()
except Exception:
self.session = None
def writeSession(self, session):
if self.session:
self.session.setSessionString(session)
else:
self.session = rhnpush_cache.RHNPushSession()
self.session.setSessionString(session)
if not self.options.no_session_caching:
self.session.writeSession()
def authenticate(self):
# Only use the session token stuff if we're talking to a sat that supports session-token authentication.
self.readSession()
if self.session and not self.options.new_cache and self.options.username == self.username:
chksession = self.checkSession(self.session.getSessionString())
if chksession:
return
self.setUsernamePassword()
sessstr = call(self.server.packages.login, self.username, self.password)
self.writeSession(sessstr)
# set whether we should use checksum paths or not (if upstream supports
# it we should).
self.use_checksum_paths = hasChannelChecksumCapability(self.server)
@staticmethod
def _processFile(filename, relativeDir=None, source=None, nosig=None):
""" Processes a file
Returns a hash containing:
header
packageSize
checksum
relativePath
nvrea
"""
# Is this a file?
if not os.access(filename, os.R_OK):
raise UploadError("Could not stat the file %s" % filename)
if not os.path.isfile(filename):
raise UploadError("%s is not a file" % filename)
# Size
size = os.path.getsize(filename)
try:
a_pkg = package_from_filename(filename)
a_pkg.read_header()
a_pkg.payload_checksum()
assert a_pkg.header
except:
raise_with_tb(UploadError("%s is not a valid package" % filename), sys.exc_info()[2])
if nosig is None and not a_pkg.header.is_signed():
raise UploadError("ERROR: %s: unsigned rpm (use --nosig to force)"
% filename)
# Get the name, version, release, epoch, arch
lh = []
for k in ['name', 'version', 'release', 'epoch']:
if k == 'epoch' and not a_pkg.header[k]:
# Fix the epoch
lh.append(sstr(""))
else:
lh.append(sstr(a_pkg.header[k]))
if source:
lh.append('src')
else:
lh.append(sstr(a_pkg.header['arch']))
# Build the header hash to be sent
info = {'header': Binary(a_pkg.header.unload()),
'checksum_type': a_pkg.checksum_type,
'checksum': a_pkg.checksum,
'packageSize': size,
'header_start': a_pkg.header_start,
'header_end': a_pkg.header_end}
if relativeDir:
# Append the relative dir too
info["relativePath"] = "%s/%s" % (relativeDir,
os.path.basename(filename))
info['nvrea'] = tuple(lh)
return info
def _processBatch(self, batch, relativeDir, source, verbose, nosig=None):
sentPackages = {}
headersList = []
for filename in batch:
if verbose:
print("Uploading %s" % filename)
info = self._processFile(filename, relativeDir=relativeDir, source=source,
nosig=nosig)
# Get nvrea
nvrea = info['nvrea']
del info['nvrea']
sentPackages[nvrea] = (filename, info['checksum'])
# Append the header to the list of headers to be sent out
headersList.append(info)
return sentPackages, headersList
def readStdin():
# Reads the standard input lines and returns a list
l = []
while 1:
line = sys.stdin.readline()
if not line:
break
l.append(line.strip())
return l
def getUsernamePassword(cmdlineUsername, cmdlinePassword):
# Returns a username and password (either by returning the ones passed as
# args, or the user's input
if cmdlineUsername and cmdlinePassword:
return cmdlineUsername, cmdlinePassword
username = cmdlineUsername
password = cmdlinePassword
# Read the username, if not already specified
tty = open("/dev/tty", "w")
tty.write("SUSE Manager username: ")
tty.close()
tty = open("/dev/tty", "r")
while not username:
try:
username = tty.readline()
except KeyboardInterrupt:
tty.write("\n")
sys.exit(0)
if username is None:
# EOF
tty.write("\n")
sys.exit(0)
username = username.strip()
if username:
break
# Now read the password
while not password:
try:
password = getpass.getpass("SUSE Manager password: ")
except KeyboardInterrupt:
tty.write("\n")
sys.exit(0)
tty.close()
return username, password
def listdir(directory):
directory = os.path.abspath(os.path.normpath(directory))
if not os.access(directory, os.R_OK | os.X_OK):
raise UploadError("Cannot read from directory %s" % directory)
if not os.path.isdir(directory):
raise UploadError("%s not a directory" % directory)
# Build the package list
packagesList = []
for f in os.listdir(directory):
packagesList.append("%s/%s" % (directory, f))
return packagesList
def call(function, *params, **kwargs):
# Wrapper function
try:
ret = function(*params)
except xmlrpclib.Fault:
e = sys.exc_info()[1]
x = parseXMLRPCfault(e)
if x.faultString:
print(x.faultString)
if x.faultExplanation:
print(x.faultExplanation)
sys.exit(-1)
except xmlrpclib.ProtocolError:
e = sys.exc_info()[1]
if kwargs.get('raise_protocol_error'):
raise
print(e.errmsg)
sys.exit(-1)
return ret
def parseXMLRPCfault(fault):
if not isinstance(fault, xmlrpclib.Fault):
return None
faultCode = fault.faultCode
if faultCode and isinstance(faultCode, type(1)):
faultCode = -faultCode
return ServerFault(faultCode, "", fault.faultString)
# pylint: disable=C0103
def listChannel(server, username, password, channels):
return call(server.packages.listChannel, channels, username, password)
def listChannelChecksum(server, username, password, channels):
return call(server.packages.listChannelChecksum, channels, username,
password)
def listChannelBySession(server, session_string, channels):
return call(server.packages.listChannelBySession, channels, session_string)
def listChannelChecksumBySession(server, session_string, channels):
return call(server.packages.listChannelChecksumBySession, channels,
session_string)
def listChannelSource(server, username, password, channels):
return call(server.packages.listChannelSource, channels, username, password)
def listChannelSourceBySession(server, session_string, channels):
return call(server.packages.listChannelSourceBySession, channels, session_string)
def listMissingSourcePackages(server, username, password, channels):
return call(server.packages.listMissingSourcePackages, channels, username, password)
def listMissingSourcePackagesBySession(server, session_string, channels):
return call(server.packages.listMissingSourcePackagesBySession, channels, session_string)
def getPackageChecksumBySession(server, session_string, info):
return call(server.packages.getPackageChecksumBySession, session_string, info)
def getSourcePackageChecksumBySession(server, session_string, info):
return call(server.packages.getSourcePackageChecksumBySession, session_string, info)
def getSourcePackageChecksum(server, username, password, info):
return call(server.packages.getSourcePackageChecksum, username, password, info)
# for backward compatibility with satellite <5.4.0
def getPackageMD5sumBySession(server, session_string, info):
return call(server.packages.getPackageMD5sumBySession, session_string, info)
def getSourcePackageMD5sumBySession(server, session_string, info):
return call(server.packages.getSourcePackageMD5sumBySession, session_string, info)
def getServer(uri, proxy=None, username=None, password=None, ca_chain=None):
s = rpclib.Server(uri, proxy=proxy, username=username, password=password)
if ca_chain:
s.add_trusted_cert(ca_chain)
return s
# pylint: disable=E1123
def hasChannelChecksumCapability(rpc_server):
""" check whether server supports getPackageChecksumBySession function"""
# pylint: disable=W1505
if 'rpcServerOverride' in inspect.getargspec(rhnserver.RhnServer.__init__)[0]:
server = rhnserver.RhnServer(rpcServerOverride=rpc_server)
else:
server = rhnserver.RhnServer()
# pylint: disable=W0212
server._server = rpc_server
return server.capabilities.hasCapability('xmlrpc.packages.checksums')
def exists_getPackageChecksumBySession(rpc_server):
""" check whether server supports getPackageChecksumBySession function"""
# unfortunatelly we do not have capability for getPackageChecksumBySession function,
# but extended_profile in version 2 has been created just 2 months before
# getPackageChecksumBySession lets use it instead
# pylint: disable=W1505
if 'rpcServerOverride' in inspect.getargspec(rhnserver.RhnServer.__init__)[0]:
server = rhnserver.RhnServer(rpcServerOverride=rpc_server)
else:
server = rhnserver.RhnServer()
# pylint: disable=W0212
server._server = rpc_server
return server.capabilities.hasCapability('xmlrpc.packages.extended_profile', 2)
# compare two package [n,v,r,e] tuples
def packageCompare(pkg1, pkg2, is_mpm=None):
if pkg1[0] != pkg2[0]:
raise ValueError("You should only compare packages with the same name")
packages = []
for pkg in (pkg1, pkg2):
e = pkg[3]
if e == "":
e = None
elif e is not None:
e = str(e)
evr = (e, str(pkg[1]), str(pkg[2]))
packages.append(evr)
if is_mpm:
func = rhn_mpm.labelCompare
else:
from uyuni.common import rhn_rpm
func = rhn_rpm.labelCompare
return func(packages[0], packages[1])
# returns a header from a package file on disk.
def get_header(filename, fildes=None, source=None):
try:
h = get_package_header(filename=filename, fd=fildes)
except:
raise_with_tb(UploadError("Package is invalid"), sys.exc_info()[2])
# Verify that this is indeed a binary/source. xor magic
# xor doesn't work with None values, so compare the negated values - the
# results are identical
if (not source) ^ (not h.is_source):
raise UploadError("Unexpected RPM package type")
return h
def ReportError(*args):
sys.stderr.write(' '.join(map(str, args)) + "\n")
0707010000001D000081B4000000000000000000000001624467C000000B4C000000000000000000000000000000000000001200000000mgr-push/utils.py#
# Copyright (c) 2008--2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#
import os
import pwd
import sys
def get_home_dir():
userid = os.getuid()
info = pwd.getpwuid(userid)
return info[5]
# If Object1 and Object2 have any common attributes, set the attribute in Object1
# to the value of the attribute in Object2. Does not make functions or variables starting with '_' equivalent.
def make_common_attr_equal(object1, object2):
# Go through every attribute in object1
for attr in object1.__dict__.keys():
# Make sure that the attribute name doesn't begin with "_"
if len(attr) < 1 or attr[0] == "_":
continue
# Make sure that object2 has the attribute as well. and that it's not equal to ''.
if attr not in object2.__dict__ or object2.__dict__[attr] == '':
continue
# Make sure the attributes are the same type OR that the attribute in object1 is None.
if isinstance(object1.__dict__[attr], (type(object2.__dict__[attr]), type(None))):
if object1.__dict__[attr] != object2.__dict__[attr]:
object1.__dict__[attr] = object2.__dict__[attr]
else:
continue
else:
continue
return (object1, object2)
# Pylint is too stupid to understand subclasses of tuples apparently.
# This is just to make it shut up.
def tupleify_urlparse(urlparse_object):
ret = []
if hasattr(urlparse_object, 'scheme'):
ret.append(urlparse_object.scheme)
ret.append(urlparse_object.netloc)
ret.append(urlparse_object.path)
ret.append(urlparse_object.params)
ret.append(urlparse_object.query)
ret.append(urlparse_object.fragment)
else:
ret = [urlparse_object[i] for i in range(0, 6)]
if sys.version_info[0] == 3:
for i in range(0, 6):
if not isinstance(ret[i], str):
ret[i] = ret[i].decode('ascii')
return tuple(ret)
if __name__ == "__main__":
# This is just for testing purposes.
# pylint: disable=R0903
class class1:
def __init__(self):
self.a = "aaaa"
class class2:
def __init__(self):
self.a = 1
obj1 = class1()
obj2 = class2()
obj1, obj2 = make_common_attr_equal(obj1, obj2)
print(obj1.a)
print(obj2.a)
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!