Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP2:GA
mgr-cfg.11260
mgr-cfg-git-0.1d575b5.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File mgr-cfg-git-0.1d575b5.obscpio of Package mgr-cfg.11260
07070100000000000041FD0000000000000000000000015CDC5C8900000000000000000000000000000000000000000000000800000000mgr-cfg07070100000001000081B40000000000000000000000015CDC5C89000046AC000000000000000000000000000000000000001000000000mgr-cfg/LICENSE 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. 07070100000002000081B40000000000000000000000015CDC5C8900000022000000000000000000000000000000000000001100000000mgr-cfg/Makefileinclude ../../../rel-eng/Makefile 07070100000003000081B40000000000000000000000015CDC5C8900000943000000000000000000000000000000000000001600000000mgr-cfg/Makefile.defs# Common pathnames and programs for the Spacewalk project # # if not defined, definit as a noop TOP ?= . # global defines which control this build and where we deploy files ROOT ?= /usr/share/rhn export ROOT PREFIX ?= export PREFIX BINDIR ?= /usr/bin CONFDIR ?= /etc/sysconfig/rhn # Compilation stuff CC = gcc PYTHON_INCLUDE = -I/usr/include/python$(PythonVersion) CFLAGS = -Wall -O2 -fomit-frame-pointer $(PYTHON_INCLUDE) -fPIC SOFLAGS = -shared -fPIC # Installation stuff INSTALL ?= /usr/bin/install -c --verbose INSTALL_BIN ?= $(INSTALL) -m 755 INSTALL_DATA ?= $(INSTALL) -m 644 INSTALL_DIR ?= $(INSTALL) -m 755 -d # This is for the subdir part PYFILES = $(addsuffix .py,$(FILES)) # what do we need to install and where INSTALL_FILES += $(PYFILES) $(PYCFILES) INSTALL_DEST ?= $(ROOT)/$(SUBDIR) DIRS += $(addprefix $(PREFIX), \ $(sort $(EXTRA_DIRS)) $(INSTALL_DEST)) LN ?= ln -sfv all :: $(INSTALL_FILES) install :: all $(DIRS) $(INSTALL_FILES) @$(foreach f,$(INSTALL_FILES), \ $(INSTALL_DATA) $(f) $(PREFIX)$(INSTALL_DEST)/$(f) ; ) $(DIRS): $(INSTALL_DIR) $@ clean :: @rm -f *~ *.pyc *.pyo .??*~ @rm -f .\#* @rm -f core %.8 : %.sgml [ -f /usr/bin/docbook2man ] && /usr/bin/docbook2man $< || true # useful macro descend-subdirs = @$(foreach d,$(SUBDIRS), $(MAKE) -C $(d) $@ || exit 1; ) # subdirs are treated at the end all install clean:: $(SUBDIRS) $(descend-subdirs) # extra toy targets # Python checker support PYTHONPATH = $(TOP) PYTHONVERSION ?= 2 PYCHECKER = pychecker PYCHECKEROPTS = --maxreturns 10 --maxbranches 15 pychecker :: $(PYFILES) @PYTHONPATH=$(PYTHONPATH) $(PYCHECKER) $(PYCHECKEROPTS) $(PYFILES) || : $(descend-subdirs) graphviz :: @PYTHONPATH=$(PYTHONPATH) $(PYCHECKER) -Z $(PYCHECKEROPTS) $(PYFILES) || exit 0 make_rel = $(shell a="$1"; b=; while [ "$$a" != '/' ]; do \ a=$$(dirname $$a); \ if [ -z "$$b" ]; then b=".."; else b="$$b/.."; fi; \ done; echo $$b) install-bin: $(PREFIX)$(BINDIR) $(addsuffix .py,$(EXEC_FILES)) @$(foreach f,$(EXEC_FILES), \ chmod 755 $(PREFIX)$(INSTALL_DEST)/$(f).py; \ $(LN) $(call make_rel,$(BINDIR))$(INSTALL_DEST)/$(f).py $(PREFIX)$(BINDIR)/$(f)-$(PYTHONVERSION); ) install-conf: $(PREFIX)$(CONFDIR) $(CONFIG_FILES) @$(foreach f,$(CONFIG_FILES), \ $(INSTALL_DATA) $(f) $(PREFIX)$(CONFDIR)/$(f) ; ) 07070100000004000081B40000000000000000000000015CDC5C8900000343000000000000000000000000000000000000001800000000mgr-cfg/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 = uyuni-master DOCKER_REGISTRY = registry.mgr.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-cfg; 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 07070100000005000081B40000000000000000000000015CDC5C89000000C0000000000000000000000000000000000000001800000000mgr-cfg/Makefile.rhncfg# Makefile for spacewalk backend # # what is the backend top dir TOP = . # Specific stuff SUBDIRS = actions config_common config_client config_management include $(TOP)/Makefile.defs 07070100000006000041FD0000000000000000000000015CDC5C8900000000000000000000000000000000000000000000001000000000mgr-cfg/actions07070100000007000081B40000000000000000000000015CDC5C890000042E000000000000000000000000000000000000001900000000mgr-cfg/actions/Makefile# Makefile for spacewalk backend # # what is the backend top dir TOP = .. # Specific stuff SUBDIR = rhn/actions EXEC_FILES = rhn-actions-control FILES = $(EXEC_FILES) configfiles script \ Modes ModeController ModeControllerCreator CAPS = configfiles script CAPS_DIR = /etc/sysconfig/rhn/clientCaps.d #man page stuff DOCBOOK = $(wildcard /usr/bin/docbook2man) SGMLS = $(wildcard *.sgml) MANS = $(patsubst %.sgml,%.8,$(SGMLS)) MANDIR ?= /usr/man EXTRA_DIRS = $(MANDIR)/man8 $(BINDIR) $(CAPS_DIR) include $(TOP)/Makefile.defs install :: install-bin install-caps install-caps :: $(CAPS) $(PREFIX)$(CAPS_DIR) $(INSTALL_DATA) $(CAPS) $(PREFIX)$(CAPS_DIR) ifneq ($(DOCBOOK),) # install man pages all :: $(MANS) install :: $(MANS) $(PREFIX)$(MANDIR) $(INSTALL_DATA) $(MANS) $(PREFIX)$(MANDIR)/man8 endif clean :: @rm -fv $(MANS) manpage.* 07070100000008000081B40000000000000000000000015CDC5C8900001496000000000000000000000000000000000000002200000000mgr-cfg/actions/ModeController.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 try: PY3 = sys.version_info.major >= 3 raw_input = input except AttributeError: PY3 = False class ModeMissingException(Exception): pass #Handles operations on a group of Modes. class ModeController: def __init__(self, force=None): self.mode_list = {} #Indexed on the name of the mode self.force = False #Enable the mode. def on(self, mode_name): if mode_name in self.mode_list: self.mode_list[mode_name].on() else: raise ModeMissingException() #Disable the mode def off(self, mode_name): if mode_name in self.mode_list: self.mode_list[mode_name].off() else: raise ModeMissingException() #Turn on all of the modes. def all_on(self): for m in self.mode_list.keys(): self.mode_list[m].on() if self.mode_list['all'].is_on(): self.mode_list['all'].off() #Turn off all of the modes. def all_off(self): for m in self.mode_list.keys(): self.mode_list[m].off() #Check to see if the mode is on. def is_on(self, mode_name): if mode_name in self.mode_list: return self.mode_list[mode_name].is_on() else: return 0 #Check to see if the mode is off. def is_off(self, mode_name): if mode_name in self.mode_list: return self.mode_list[mode_name].is_off() else: return 0 #Add another mode to the batch. def add_mode(self, mode_obj): mode_name = mode_obj.get_name() if not mode_name in self.mode_list: self.mode_list[mode_name] = mode_obj #Remove a mode from the batch. def del_mode(self, mode_obj): mode_name = mode_obj.get_name() if mode_name in self.mode_list: del self.mode_list[mode_name] #set the value of force def set_force(self, force): self.force = force class ConfigFilesModeController(ModeController): def __init__(self): ModeController.__init__(self) def is_on(self, mode_name): if ModeController.is_on(self, 'all'): return 1 else: return ModeController.is_on(self, mode_name) def is_off(self, mode_name): if ModeController.is_off(self, 'all'): return 1 else: return ModeController.is_off(self, mode_name) #the possible presence of the 'all' file confuses things a little. #If the user enables something while the 'all' file is present, nothing should be added to the configfiles dir. def on(self, mode_name): if mode_name != 'all': if self.is_off('all'): ModeController.on(self, mode_name) #Go through each of the modes and see if they're on. If they're all on, then place the 'all' file in there. all_modes_on = 1 for m in self.mode_list.keys(): if m != 'all' and self.mode_list[m].is_off(): all_modes_on = 0 if all_modes_on: self.all_on() else: self.all_on() #If the 'all' file is present and the user disables a mode, then the all file must be removed, and all modes other than #the specified mode need to be turned on. def off(self, mode_name): if mode_name != 'all': if self.is_on('all'): if not self.force: ask_before_continuing("All modes are currently enabled. Continue? (y or n):") #Turn off all modes self.all_off() #Manually flip on all of the modes except 'all'. for m in self.mode_list.keys(): if m != 'all': self.mode_list[m].on() #Turn off the specified mode. Calls off() at the Mode level, not at the Controller level. self.mode_list[mode_name].off() else: self.all_off() #This is a little different when the 'all' file is used. #There shouldn't be any other files in the directory when 'all' is used. def all_on(self): #Get rid of all of the files. self.all_off() #Turn on the 'all' mode. self.mode_list['all'].on() def ask_before_continuing(question=None): if question is None: the_question = "Continue? (y or n):" else: the_question = question answer = '-1' while answer != 'y' and answer != 'n': answer = raw_input(the_question) answer = answer.lower()[0] if answer == 'n': sys.exit(0) 07070100000009000081B40000000000000000000000015CDC5C89000009E7000000000000000000000000000000000000002900000000mgr-cfg/actions/ModeControllerCreator.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 ModeController import Modes class ModeControllerCreator: #if mode_list isn't set in the constructor, the populate_list is going to have to be called before create_controller. def __init__(self, mode_list=None): self.mode_list = mode_list or [] #A reference to a class obj. This is the type of controller that will be returned by create_controller. self.controller_class = ModeController.ModeController #Sets the class that the controller will be instantiated as. The constructor for the class shouldn't have #to take any parameters. def set_controller_class(self, controller_class): self.controller_class = controller_class #Populates self.mode_list with concrete implementations of Modes. def populate_list(self, mode_list): self.mode_list = mode_list #using the Modes in self.mode_list, create, populate, and return a ModeController def create_controller(self): controller = self.controller_class() for m in self.mode_list: controller.add_mode(m) return controller def get_controller_creator(): if sys.platform.find('sunos') > -1: mode_list = [Modes.SolarisDeployMode(), Modes.SolarisDiffMode(), Modes.SolarisUploadMode(), Modes.SolarisMTimeUploadMode(), Modes.SolarisAllMode()] else: mode_list = [Modes.DeployMode(), Modes.DiffMode(), Modes.UploadMode(), Modes.MTimeUploadMode(), Modes.AllMode()] controller = ModeControllerCreator(mode_list=mode_list) controller.set_controller_class(ModeController.ConfigFilesModeController) return controller def get_run_controller_creator(): if sys.platform.find('sunos') > -1: mode_list = [Modes.SolarisRunMode(), Modes.SolarisRunAllMode()] else: mode_list = [Modes.RunMode(), Modes.RunAllMode()] controller = ModeControllerCreator(mode_list=mode_list) return controller 0707010000000A000081B40000000000000000000000015CDC5C89000017BA000000000000000000000000000000000000001900000000mgr-cfg/actions/Modes.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 os #Base class for all modes. class BaseMode: def __init__(self): self.state = False self.name = "" def on(self): self.state = True def off(self): self.state = False def is_on(self): if self.state: return True else: return False def is_off(self): if not self.state: return True else: return False def set_name(self, name): self.name = name def get_name(self): return self.name #Contains the directory and file manipulation stuff class PathHandler: def __init__(self): self.rhn_root = "/etc/sysconfig/rhn/allowed-actions/configfiles" #Set the rhn_root variable. def set_rhn_root(self, rhn_root): self.rhn_root = rhn_root #Creates the self.rhn_root directories if they don't already exist. This allows subclasses to implement modes in different locations. def _create_rhnconfig_path(self): if not os.path.exists(self.rhn_root): os.makedirs(self.rhn_root, int('0770', 8)) #Create the file if it doesn't already exist. def add_file(self, filename): self._create_rhnconfig_path() if not self.check_for_file(filename): try: f = open(os.path.join(self.rhn_root, filename), "w") f.close() except Exception: raise #remove the file if it's present. def remove_file(self, filename): self._create_rhnconfig_path() if self.check_for_file(filename): try: os.remove(os.path.join(self.rhn_root, filename)) except Exception: raise #Returns True if filename exists in /etc/sysconfig/rhn/allowed-actions/configfiles def check_for_file(self, filename): self._create_rhnconfig_path() return os.path.exists(os.path.join(self.rhn_root, filename)) #Stuff that's common to the Mode subclasses. class ConfigFilesBaseMode(BaseMode): def __init__(self): BaseMode.__init__(self) self.ph = PathHandler() self.name = None #Must be set in subclass def on(self): self.ph.add_file(self.name) self.state = True def off(self): self.ph.remove_file(self.name) self.state = False #Could probably just check the value of state... def is_on(self): return self.ph.check_for_file(self.name) def is_off(self): if self.ph.check_for_file(self.name): return False elif not self.ph.check_for_file(self.name): return True class RunMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "run" self.ph.set_rhn_root("/etc/sysconfig/rhn/allowed-actions/script") class RunAllMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "all" self.ph.set_rhn_root("/etc/sysconfig/rhn/allowed-actions/script") class AllMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "all" class DeployMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "deploy" class DiffMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "diff" class UploadMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "upload" class MTimeUploadMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "mtime_upload" #Solaris Specific Modes class SolarisRunMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "run" self.ph.set_rhn_root("/opt/redhat/rhn/solaris/etc/sysconfig/rhn/allowed-actions/script") class SolarisAllRunMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "all" self.ph.set_rhn_root("/opt/redhat/rhn/solaris/etc/sysconfig/rhn/allowed-actions/script") class SolarisAllMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "all" self.ph.set_rhn_root("/opt/redhat/rhn/solaris/etc/sysconfig/rhn/allowed-actions/configfiles") class SolarisDeployMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "deploy" self.ph.set_rhn_root("/opt/redhat/rhn/solaris/etc/sysconfig/rhn/allowed-actions/configfiles") class SolarisDiffMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "diff" self.ph.set_rhn_root("/opt/redhat/rhn/solaris/etc/sysconfig/rhn/allowed-actions/configfiles") class SolarisUploadMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "upload" self.ph.set_rhn_root("/opt/redhat/rhn/solaris/etc/sysconfig/rhn/allowed-actions/configfiles") class SolarisMTimeUploadMode(ConfigFilesBaseMode): def __init__(self): ConfigFilesBaseMode.__init__(self) self.name = "mtime_upload" self.ph.set_rhn_root("/opt/redhat/rhn/solaris/etc/sysconfig/rhn/allowed-actions/configfiles") 0707010000000B000081B40000000000000000000000015CDC5C8900000266000000000000000000000000000000000000001C00000000mgr-cfg/actions/__init__.py# # Copyright (c) 2008--2013 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. # 0707010000000C000081B40000000000000000000000015CDC5C8900000081000000000000000000000000000000000000001C00000000mgr-cfg/actions/configfilesconfigfiles.mtime_upload(1)=1 configfiles.upload(1)=1 configfiles.deploy(1)=1 configfiles.diff(1)=1 configfiles.base64_enc(1)=1 0707010000000D000081FD0000000000000000000000015CDC5C89000047C5000000000000000000000000000000000000001F00000000mgr-cfg/actions/configfiles.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 os import time import sys from config_common import local_config, file_utils, utils, cfg_exceptions from config_common.rhn_log import set_debug_level, get_debug_level, set_logfile, log_to_file from config_common.transactions import DeployTransaction, FailedRollback from config_client import rpc_cli_repository from up2date_client import config from spacewalk.common.usix import StringType, UnicodeType # this is a list of the methods that get exported by a module __rhnexport__ = [ 'mtime_upload', 'upload', 'deploy', 'verify', 'diff', ] # action version we understand ACTION_VERSION = 2 # when we do this within up2date, should just have something like # __rhn_require_local_permission__ that flags the methods requiring the touched # file _permission_root_dir = '/etc/sysconfig/rhn/allowed-actions' def _local_permission_check(action_type): # action_type ala configfiles.deploy atype_structure = action_type.split('.') for i in range(len(atype_structure)): all_structure = atype_structure[:i] all_structure.append('all') if os.path.exists(os.path.join(_permission_root_dir, "script/all")) and action_type == "script.run": os.rename(os.path.join(_permission_root_dir, "script/all"),os.path.join(_permission_root_dir, "script/run")) potential_all_path = os.path.join(*all_structure) if os.path.exists(os.path.join(_permission_root_dir, potential_all_path)): return 1 action_path = os.path.join(*atype_structure) return os.path.exists(os.path.join(_permission_root_dir, action_path)) def _perm_error(action_type): return (42, "Local permission not set for action type %s" % action_type, {}) def _visit_dir(params, dirname, names): matches = params['matches'] info = params['info'] ignore_dirs = params['ignore'] now = params['now'] i = 0 while i < len(names): full_path = os.path.join(dirname, names[i]) is_dir = os.path.isdir(full_path) if is_dir: if full_path in ignore_dirs: # don't consider the entire subtree on subsequent runs of # visit del names[i] else: i = i + 1 # since we can have multiple search paths hitting the same subdir, # filter 'em out after the first pass ignore_dirs[full_path] = None continue if not os.path.exists(full_path): i = i + 1 continue mtime = os.path.getmtime(full_path) # do it via delta... if (now - mtime) <= (info['now'] - info['start_date']): if info['end_date']: if (now - mtime) >= (info['now'] - info['end_date']): matches.append(full_path) else: matches.append(full_path) i = i + 1 def format_result(result, files): files_too_large = result.get('files_too_large') or [] quota_failed = result.get('failed_due_to_quota') or [] missing_files = result.get('missing_files') or [] extras = { 'attempted_paths' : files } if missing_files: extras['missing_files'] = missing_files if files_too_large: extras['files_too_large'] = files_too_large if quota_failed: extras['quota_failed'] = quota_failed num_files = len(files) num_uploaded = num_files - (len(missing_files) + len(files_too_large) + len(quota_failed)) if num_uploaded == num_files: return 0, "All files successfully uploaded", extras else: return -1, "Some files failed to upload", extras ## foo = {'ignore': ['\n', '/home/bretm/rhn/build/', '/home/bretm/rhn/sql'], ## 'info': {'now': 1071722611.0, 'import_contents': 'N', 'start_date': 1071290580.0, 'end_date': ''}, ## 'search': ['/home/bretm/'], ## } def mtime_upload(action_id, params, cache_only=None): if cache_only: return (0, "no-ops for caching", {}) action_type = 'configfiles.mtime_upload' if not _local_permission_check(action_type): log_to_file(0, "permissions error: " + str(action_type)) return _perm_error(action_type) _init() file_matches = [] now = time.time() upload_contents = None ignore_dirs = {'/proc':None, '/dev':None} if params['info']['import_contents'] == 'Y': upload_contents = 1 for to_ignore in params['ignore']: ignore_dirs[utils.normalize_path(to_ignore)] = 1 for search_path in params['search']: for dirname, dirs, names in os.walk(utils.normalize_path(search_path)): _visit_dir({ 'matches' : file_matches, 'info' : params['info'], 'ignore' : ignore_dirs, 'now' : now, }, dirname, names) if not file_matches: return 0, "No files found", {} r = rpc_cli_repository.ClientRepository() result = r.put_files(action_id, file_matches, upload_contents=upload_contents) formatted_result = format_result(result, file_matches) log_to_file(0, formatted_result) return formatted_result def upload(action_id, params, cache_only=None): if cache_only: return (0, "no-ops for caching", {}) action_type = 'configfiles.upload' if not _local_permission_check(action_type): log_to_file(0, "permissions error: " + str(action_type)) return _perm_error(action_type) _init() files = params or [] r = rpc_cli_repository.ClientRepository() result = r.put_files(action_id, files) formatted_result = format_result(result, files) log_to_file(0, formatted_result) return formatted_result def deploy(params, topdir=None, cache_only=None): if cache_only: return (0, "no-ops for caching", {}) action_type = 'configfiles.deploy' if not _local_permission_check(action_type): log_to_file(0, "permissions error: " + str(action_type)) return _perm_error(action_type) _init() files = params.get('files') or [] dep_trans = DeployTransaction(transaction_root=topdir, auto_rollback=0) for file in files: dep_trans.add(file) try: dep_trans.deploy() #5/3/05 wregglej - 135415 Adding stuff for missing user info except cfg_exceptions.UserNotFound: e = sys.exc_info()[1] try: dep_trans.rollback() except FailedRollback: log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (e[0], )) return (44, "Failed deployment and rollback, information on user '%s' could not be found" % (e[0], ), {}) #5/3/05 wregglej - 136415 Adding some more exceptions to handle except cfg_exceptions.UserNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], )) return (50, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], ), {}) #5/5/05 wregglej - 136415 Adding exception handling for unknown group, except cfg_exceptions.GroupNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, group '%s' could not be found" % (f[0],)) return (51, "Failed deployment and rollback, group '%s' could not be found" % (f[0],), {}) else: log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (e[0], )) return (50, "Failed deployment and rollback, information on user '%s' could not be found" % (e[0], ), {}) except cfg_exceptions.GroupNotFound: e = sys.exc_info()[1] try: dep_trans.rollback() except FailedRollback: log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (e[0], )) return (44, "Failed deployment and rollback, information on user '%s' could not be found" % (e[0], ), {}) #5/3/05 wregglej - 136415 Adding some more exceptions to handle except cfg_exceptions.UserNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], ) ) return (50, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], ), {}) #5/5/05 wregglej - 136415 Adding exception handling for unknown group, except cfg_exceptions.GroupNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, group '%s' could not be found" % (f[0],)) return (51, "Failed deployment and rollback, group '%s' could not be found" % (f[0],), {}) else: log_to_file(0, "Failed deployment and rollback, group '%s' could not be found" % (e[0], )) return (51, "Failed deployment and rollback, group '%s' could not be found" % (e[0], ), {}) except cfg_exceptions.FileEntryIsDirectory: e = sys.exc_info()[1] try: dep_trans.rollback() except FailedRollback: log_to_file(0, "Failed deployment and rollback, %s already exists as a directory" % (e[0], )) return (44, "Failed deployment and rollback, %s already exists as a directory" % (e[0], ), {}) #5/3/05 wregglej - 136415 Adding some more exceptions to handle except cfg_exceptions.UserNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], )) return (50, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], ), {}) #5/5/05 wregglej - 136415 Adding exception handling for unknown group, except cfg_exceptions.GroupNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, group '%s' could not be found" % (f[0],)) return (51, "Failed deployment and rollback, group '%s' could not be found" % (f[0],), {}) else: log_to_file(0, "Failed deployment, %s already exists as a directory" % (e[0], )) return (45, "Failed deployment, %s already exists as a directory" % (e[0], ), {}) except cfg_exceptions.DirectoryEntryIsFile: e = sys.exc_info()[1] try: dep_trans.rollback() except FailedRollback: log_to_file(0, "Failed deployment and rollback, %s already exists as a file" % (e[0], )) return (46, "Failed deployment and rollback, %s already exists as a file" % (e[0], ), {}) #5/3/05 wregglej - 136415 Adding exceptions for missing user except cfg_exceptions.UserNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], )) return (50, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0], ), {}) #5/5/05 wregglej - 136415 Adding exception handling for unknown group, except cfg_exceptions.GroupNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, group '%s' could not be found" % (f[0],)) return (51, "Failed deployment and rollback, group '%s' could not be found" % (f[0],), {}) else: log_to_file(0, "Failed deployment, %s already exists as a file" % (e[0], )) return (47, "Failed deployment, %s already exists as a file" % (e[0], ), {}) except Exception: e = sys.exc_info()[1] print(e) try: dep_trans.rollback() except FailedRollback: e2 = sys.exc_info()[1] log_to_file(0, "Failed deployment, failed rollback: %s" % e2) return (48, "Failed deployment, failed rollback: %s" % e2, {}) #5/3/05 wregglej - 135415 Add exception handling for missing user. except cfg_exceptions.UserNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0])) return (50, "Failed deployment and rollback, information on user '%s' could not be found" % (f[0]), {}) #5/5/05 wregglej - 136415 Adding exception handling for unknown group, except cfg_exceptions.GroupNotFound: f = sys.exc_info()[1] log_to_file(0, "Failed deployment and rollback, group '%s' could not be found" % (f[0],)) return (51, "Failed deployment and rollback, group '%s' could not be found" % (f[0],), {}) else: log_to_file(0, "Failed deployment, rolled back: %s" % e) return (49, "Failed deployment, rolled back: %s" % e, {}) extras = {} log_to_file(0, "Files successfully deployed: %s %s" % (format_file_string(files, create_key_list()), str(extras))) return 0, "Files successfully deployed", extras def diff(params, cache_only=None): def is_utf8(in_string): """Returns true if input is a valid UTF-8 string, False otherwise.""" if isinstance(in_string, UnicodeType): return True elif isinstance(in_string, StringType): try: in_string.decode('utf-8') return True except UnicodeDecodeError: return False return False if cache_only: return (0, "no-ops for caching", {}) action_type = 'configfiles.diff' if not _local_permission_check(action_type): log_to_file(0, "permissions error: " + str(action_type)) return _perm_error(action_type) _init() files = params.get('files') or [] fp = file_utils.FileProcessor() missing_files = [] diffs = {} exists = hasattr(os.path, 'lexists') and os.path.lexists or os.path.exists for file in files: path = file['path'] if not exists(path): missing_files.append(path) continue diff = fp.diff(file) if diff: diffs[path] = diff extras = {} if missing_files: extras['missing_files'] = missing_files if diffs: for file in diffs.keys(): if not is_utf8(diffs[file]): diffs[file] = "%s: binary files differ" % file extras['diffs'] = diffs log_to_file(0, "Files successfully compared: %s %s" % (format_file_string(files, create_key_list()), str(extras))) return 0, "Files successfully compared", extras verify = diff #The format_file_string and create_key_list functions can be used together to create a string #containing information about the files in file_list. Use sparingly. #file_list is a list of dictionaries containing file information. #keylist is a list of strings containing the keys of the information in file_list that you wish to print out. def format_file_string(file_list, keylist): outstr = "" for afile in file_list: outstr for key in keylist: formatstr = "\n%s: %s" if key in afile: outstr = outstr + formatstr % (key, afile[key]) outstr = outstr + "\n" return outstr #Returns a list of strings. Each string is a key in the dictionary containing file information. #The number of keys returned corresponds to the debug_level. The higher the debug_level, the longer the #list of keys. def create_key_list(): #The list of keys. The order of the keys determines what debug_level they will be returned in. #For example, at debug level 0 only the path and revision will be included. At level 1, the path, revision, #config_channel, and filemode keys should be included. key_list = [ 'path', 'revision', 'config_channel', 'filemode', 'filetype', 'encoding', 'username', 'groupname', 'delim_start', 'delim_end', 'md5sum', 'checksum_type', 'checksum', 'verify_contents', 'file_contents', ] #This dictionary associates each debug level (the key) with the index into key_list (the value) at which #we should stop including keys in the returned list. debug_levels = { 0 : 2, 1 : 4, 2 : 6, 3 : 8, 4 : 10, 5 : 15, } curr_debug = get_debug_level() if curr_debug > 5: curr_debug = 5 if curr_debug < 0: curr_debug = 0 if not curr_debug in debug_levels.keys(): curr_debug = 0 return key_list[:debug_levels[curr_debug]] def _init(): cfg = config.initUp2dateConfig() cfg_dict = dict(cfg.items()) server_url = config.getServerlURL() cfg_dict['proto'], cfg_dict['server_name'] = utils.parse_url(server_url[0], scheme="https")[:2] if len(server_url) > 1: cfg_dict['server_list'] = server_url local_config.init('rhncfg-client', defaults=cfg_dict) set_debug_level(int(local_config.get('debug_level') or 0)) set_logfile("/var/log/rhncfg-actions") 0707010000000E000081FD0000000000000000000000015CDC5C890000115A000000000000000000000000000000000000002700000000mgr-cfg/actions/rhn-actions-control.py#!/usr/bin/python # # 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 ModeControllerCreator from optparse import Option, OptionParser def main(): optionsTable = [ Option('--enable-deploy', action='store_true', help='Allow rhncfg-client to deploy files.', default=0), Option('--enable-diff', action='store_true', help='Allow rhncfg-client to diff files.', default=0), Option('--enable-upload', action='store_true', help='Allow rhncfg-client to upload files.', default=0), Option('--enable-mtime-upload', action='store_true', help='Allow rhncfg-client to upload mtime.', default=0), Option('--enable-run', action='store_true', help='Allow rhncfg-client the ability to execute remote scripts.', default=0), Option('--enable-all', action='store_true', help='Allow rhncfg-client to do everything.', default=0), Option('--disable-deploy', action='store_true', help='Disable deployment.', default=0), Option('--disable-diff', action='store_true', help='Disable diff.', default=0), Option('--disable-upload', action='store_true', help='Disable upload.', default=0), Option('--disable-mtime-upload',action='store_true', help='Disable mtime upload.', default=0), Option('--disable-run', action='store_true', help='Disable remote script execution.', default=0), Option('--disable-all', action='store_true', help='Disable all options.', default=0), Option('-f', '--force', action='store_true', help='Force the operation without confirmation', default=0), Option('--report', action='store_true', help='Report the status of the mode settings (enabled or disabled)', default=0), ] parser = OptionParser(option_list=optionsTable) (options, args) = parser.parse_args() creator = ModeControllerCreator.get_controller_creator() controller = creator.create_controller() controller.set_force(options.force) runcreator = ModeControllerCreator.get_run_controller_creator() runcontroller = runcreator.create_controller() runcontroller.set_force(options.force) if options.enable_deploy: controller.on('deploy') if options.enable_diff: controller.on('diff') if options.enable_upload: controller.on('upload') if options.enable_mtime_upload: controller.on('mtime_upload') if options.enable_all: controller.all_on() runcontroller.all_on() if options.enable_run: runcontroller.on('run') runcontroller.off('all') if options.disable_deploy: controller.off('deploy') if options.disable_diff: controller.off('diff') if options.disable_upload: controller.off('upload') if options.disable_mtime_upload: controller.off('mtime_upload') if options.disable_all: controller.all_off() runcontroller.all_off() if options.disable_run: runcontroller.off('run') runcontroller.off('all') if options.report: mode_list = ['deploy', 'diff', 'upload', 'mtime_upload'] for m in mode_list: rstring = "%s is %s" status = "disabled" if controller.is_on(m): status = "enabled" print(rstring % (m, status)) status = "disabled" if runcontroller.is_on('all'): runcontroller.off('all') runcontroller.on('run') if runcontroller.is_on('run'): status = "enabled" print(rstring % ('run', status)) if __name__ == "__main__": try: sys.exit(main() or 0) except KeyboardInterrupt: sys.stderr.write("user interrupted\n") sys.exit(0) 0707010000000F000081B40000000000000000000000015CDC5C8900000FA1000000000000000000000000000000000000002900000000mgr-cfg/actions/rhn-actions-control.sgml<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [ <!ENTITY RHNACTIONSCONTROL "Spacewalk Actions Control" > ]> <refentry> <RefMeta> <RefEntryTitle>rhn-actions-control</RefEntryTitle><manvolnum>8</manvolnum> <RefMiscInfo>Version 4.0</RefMiscInfo> </RefMeta> <RefNameDiv> <RefName><command>rhn-actions-control</command></RefName> <RefPurpose> Controls the enabling and disabling of scheduled actions. </RefPurpose> </RefNameDiv> <RefSynopsisDiv> <Synopsis> <cmdsynopsis> <command>rhn-actions-control</command> <arg>options <replaceable>...</replaceable></arg> </cmdsynopsis> <cmdsynopsis> <arg>--enable-deploy | --disable-deploy</arg> </cmdsynopsis> <cmdsynopsis> <arg>--enable-diff | --disable-diff</arg> </cmdsynopsis> <cmdsynopsis> <arg>--enable-upload | --disable-upload</arg> </cmdsynopsis> <cmdsynopsis> <arg>--enable-mtime-upload | --disable-mtime-upload</arg> </cmdsynopsis> <cmdsynopsis> <arg>--enable-run | --disable-run</arg> </cmdsynopsis> <cmdsynopsis> <arg>--enable-all | --disable-all</arg> </cmdsynopsis> <cmdsynopsis> <arg>--report</arg> </cmdsynopsis> </Synopsis> </RefSynopsisDiv> <RefSect1><Title>Description</Title> <para> The &RHNACTIONSCONTROL; (<emphasis>rhn-actions-control</emphasis>) allows the user to enable and disable different types of scheduled actions. These various modes are enabled/disabled by placing/removing files in the <emphasis>/etc/sysconfig/rhn/allowed-actions/</emphasis> directory. Due to the default permissions on the <emphasis>/etc/sysconfig/rhn/</emphasis> directory, &RHNACTIONSCONTROL; will most likely have to be run by someone with root access. </para> </RefSect1> <RefSect1><Title>Options</Title> <variablelist> <varlistentry> <term>--enable-deploy, --disable-deploy</term> <listitem> <para>Enable/disable the deploy scheduled action. I.e., allow installation of configuration files from the central repository on the system. Without this functionality enabled, the configuration management interface provides no real value for this system.</para> </listitem> </varlistentry> <varlistentry> <term>--enable-diff, --disable-diff</term> <listitem> <para>Enable/disable the diff scheduled action.</para> </listitem> </varlistentry> <varlistentry> <term>--enable-upload, --disable-upload</term> <listitem> <para>Enable/disable the upload scheduled action.</para> </listitem> </varlistentry> <varlistentry> <term>--enable-mtime-upload, --disable-mtime-upload</term> <listitem> <para>Enable/disable the mtime-upload scheduled action.</para> </listitem> </varlistentry> <varlistentry> <term>--enable-run, --disable-run</term> <listitem> <para>Enable/disable the ability to run remote scripts.</para> </listitem> </varlistentry> <varlistentry> <term>--enable-all, --disable-all</term> <listitem> <para>Enable/disable all capabilities.</para> </listitem> </varlistentry> <varlistentry> <term>-f, --force</term> <listitem> <para>Force an operation without confirmation.</para> </listitem> </varlistentry> <varlistentry> <term>--report</term> <listitem> <para>Report the status of the mode settings (enabled or disabled).</para> </listitem> </varlistentry> </variablelist> </RefSect1> <RefSect1><Title>Authors</Title> <simplelist> <member>John Wregglesworth <email>wregglej@redhat.com</email></member> <member>Todd Warner <email>taw@redhat.com</email> (man page only)</member> </simplelist> </RefSect1> </RefEntry> 07070100000010000081B40000000000000000000000015CDC5C8900000010000000000000000000000000000000000000001700000000mgr-cfg/actions/scriptscript.run(1)=1 07070100000011000081B40000000000000000000000015CDC5C89000022BD000000000000000000000000000000000000001A00000000mgr-cfg/actions/script.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 sys import pwd import grp import time import select import signal import tempfile import base64 try: MAXFD = os.sysconf("SC_OPEN_MAX") except: MAXFD = 256 # this is ugly, hopefully it will be natively supported in up2date from rhn.actions.configfiles import _local_permission_check, _perm_error from config_common import local_config from config_common.rhn_log import set_logfile, log_to_file from up2date_client import config # this is a list of the methods that get exported by a module __rhnexport__ = [ 'run', ] # action version we understand ACTION_VERSION = 2 def _create_script_file(script, uid=None, gid=None): storageDir = tempfile.gettempdir() script_path = os.path.join(storageDir, 'rhn-remote-script') # Loop a couple of times to try to get rid of race conditions for i in range(2): try: fd = os.open(script_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, int("0700", 8)) # If this succeeds, break out the loop break except OSError: e = sys.exc_info()[1] if e.errno != 17: # File exists raise # File does exist, try to remove it try: os.unlink(script_path) except OSError: e = sys.exc_info()[1] if e.errno != 2: # No such file or directory raise else: # Tried a couple of times, failed; bail out raising the latest error raise sf = os.fdopen(fd, 'wb') sf.write(script.encode("utf-8")) sf.close() if uid and gid: os.chown(script_path, uid, gid) return script_path # Make sure the dir-path to a file exists def _create_path(fpath): d = os.path.dirname(fpath) if d and not os.path.exists(d): os.makedirs(d, int("0700", 8)) return os.path.exists(d) def run(action_id, params, cache_only=None): cfg = config.initUp2dateConfig() local_config.init('rhncfg-client', defaults=dict(cfg.items())) tempfile.tempdir = local_config.get('script_tmp_dir') logfile_name = local_config.get('script_log_file') log_output = local_config.get('script_log_file_enable') if log_output: # If we're going to log, make sure we can create the logfile _create_path(logfile_name) if cache_only: return (0, "no-ops for caching", {}) action_type = 'script.run' if not _local_permission_check(action_type): return _perm_error(action_type) extras = {'output':''} script = params.get('script') if not script: return (1, "No script to execute", {}) username = params.get('username') groupname = params.get('groupname') if not username: return (1, "No username given to execute script as", {}) if not groupname: return (1, "No groupname given to execute script as", {}) timeout = params.get('timeout') if timeout: try: timeout = int(timeout) except ValueError: return (1, "Invalid timeout value", {}) else: timeout = None db_now = params.get('now') if not db_now: return (1, "'now' argument missing", {}) db_now = time.mktime(time.strptime(db_now, "%Y-%m-%d %H:%M:%S")) now = time.time() process_start = None process_end = None child_pid = None # determine uid/ugid for script ownership, uid also used for setuid... try: user_record = pwd.getpwnam(username) except KeyError: return 1, "No such user %s" % username, extras uid = user_record[2] ugid = user_record[3] # create the script on disk try: script_path = _create_script_file(script, uid=uid, gid=ugid) except OSError: e = sys.exc_info()[1] return 1, "Problem creating script file: %s" % e, extras # determine gid to run script as try: group_record = grp.getgrnam(groupname) except KeyError: return 1, "No such group %s" % groupname, extras run_as_gid = group_record[2] # create some pipes to communicate w/ the child process (pipe_read, pipe_write) = os.pipe() process_start = time.time() child_pid = os.fork() if not child_pid: # Parent doesn't write to child, so close that part os.close(pipe_read) # Redirect both stdout and stderr to the pipe os.dup2(pipe_write, sys.stdout.fileno()) os.dup2(pipe_write, sys.stderr.fileno()) # Close unnecessary file descriptors (including pipe since it's duped) for i in range(3, MAXFD): try: os.close(i) except: pass # all scripts initial working directory will be / # puts burden on script writer to ensure cwd is correct within the # script os.chdir('/') # the child process gets the desired uid/gid os.setgid(run_as_gid) groups=[g.gr_gid for g in grp.getgrall() if username in g.gr_mem or username in g.gr_name] os.setgroups(groups) os.setuid(uid) # give this its own process group (which happens to be equal to its # pid) os.setpgrp() clean_env = {"PATH": "/sbin:/bin:/usr/sbin:/usr/bin", "TERM": "xterm"} # Finally, exec the script try: os.umask(int("022", 8)) os.execve(script_path, [script_path, ], clean_env) finally: # This code can be reached only when script_path can not be # executed as otherwise execv never returns. # (The umask syscall always succeeds.) os._exit(1) # Parent doesn't write to child, so close that part os.close(pipe_write) output = None timed_out = None out_stream = open('/var/lib/up2date/action.%s' % str(action_id), 'ab+', 0) while 1: select_wait = None if timeout: elapsed = time.time() - process_start if elapsed >= timeout: timed_out = 1 # Send TERM to all processes in the child's process group # Send KILL after that, just to make sure the child died os.kill(-child_pid, signal.SIGTERM) time.sleep(2) os.kill(-child_pid, signal.SIGKILL) break select_wait = timeout - elapsed try: input_fds, output_fds, error_fds = select.select([pipe_read], [], [], select_wait) except select.error: return 255, "Termination signal occurred during execution.", {} if error_fds: # when would this happen? os.close(pipe_read) return 1, "Fatal exceptional case", extras if not (pipe_read in input_fds): # Read timed out, should be caught in the next loop continue output = os.read(pipe_read, 4096) if not output: # End of file from the child break out_stream.write(output) os.close(pipe_read) # wait for the child to complete (somepid, exit_status) = os.waitpid(child_pid, 0) process_end = time.time() # Copy the output from the temporary file out_stream.seek(0, 0) extras['output'] = out_stream.read() out_stream.close() # Log script-output locally, unless we're asked not to if log_output : set_logfile(logfile_name) log_to_file(0, extras['output']) # since output can contain chars that won't make xmlrpc very happy, # base64 encode it... extras['base64enc'] = 1 extras['output'] = base64.encodestring(extras['output']) extras['return_code'] = exit_status # calculate start and end times in db's timespace extras['process_start'] = db_now + (process_start - now) extras['process_end'] = db_now + (process_end - now) for key in ('process_start', 'process_end'): extras[key] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(extras[key])) # clean up the script os.unlink(script_path) if timed_out: return 1, "Script killed, timeout of %s seconds exceeded" % timeout, extras if exit_status == 0: return 0, "Script executed", extras return 1, "Script failed", extras 07070100000012000041FD0000000000000000000000015CDC5C8900000000000000000000000000000000000000000000001600000000mgr-cfg/config_client07070100000013000081B40000000000000000000000015CDC5C8900000386000000000000000000000000000000000000001F00000000mgr-cfg/config_client/Makefile# Makefile for spacewalk backend # # what is the backend top dir TOP = .. # Specific stuff SUBDIR = config_client EXEC_FILES = rhncfg-client FILES = $(EXEC_FILES) __init__ handler_base \ rhncfgcli_channels \ rhncfgcli_diff rhncfgcli_get \ rhncfgcli_list \ rhncfgcli_elist \ rhncfgcli_verify rpc_cli_repository CONFIG_FILES = rhncfg-client.conf # man pages DOCBOOK = $(wildcard /usr/bin/docbook2man) SGMLS = $(wildcard *.sgml) MANS = $(patsubst %.sgml,%.8,$(SGMLS)) MANDIR ?= /usr/man EXTRA_DIRS = $(BINDIR) $(CONFDIR) $(MANDIR)/man8 include $(TOP)/Makefile.defs ifneq ($(DOCBOOK),) # install man pages all :: $(MANS) install :: $(MANS) $(PREFIX)/$(MANDIR) $(INSTALL_DATA) $(MANS) $(PREFIX)/$(MANDIR)/man8 clean :: @rm -fv $(MANS) manpage.* endif install :: install-bin install-conf 07070100000014000081B40000000000000000000000015CDC5C8900000266000000000000000000000000000000000000002200000000mgr-cfg/config_client/__init__.py# # Copyright (c) 2008--2013 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. # 07070100000015000081B40000000000000000000000015CDC5C89000008D4000000000000000000000000000000000000002800000000mgr-cfg/config_client/cli_repository.py#!/usr/bin/python # # 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 os from config_common import cfg_exceptions, repository # this is a bit odd; right now, we instantiate a regular # repository.Repository object since it does a lot of local disk # access for us. In the future, though, it is unlikely Repository and # ClientRepository classes/APIs will actually share this in common # since Repository mostly requires user perms and ClientRepository # mostly requires server perms class ClientRepository: def __init__(self): self.server_repository = repository.Repository() tmp_channels = os.environ.get("RHNCFG_CHANNELS") or "all" # listed in order of losers first, ie, entry 2 overrides entry # 1, etc self.config_channels = tmp_channels.split(":") self.cfg_files = {} def list_files(self): # iterate over channels, accumulating hash of what files # come from where; subsequent entries override previous ones, # so the final hash is the result we seek if self.cfg_files: return self.cfg_files self.cfg_files = {} for ns in self.config_channels: for file in self.server_repository.list_files(ns): self.cfg_files[file] = [ ns, file ] return self.cfg_files def get_file(self, file): if not self.cfg_files: raise "never did a list_files" if file not in self.cfg_files: raise cfg_exceptions.ConfigNotManaged(file) return self.server_repository.get_file(self.cfg_files[file][0], self.cfg_files[file][1]) def list_config_channels(self): return self.config_channels 07070100000016000081B40000000000000000000000015CDC5C8900000AEB000000000000000000000000000000000000002600000000mgr-cfg/config_client/handler_base.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 os import os.path from config_common import handler_base, utils from config_common.rhn_log import log_debug class HandlerBase(handler_base.HandlerBase): def get_dest_file(self, file): target_dir = os.sep return utils.normalize_path(target_dir + os.sep + file) # Returns a list of valid files def get_valid_files(self): files_hash = {} for file in self.repository.list_files(): files_hash[file[1]] = None if not self.args: # No file specified; use all of them files = list(files_hash.keys()) files.sort() return files files = [] for file in self.args: #5/9/05 wregglej - 151197 make sure that trailing /'s aren't passed through for directories. if os.path.isdir(file): if file[-1] == "/": file = file[0:-1] if file not in files_hash: print("Not found on server: %s" % file) continue files.append(file) files.sort() return files # Main function to be run def run(self): log_debug(2) for file in self.get_valid_files(): (src, file_info, dirs_created) = self.repository.get_file_info(file) ftype = file_info.get('filetype') if not src: continue dst = self.get_dest_file(file) self._process_file(src, dst, file, ftype, file_info) # To be overridden with specific actions in subclasses def _process_file(self, *args): pass class TopdirHandlerBase(HandlerBase): _options_table = [ HandlerBase._option_class( '--topdir', action="store", help="Make all file operations relative to this directory.", ), HandlerBase._option_class( '--exclude', action="append", help="Excludes a file from being deployed with 'get'. May be used multiple times.", ), ] def get_dest_file(self, file): target_dir = self.options.topdir or os.sep return utils.normalize_path(target_dir + os.sep + file) 07070100000017000081B40000000000000000000000015CDC5C89000005A6000000000000000000000000000000000000002900000000mgr-cfg/config_client/rhncfg-client.conf[rhncfg-client] # don't change this systemid = /etc/sysconfig/rhn/systemid # increase for debugging output debug_level = 0 # Default directory for remote command temporary script file. # If not set it will default to None and it will fallback to python's default tempdir search algorithm script_tmp_dir = /var/spool/rhn # don't change this... used in substitutions below. # if you get rid of the '%(server_handler)s' bits below, # the *MUST* be replaced with this value... server_handler = /CONFIG-MANAGEMENT # don't change this, period. repository_type = rpc_cli_repository # to use a server other than what up2date is configured to use, # do something along the lines of: # server_url = https://some.example.com%(server_handler)s # server_url = http://another.example.net:8080%(server_handler)s # server_url = https://yet.another.example.org:8081/CONFIG-MANAGEMENT server_url = https://%(server_name)s%(server_handler)s # the following fields are inherited from up2date's configuration, # but are overridable in this file # enableProxy = 1 # enableProxyAuth = 1 # httpProxy = some.proxy.example.com:3030 # proxyUser = proxy_user_name # proxyPassword = proxy_password # sslCACert = /usr/share/rhn/RHNS-CA-CERT # control of remote-script-logging # Should we store output of a RemoteCommand locally? 1 = yes, 0 = no script_log_file_enable = 0 # Where will we write that output? script_log_file = /var/log/rhn/rhncfg-action-output.log 07070100000018000081B40000000000000000000000015CDC5C89000004F1000000000000000000000000000000000000002700000000mgr-cfg/config_client/rhncfg-client.py#!/usr/bin/python # # Copyright (c) 2008--2012 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, os from config_common.rhn_main import BaseMain class Main(BaseMain): modes = ['diff', 'get', 'list', 'elist', 'channels', 'verify'] repository_class_name = 'ClientRepository' plugins_dir = 'config_client' config_section = 'rhncfg-client' mode_prefix = 'rhncfgcli' if __name__ == '__main__': # quick check to see if you are a super-user. if os.getuid() != 0: sys.stderr.write("ERROR: must be root to execute!" + "\n") sys.exit(1) try: sys.exit(Main().main() or 0) except KeyboardInterrupt: sys.stderr.write("user interrupted\n") sys.exit(0) 07070100000019000081B40000000000000000000000015CDC5C8900001408000000000000000000000000000000000000002900000000mgr-cfg/config_client/rhncfg-client.sgml<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [ <!ENTITY RHN "Spacewalk" > <!ENTITY RHNCFGC "rhncfg-client" > ]> <refentry> <RefMeta> <RefEntryTitle>&RHNCFGC;</RefEntryTitle><manvolnum>8</manvolnum> <RefMiscInfo>Version 4.0</RefMiscInfo> </RefMeta> <RefNameDiv> <RefName><command>&RHNCFGC;</command></RefName> <RefPurpose> a tool used for system configuration querying and deployment. </RefPurpose> </RefNameDiv> <RefSynopsisDiv> <Synopsis> <cmdsynopsis> <command>&RHNCFGC;</command> <arg>mode </arg> <arg>options <replaceable>...</replaceable></arg> <arg>arguments <replaceable>...</replaceable></arg> </cmdsynopsis> </Synopsis> </RefSynopsisDiv> <RefSect1><Title>Description</Title> <para> The &RHN; Configuration Client (<emphasis>&RHNCFGC;</emphasis>) is used to query a system's configuration makeup and deployment status as managed by &RHN;. </para> <para> This tool enables a &RHN; client to <emphasis>list</emphasis> available configuration files, fetch (or <emphasis>get</emphasis>) those configuration files, <emphasis>view</emphasis> configuration channels, and finally, <emphasis>diff</emphasis> and <emphasis>verify</emphasis> current configuration of the system in comparison to the system's status according to &RHN;. </para> <para> <simplelist> <member>This tool utilizes a "cvs style" command syntax: command mode options</member> <member>For example: &RHNCFGC; diff --topdir /home/test/blah/</member> </simplelist> </para> </RefSect1> <RefSect1><Title>mode & options</Title> <variablelist> <varlistentry> <term>list</term> <listitem> <para> List the configuration files for the machine and the labels of the config channels containing them. Used alone. </para> </listitem> </varlistentry> <varlistentry> <term>elist</term> <listitem> <para> List the configuration files for the machine and the labels of the config channels containing them which provides an ls-esque style output. Used alone. </para> </listitem> </varlistentry> <varlistentry> <term>get <replaceable>CONFIG_FILEPATH</replaceable></term> <listitem> <para> Download/deploy the most relevant configuration file for the machine. For example: &RHNCFGC; get /etc/foo.conf </para> </listitem> </varlistentry> <varlistentry> <term>channels</term> <listitem> <para> View the labels and names of the config channels that apply to the system. Used alone. </para> </listitem> </varlistentry> <varlistentry> <term>diff</term> <listitem> <para> Obtain a difference between the config files deployed on the system and those for it stored by &RHN;. </para> <para> May be used with the --topdir <replaceable>ROOT_PATH</replaceable> option. E.g., &RHNCFGC; diff --topdir /home/test/foo/ </para> <para> If a file is readable only by root then the differences are not displayed by default. The option -d or --diff-secure-files forces the differences to be displayed. </para> </listitem> </varlistentry> <varlistentry> <term>verify</term> <listitem> <para> Quick verification to see if files are different than those associated with it on &RHN;. </para> <para> All files are displayed unless called with the -o or --only option, in which case only files with differences are displayed. </para> </listitem> </varlistentry> <varlistentry> <term>-h, --help</term> <listitem> <para>Display the help screen with a list of options.</para> </listitem> </varlistentry> </variablelist> </RefSect1> <RefSect1><Title>Examples/Summary</Title> <simplelist> <member><command>&RHNCFGC; list</command></member> <member><command>&RHNCFGC; elist</command></member> <member><command>&RHNCFGC; get /etc/foo.conf</command></member> <member><command>&RHNCFGC; channels</command></member> <member><command>&RHNCFGC; diff</command></member> <member><command>&RHNCFGC; diff --topdir /tmp/configroot/</command></member> <member><command>&RHNCFGC; verify</command></member> </simplelist> </RefSect1> <RefSect1><Title>See Also</Title> <simplelist> <member>rhncfg-manager (8)</member> </simplelist> </RefSect1> <RefSect1><Title>Authors</Title> <simplelist> <member>Mihai Ibanescu <email>misa@redhat.com</email></member> <member>Bret McMillan <email>bretm@redhat.com</email></member> <member>Todd Warner <email>taw@redhat.com</email> (man page only)</member> </simplelist> </RefSect1> </RefEntry> 0707010000001A000081B40000000000000000000000015CDC5C8900000497000000000000000000000000000000000000002500000000mgr-cfg/config_client/rhncfgcli.conf[rhncfgcli] # Should we display the diff of a file that is only readable by root? By # default we do not, uncomment this line to change that. #display_diff = True # don't change this systemid = /etc/sysconfig/rhn/systemid # increase for debugging output debug_level = 0 # don't change this... used in substitutions below. # if you get rid of the '%(server_handler)s' bits below, # the *MUST* be replaced with this value... server_handler = /CONFIG-MANAGEMENT # don't change this, period. repository_type = rpc_cli_repository # to use a server other than what up2date is configured to use, # do something along the lines of: # server_url = https://some.example.com%(server_handler)s # server_url = http://another.example.net:8080%(server_handler)s # server_url = https://yet.another.example.org:8081/CONFIG-MANAGEMENT server_url = https://%(server_name)s%(server_handler)s # the following fields are inherited from up2date's configuration, # but are overridable in this file # enableProxy = 1 # enableProxyAuth = 1 # httpProxy = some.proxy.example.com:3030 # proxyUser = proxy_user_name # proxyPassword = proxy_password # sslCACert = /usr/share/rhn/RHNS-CA-CERT 0707010000001B000081B40000000000000000000000015CDC5C89000004E1000000000000000000000000000000000000002C00000000mgr-cfg/config_client/rhncfgcli_channels.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. # from config_common.rhn_log import log_debug import handler_base class Handler(handler_base.HandlerBase): def run(self): log_debug(2) r = self.repository config_channels = r.load_config_channels() if not config_channels: print("(no config channels associated with this system)") return print("Config channels:") templ = "%-32s%s" label = "Label" name = "Name" print(templ % (label, name)) print(templ % ('-' * len(label), '-' * len(name))) for c in config_channels: print(templ % (c['label'], c['name'])) 0707010000001C000081B40000000000000000000000015CDC5C8900000C5E000000000000000000000000000000000000002800000000mgr-cfg/config_client/rhncfgcli_diff.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 sys import os import handler_base from config_common.file_utils import diff from config_common.local_config import get as get_config class Handler(handler_base.HandlerBase): _options_table = [ handler_base.HandlerBase._option_class( '-d', '--diff-secure-files', dest='display_diff', action="store_true", default=False, help="Force diff to display the diff for secure files.", ) ] _usage_options = handler_base.HandlerBase._usage_options + " [ files ... ]" def _process_file(self, *args): src, dst= args [:2] type = args[3] file_info = args[4] if type == 'symlink': if not os.path.exists(dst): print("Symbolic link '%s' is missing" % dst) return if not os.path.islink(dst): print("Path '%s' is not a symbolic link" % dst) return #dst is a symlink, so just tell the user we're skipping the entry srclink = os.readlink(src) destlink = os.readlink(dst) if srclink != destlink: print("Symbolic links differ. Channel: '%s' -> '%s' System: '%s' -> '%s' " % (dst,srclink, dst, destlink)) elif type == 'file': if 'is_binary' in file_info and file_info['is_binary'] == 'Y': src_content = dst_content = None content_differs = False src_file = open(src, 'rb') src_content = src_file.read() src_file.close() if os.access(dst, os.R_OK): dst_file = open(dst, 'rb') dst_content = dst_file.read() dst_file.close() else: print("File %s that is not readable. Re-deployment of configuration file is recommended." % dst) return if len(src_content) != len(dst_content): content_differs = True else: for i in range(len(src_content)): if src_content[i] != dst_content[i]: content_differs = True break if content_differs: sys.stdout.write("Binary file %s differs.\n" % (dst)) else: sys.stdout.write(''.join(diff(src, dst, srcname=dst, dstname=dst, display_diff= (self.options.display_diff or get_config('display_diff'))))) 0707010000001D000081FD0000000000000000000000015CDC5C8900000714000000000000000000000000000000000000002900000000mgr-cfg/config_client/rhncfgcli_elist.pyfrom config_common.rhn_log import log_debug, die from config_common.file_utils import ostr_to_sym import handler_base, base64 import sys from rhn.i18n import bstr class Handler(handler_base.HandlerBase): def run(self): log_debug(2) r = self.repository files = r.list_files() if not files: die(1, "No managed files.") label = "Config Channel" maxlen = max([len(s[0]) for s in files]) maxlen = max(maxlen, len(label)) + 2 print("%-10s %8s %-8s %10s %+3s %*s %s" % ('Mode', 'Owner', 'Group', 'Size', 'Rev', maxlen, label, "File")) arg_files = [] if len(sys.argv) > 2: arg_files = sys.argv[2:len(sys.argv)] for file in files: if len(arg_files) and not file[1] in arg_files: continue # Get the file info finfo = r.get_file_info(file[1])[1] # Get the file length if finfo['encoding'] == 'base64': fsize = len(base64.decodestring(bstr(finfo['file_contents']))) else: # * indicates raw 'unencoded' size fsize = '*' + str(len(finfo['file_contents'])) if finfo['filetype'] == 'symlink': permstr = ostr_to_sym('777', finfo['filetype']) dest = "%s -> %s" % (file[1], finfo['symlink']) fsize = str(len(finfo['symlink'])) finfo['username'] = 'root' finfo['groupname'] = 'root' else: permstr = ostr_to_sym(finfo['filemode'], finfo['filetype']) or '' dest = file[1] print("%10s %8s %-8s %10s %+3s %*s %s" % (permstr, finfo['username'], finfo['groupname'], fsize, finfo['revision'], maxlen, file[0], dest)) 0707010000001E000081B40000000000000000000000015CDC5C89000003E6000000000000000000000000000000000000002700000000mgr-cfg/config_client/rhncfgcli_get.py# # Copyright (c) 2008--2013 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 handler_base from config_common.deploy import deploy_files class Handler(handler_base.TopdirHandlerBase): _usage_options = handler_base.HandlerBase._usage_options + " [ files ... ]" def run(self): deploy_files(self.options.topdir, self.repository, self.get_valid_files(), self.options.exclude) 0707010000001F000081B40000000000000000000000015CDC5C89000006F6000000000000000000000000000000000000002800000000mgr-cfg/config_client/rhncfgcli_list.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. # from config_common.rhn_log import log_debug, die import handler_base import sys class Handler(handler_base.HandlerBase): def run(self): log_debug(2) r = self.repository files = r.list_files() if not files: die(1, "No managed files.") label = "Config Channel" maxlen = max([len(s[0]) for s in files]) maxlen = max(maxlen, len(label)) + 2 print("DoFoS %*s %s" % (maxlen, label, "File")) arg_files = [] if len(sys.argv) > 2: arg_files = sys.argv[2:len(sys.argv)] for file in files: if len(arg_files) and not file[1] in arg_files: continue # checking to see if the filetype is in the 'file' entry, # and if it is and that type is '1', it is a file if (len(file) < 3) or file[2] == 1: print("F %*s %s" % (maxlen, file[0], file[1])) elif file[2] == 2 : # the filetype is a directory print("D %*s %s" % (maxlen, file[0], file[1])) else: print("S %*s %s" % (maxlen, file[0], file[1])) 07070100000020000081B40000000000000000000000015CDC5C89000031E2000000000000000000000000000000000000002A00000000mgr-cfg/config_client/rhncfgcli_verify.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. # from config_common import utils from config_common.rhn_log import log_debug import handler_base import os import stat import pwd, grp try: from selinux import lgetfilecon except: # on rhel4 we do not support selinux def lgetfilecon(path): return [0, ''] class Handler(handler_base.HandlerBase): _usage_options = handler_base.HandlerBase._usage_options + " [ files ... ]" _options_table = [ handler_base.HandlerBase._option_class( '--verbose', "-v", action="count", help="Increase the amount of output detail.", ), handler_base.HandlerBase._option_class( '--only', "-o", action="count", help="Only show files that differ.", ), ] # Main function to be run def run(self): log_debug(2) ret = [] #Labels for column headers status_label = "STATUS" owner_status = "OWNER" group_status = "GROUP" mode_status = "MODE" selinux_status = "SELINUX" file_status = "FILE" status_help = "(channel:local)" maxlenarr = { 'status' : len(status_label), 'owner' : max(len(owner_status), len(status_help)), 'group' : max(len(group_status), len(status_help)), 'mode' : max(len(mode_status), len(status_help)), 'selinux' : max(len(selinux_status), len(status_help)), } #Iterate throught the files and process them. The src file is the file as it is in the config channel, #the dst file is the file as it is in the filesystem. for file in self.get_valid_files(): (src, file_info, dirs_created) = self.repository.get_file_info(file) ftype = file_info.get('filetype') if not src: continue dst = self.get_dest_file(file) #Added file_info parameter, which contains information needed to look for differences in the owner, group, and mode. ret_dict = self._process_file(src, dst, file, ftype, file_info) if self.options.verbose: #Get the max of the return values for this file, which is used to determine the length of each field in the output. #Don't include the 'file' value, because it gets displayed last in each row and will throw off the size of the other fields. maxlenarr['status'] = max(maxlenarr['status'], len(ret_dict['status'])) maxlenarr['owner'] = max(maxlenarr['owner'], len(ret_dict['owner'])) maxlenarr['group'] = max(maxlenarr['group'], len(ret_dict['group'])) maxlenarr['mode'] = max(maxlenarr['mode'], len(ret_dict['mode'])) if len(ret_dict['selinux']) > 0: (src, dst) = ret_dict['selinux'].split('|') maxlenarr['selinux'] = max(maxlenarr['selinux'], len(src), len(dst)) #Place the return values into a list so we can iterate through them later when we want to print them out. ret.append(ret_dict) if self.options.verbose: formatstr = "%-*s" #format string for the fields where the length matters. formatstr_nolimit = "%-s" #format string for the fields where the length of the field doesn't matter. Namely, the file field. #The overall format of the output. outstring = "%(status)s %(owner)s %(group)s %(mode)s %(selinux)s %(file)s" #Print out the column labels. print(outstring % { "status" : formatstr % (maxlenarr['status'], status_label), "owner" : formatstr % (maxlenarr['owner'], owner_status), "group" : formatstr % (maxlenarr['group'], group_status), "mode" : formatstr % (maxlenarr['mode'], mode_status), "selinux" : formatstr % (maxlenarr['selinux'], selinux_status), "file" : formatstr_nolimit % (file_status), }) print(outstring % { "status" : formatstr % (maxlenarr['status'], ""), "owner" : formatstr % (maxlenarr['owner'], status_help), "group" : formatstr % (maxlenarr['group'], status_help), "mode" : formatstr % (maxlenarr['mode'], status_help), "selinux" : formatstr % (maxlenarr['selinux'], status_help), "file" : "" }) #Go through each of the dictionaries returned by self._process_file(), format their values, and print out the result. for fdict in ret: src_selinux = dst_selinux = "" if len(fdict['selinux']) > 0: (src_selinux, dst_selinux) = fdict['selinux'].split('|') if self.options.only: sum = 0 for key in fdict.keys(): if key != 'file': sum += len(fdict[key]) if sum == 0: continue print(outstring % { "status" : formatstr % (maxlenarr['status'], fdict['status']), "owner" : formatstr % (maxlenarr['owner'], fdict['owner']), "group" : formatstr % (maxlenarr['group'], fdict['group']), "mode" : formatstr % (maxlenarr['mode'], fdict['mode']), "selinux" : formatstr % (maxlenarr['selinux'], src_selinux), "file" : formatstr_nolimit % (fdict['file']), }) if len(dst_selinux) > 0: print(outstring % { "status" : formatstr % (maxlenarr['status'], ""), "owner" : formatstr % (maxlenarr['owner'], ""), "group" : formatstr % (maxlenarr['group'], ""), "mode" : formatstr % (maxlenarr['mode'], ""), "selinux" : formatstr % (maxlenarr['selinux'], dst_selinux), "file" : "", }) #Not verbose, so give the simple output for each file... else: outstring = "%*s %s" maxlen = max([0] + [len(x['status']) for x in ret]) + 1 for fdict in ret: if self.options.only and len(fdict['status']) == 0: continue print(outstring % (maxlen, fdict['status'], fdict['file'])) def _process_file(self, *args): owner_report = "%s:%s" group_report = "%s:%s" perm_report = "%s:%s" selinux_report = "%s|%s" src, dst, file, type, info = args[:5] owner_status = "" group_status = "" perm_status = "" selinux_status = "" status = [] stat_err = 0 #Stat the destination file try: dst_stat = os.lstat(dst) except: stat_err = 1 if type != 'symlink': src_user = info['username'] if not stat_err: #check for owner differences dst_uid = dst_stat[stat.ST_UID] try: dst_user = pwd.getpwuid(dst_uid)[0] except KeyError: # Orphan UID with no name,return unknown dst_user = "unknown(UID %d)" % (dst_uid,) else: dst_user = "missing" #owner_status gets displayed with the verbose option. if src_user == dst_user: owner_status = "" else: owner_status = owner_report % (src_user, dst_user) status.append('user') src_group = info['groupname'] if not stat_err: #check for group differences dst_gid = dst_stat[stat.ST_GID] try: dst_group = grp.getgrgid(dst_gid)[0] except KeyError: # Orphan GID with no name,return unknown dst_group = "unknown(GID %d)" % (dst_gid,) else: dst_group = "missing" #group_status gets displayed with the verbose option. if src_group == dst_group: group_status = "" else: group_status = group_report % (src_group, dst_group) status.append('group') #check for permissions differences src_perm = str(info['filemode']) if not stat_err: #The mode returned by stat is decimal, but won't match the value in file_info unless it's octal. #Unfortunately, the mode in file_info looks like the octal value of the mode, except it's in decimal. #The solution I came up with is to convert them both into strings, rip off the leading '0' from the #mode returned by stat, use the resulting strings. It sucks, but it seems to work (for now). dst_perm = str(oct(stat.S_IMODE(dst_stat[stat.ST_MODE]))) else: dst_perm = "missing" #rip off the leading '0' from the mode returned by stat() if dst_perm[0] == '0': dst_perm = dst_perm[1:] #perm_status gets displayed with the verbose option. if src_perm == dst_perm: perm_status = "" else: perm_status = perm_report % (src_perm, dst_perm) status.append('mode') # compare selinux contexts if 'selinux_ctx' in info: src_selinux = info['selinux_ctx'] if src_selinux: if not stat_err: try: dst_selinux = lgetfilecon(dst)[1] except OSError: dst_selinux = "" if dst_selinux == None: dst_selinux = "" else: dst_selinux = "missing" if src_selinux == dst_selinux: selinux_status = "" else: selinux_status = selinux_report % (src_selinux, dst_selinux) status.append('selinux') #figure out the ultimate value of status. if stat_err: status = ["missing"] elif type == 'symlink': if not os.path.islink(file): status = ["missing"] elif os.readlink(file) != info['symlink']: status.append('target-link-modified') elif type == 'directory': if not os.path.isdir(file): status = ["missing"] elif not os.access(dst, os.R_OK): status = ["missing"] else: src_sum = utils.sha256_file(src) dst_sum = utils.sha256_file(dst) if src_sum != dst_sum: status.append('modified') return { "status" : ','.join(status), "owner" : owner_status, "group" : group_status, "mode" : perm_status, "selinux" : selinux_status, "file" : file, } 07070100000021000081B40000000000000000000000015CDC5C890000182D000000000000000000000000000000000000002C00000000mgr-cfg/config_client/rpc_cli_repository.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 sys try: # python2 import xmlrpclib except ImportError: # python3 import xmlrpc.client as xmlrpclib from config_common import local_config, cfg_exceptions, file_utils, \ repository from config_common.rhn_log import log_debug from spacewalk.common.usix import raise_with_tb import traceback class ClientRepository(repository.RPC_Repository): default_systemid = "/etc/sysconfig/rhn/systemid" # bug #170825,169203: reusing the base class's default value for setup_network def __init__(self, setup_network=1): repository.RPC_Repository.__init__(self, setup_network) systemid_file = local_config.get("systemid") or self.default_systemid try: f = open(systemid_file, "r") except IOError: e = sys.exc_info()[1] sys.stderr.write("Cannot open %s: %s\n" % (systemid_file, e)) sys.exit(1) self.system_id = f.read() f.close() log_debug(4, 'system id', self.system_id) self.files_to_delete = [] def rpc_call(self, method_name, *params): try: result = repository.RPC_Repository.rpc_call(self, method_name, *params) except xmlrpclib.Fault: e = sys.exc_info()[1] if e.faultCode == -9: # System not subscribed raise_with_tb(cfg_exceptions.AuthenticationError( "Invalid digital server certificate%s" % e.faultString), sys.exc_info()[2]) raise return result def load_config_channels(self): log_debug(4) self.config_channels = self.rpc_call( 'config.client.list_config_channels', self.system_id) return self.config_channels def list_files(self): log_debug(4) return self.rpc_call('config.client.list_files', self.system_id) def get_file_info(self, file, auto_delete=1, dest_directory=None): log_debug(4, file) result = self.rpc_call('config.client.get_file', self.system_id, file) if 'missing' in result: return None dirs_created = None # Older servers will not return directories; if filetype is missing, # assume file if result.get('filetype') == 'directory': if dest_directory: result['path'] = dest_directory.rstrip(os.path.sep) + result['path'] if os.path.isfile(result['path']): raise cfg_exceptions.DirectoryEntryIsFile(result['path']) else: auto_delete = 0 temp_file = result['path'] else: f = file_utils.FileProcessor() temp_file, dirs_created = f.process(result, directory=dest_directory) if auto_delete: self.files_to_delete.append(temp_file) return temp_file, result, dirs_created def put_files(self, action_id, files, upload_contents=1): """Inserts a set of files into the repo, as a result of a scheduled action""" log_debug(4) missing_files = [] files_too_large = [] failed_due_to_quota = [] max_file_size = self.get_maximum_file_size() for file in files: try: params = self._make_file_info(file, local_path=None, load_contents=upload_contents) except cfg_exceptions.RepositoryLocalFileError: missing_files.append(file) continue if upload_contents and (params['size'] > max_file_size): files_too_large.append(file) continue try: self.rpc_call('config.client.upload_file', self.system_id, action_id, params) except xmlrpclib.Fault: e = sys.exc_info()[1] fault_code, fault_string = e.faultCode, e.faultString # deal with particular faults if fault_code == -4003: # File too large files_too_large.append(file) elif fault_code == -4014: # Ran out of org quota space failed_due_to_quota.append(file) else: raise_with_tb(cfg_exceptions.RepositoryFilePushError(fault_code, fault_string), sys.exc_info()[2]) except Exception: traceback.print_exc() raise result = {} # If there are files too large to be pushed, result will have a key # `file_too_large' if len(files_too_large) > 0: result['files_too_large'] = files_too_large if len(failed_due_to_quota) > 0: result['failed_due_to_quota'] = failed_due_to_quota if len(missing_files) > 0: result['missing_files'] = missing_files return result def list_config_channels(self): log_debug(4) return self.config_channels def _get_default_delimiters(self): "retrieves the default delimiters from the server" log_debug(4) result = self.rpc_call('config.client.get_default_delimiters', self.system_id) return result.get('delim_start'), result.get('delim_end') def _get_maximum_file_size(self): log_debug(4) result = self.rpc_call('config.client.get_maximum_file_size', self.system_id) return result def cleanup(self): log_debug(4) for file in self.files_to_delete: os.unlink(file) 07070100000022000041FD0000000000000000000000015CDC5C8900000000000000000000000000000000000000000000001600000000mgr-cfg/config_common07070100000023000081B40000000000000000000000015CDC5C890000016B000000000000000000000000000000000000001F00000000mgr-cfg/config_common/Makefile# Makefile for spacewalk backend # # what is the backend top dir TOP = .. # Specific stuff SUBDIR = config_common FILES = __init__ cfg_exceptions \ file_utils deploy \ handler_base local_config \ repository rhn_log rhn_main \ utils transactions rpc_wrapper include $(TOP)/Makefile.defs 07070100000024000081B40000000000000000000000015CDC5C8900000266000000000000000000000000000000000000002200000000mgr-cfg/config_common/__init__.py# # Copyright (c) 2008--2013 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. # 07070100000025000081B40000000000000000000000015CDC5C89000006FE000000000000000000000000000000000000002800000000mgr-cfg/config_common/cfg_exceptions.py# # Copyright (c) 2008--2013 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. # class InvalidSession(Exception): pass class AuthenticationError(Exception): pass class ConfigChannelNotInRepo(Exception): pass class ConfigChannelAlreadyExistsError(Exception): pass class ConfigChannelNotEmptyError(Exception): pass class ConfigNotManaged(Exception): pass class ConfigurationError(Exception): pass class BinaryFileDiffError(Exception): pass class RepositoryFileError(Exception): pass class RepositoryLocalFileError(Exception): pass class RepositoryFileMissingError(Exception): pass class RepositoryFilePushError(RepositoryFileError): pass class ConfigFileTooLargeError(RepositoryFilePushError): pass class QuotaExceeded(RepositoryFilePushError): pass class RepositoryFileExistsError(RepositoryFilePushError): "Attempted to add a file that already exists" pass class RepositoryFileVersionMismatchError(RepositoryFilePushError): "File upload failed because the version changed underneath" pass class FileEntryIsDirectory(Exception): pass class DirectoryEntryIsFile(Exception): pass class UserNotFound(Exception): pass class GroupNotFound(Exception): pass 07070100000026000081B40000000000000000000000015CDC5C8900000EA0000000000000000000000000000000000000002000000000mgr-cfg/config_common/deploy.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 os import sys from config_common.transactions import DeployTransaction, FailedRollback from config_common import file_utils from config_common import cfg_exceptions def deploy_msg_callback(path): print("Deploying %s" % path) def deploy_files(topdir, repository, files, excludes = None, config_channel = None): topdir = topdir or os.sep if not excludes: excludes = [] dep_trans = DeployTransaction(transaction_root=topdir) dep_trans.deploy_callback(deploy_msg_callback) for path in files: if path in excludes: print("Excluding %s" % path) else: try: if config_channel: args = (config_channel, path) else: args = (path, ) kwargs = {'auto_delete': 0, 'dest_directory': topdir} finfo = repository.get_file_info(*args, **kwargs) except cfg_exceptions.DirectoryEntryIsFile: e = sys.exc_info()[1] print("Error: unable to deploy directory %s, as it is already a file on disk" % e[0]) continue if finfo is None: # File disappeared since we called the function continue (processed_path, file_info, dirs_created) = finfo try: dep_trans.add_preprocessed(path, processed_path, file_info, dirs_created) except cfg_exceptions.UserNotFound: e = sys.exc_info()[1] print("Error: unable to deploy file %s, information on user '%s' could not be found." % (path,e[0])) continue except cfg_exceptions.GroupNotFound: e = sys.exc_info()[1] print("Error: unable to deploy file %s, information on group '%s' could not be found." % (path, e[0])) continue try: dep_trans.deploy() #5/3/05 wregglej - 136415 added missing user exception stuff. except cfg_exceptions.UserNotFound: e = sys.exc_info()[1] try_rollback(dep_trans, "Error unable to deploy file, information on user '%s' could not be found" % e[0]) except cfg_exceptions.GroupNotFound: e = sys.exc_info()[1] try_rollback(dep_trans, "Error: unable to deploy file, information on group '%s' could not be found" % e[0]) except cfg_exceptions.FileEntryIsDirectory: e = sys.exc_info()[1] try_rollback(dep_trans, "Error: unable to deploy file %s, as it is already a directory on disk" % e[0]) except cfg_exceptions.DirectoryEntryIsFile: e = sys.exc_info()[1] try_rollback(dep_trans, "Error: unable to deploy directory %s, as it is already a file on disk" % e[0]) except Exception: try: try_rollback(dep_trans, "Deploy failed, rollback successful") except: print("Failed rollback") raise def try_rollback(dep_trans, msg): try: dep_trans.rollback() except (FailedRollback, cfg_exceptions.UserNotFound, cfg_exceptions.GroupNotFound): pass print(msg) 07070100000027000081B40000000000000000000000015CDC5C89000033F6000000000000000000000000000000000000002400000000mgr-cfg/config_common/file_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 sys import stat import time import tempfile import base64 import difflib import pwd import grp try: from selinux import lgetfilecon except: # on rhel4 we do not support selinux def lgetfilecon(path): return [0, ''] from config_common import utils from config_common.local_config import get as get_config from rhn.i18n import bstr, sstr decodestring = base64.decodestring if hasattr(base64, 'decodebytes'): decodestring = base64.decodebytes class FileProcessor: file_struct_fields = { 'file_contents' : None, 'delim_start' : None, 'delim_end' : None, } def __init__(self): pass def process(self, file_struct, directory=None, strict_ownership=1): # Older servers will not return directories; if filetype is missing, # assume file if file_struct.get('filetype') == 'directory': if directory is None: directory = "" return None, utils.mkdir_p(directory + file_struct['path']) if directory: directory += os.path.split(file_struct['path'])[0] if file_struct.get('filetype') == 'symlink': if 'symlink' not in file_struct: raise Exception("Missing key symlink") (fullpath, dirs_created, fd) = maketemp(prefix=".rhn-cfg-tmp", directory=directory) os.close(fd) os.unlink(fullpath) os.symlink(file_struct['symlink'], fullpath) return fullpath, dirs_created for k in self.file_struct_fields.keys(): if k not in file_struct: # XXX raise Exception("Missing key %s" % k) encoding = '' if 'encoding' in file_struct: encoding = file_struct['encoding'] contents = file_struct['file_contents'] if contents and (encoding == 'base64'): contents = decodestring(bstr(contents)) delim_start = file_struct['delim_start'] delim_end = file_struct['delim_end'] if ('checksum' in file_struct and 'checksum_type' in file_struct and 'verify_contents' in file_struct and file_struct['verify_contents']): if file_struct['checksum'] != utils.getContentChecksum( file_struct['checksum_type'], contents): raise Exception("Corrupt file received: Content checksums do not match!") elif ('md5sum' in file_struct and 'verify_contents' in file_struct and file_struct['verify_contents']): if file_struct['md5sum'] != utils.getContentChecksum( 'md5', contents): raise Exception("Corrupt file received: Content checksums do not match!") elif ('verify_contents' in file_struct and file_struct['verify_contents']): raise Exception("Corrupt file received: missing checksum information!") (fullpath, dirs_created, fd) = maketemp(prefix=".rhn-cfg-tmp", directory=directory) try: os.write(fd, bstr(contents)) except Exception: raise finally: os.close(fd) # try to set mtime and ctime of the file to # the last modified time on the server if 'modified' in file_struct: try: modified = xmlrpc_time(file_struct['modified'].value) epoch_time = time.mktime(modified) os.utime(fullpath, (epoch_time, epoch_time)) except (ValueError, AttributeError): # we can't parse modified time pass return fullpath, dirs_created def diff(self, file_struct): self._validate_struct(file_struct) temp_file, temp_dirs = self.process(file_struct) path = file_struct['path'] sectx_result = '' owner_result = '' group_result = '' perm_result = '' result = '' stat_err = 0 try: cur_stat = os.lstat(path) except: stat_err = 1 if file_struct['filetype'] != 'symlink': if not stat_err: #check for owner differences cur_uid = cur_stat[stat.ST_UID] try: cur_user = pwd.getpwuid(cur_uid)[0] except KeyError: #Orphan UID with no name,return unknown cur_user = "unknown(UID %d)" % (cur_uid,) else: cur_user = "missing" if cur_user == file_struct['username']: owner_result = "" else: owner_result = "User name differ: actual: [%s], expected: [%s]\n" % (cur_user, file_struct['username']) if not stat_err: #check for group differences cur_gid = cur_stat[stat.ST_GID] try: cur_group = grp.getgrgid(cur_gid)[0] except KeyError: #Orphan GID with no name,return unknown cur_group = "unknown(GID %d)" % (cur_gid,) else: cur_group = "missing" if cur_group == file_struct['groupname']: group_result = "" else: group_result = "Group name differ: actual: [%s], expected: [%s]\n" % (cur_group, file_struct['groupname']) #check for permissions differences if not stat_err: cur_perm = format(stat.S_IMODE(cur_stat[stat.ST_MODE]), 'o') else: cur_perm = "missing" #rip off the leading '0' from the mode returned by stat() if cur_perm[0] == '0': cur_perm = cur_perm[1:] #perm_status gets displayed with the verbose option. if cur_perm == str(file_struct['filemode']): perm_result = "" else: perm_result = "File mode differ: actual: [%s], expected: [%s]\n" % (cur_perm, file_struct['filemode']) try: cur_sectx = lgetfilecon(path)[1] except OSError: # workarounding BZ 690238 cur_sectx = None if cur_sectx == None: cur_sectx = '' if 'selinux_ctx' in file_struct and file_struct['selinux_ctx']: if cur_sectx != file_struct['selinux_ctx']: sectx_result = "SELinux contexts differ: actual: [%s], expected: [%s]\n" % (cur_sectx, file_struct['selinux_ctx']) if file_struct['filetype'] == 'directory': if os.path.isdir(file_struct['path']): result = '' else: result = "Deployed directory is no longer a directory!" elif file_struct['filetype'] == 'symlink': try: curlink = os.readlink(path) newlink = os.readlink(temp_file) if curlink == newlink: result = '' else: result = "Link targets differ for [%s]: actual: [%s], expected: [%s]\n" % (path, curlink, newlink) except OSError: e = sys.exc_info()[1] if e.errno == 22: result = "Deployed symlink is no longer a symlink!" else: raise e else: result = ''.join(diff(temp_file, path, display_diff=get_config('display_diff'), is_binary=False if file_struct['is_binary'] == 'N' else True)) if temp_file: os.unlink(temp_file) return owner_result + group_result + perm_result + sectx_result + result def _validate_struct(self, file_struct): for k in self.file_struct_fields.keys(): if k not in file_struct: # XXX raise Exception("Missing key %s" % k) def diff(src, dst, srcname=None, dstname=None, display_diff=False, is_binary=False): def f_content(path, name, is_binary): statinfo = None if os.access(path, os.R_OK): f = open(path, ('r' if int(sys.version[0]) == 3 else 'U') + ('b' if is_binary else '')) content = [sstr(i) for i in f.readlines()] f.close() statinfo = os.stat(path) f_time = time.ctime(statinfo.st_mtime) if not is_binary and content and content[-1] and content[-1][-1] != "\n": content[-1] += "\n" else: content = [] f_time = time.ctime(0) if not name: name = path if is_binary: return (str(content), name, f_time, statinfo) return (content, name, f_time, statinfo) (src_content, src_name, src_time, src_stat) = f_content(src, srcname, is_binary) (dst_content, dst_name, dst_time, dst_stat) = f_content(dst, dstname, is_binary) diff_u = difflib.unified_diff(src_content, dst_content, src_name, dst_name, src_time, dst_time) ret_list = list(diff_u) # don't return the diff if the file is not readable by everyone # for security reasons. if (len(ret_list) > 0 # if differences exist and not display_diff # and we have not explicitly decided to display and (dst_stat == None # file is not there or not readable to root or (dst_stat.st_uid == 0 # file is owned by root and not dst_stat.st_mode & stat.S_IROTH))): # not read-all ret_list = [ "Differences exist in a file %s that is not readable by all. " % dst, "Re-deployment of configuration file is recommended."] return ret_list def maketemp(prefix=None, directory=None): """Creates a temporary file (guaranteed to be new), using the specified prefix. Returns the filename and a file descriptor """ if not directory: directory = tempfile.gettempdir() dirs_created = None if not os.path.exists(directory): dirs_created = utils.mkdir_p(directory) if not prefix: # Create the file in /tmp by default prefix = 'rhncfg-tempfile' file_prefix = "%s-%s-" % (prefix, os.getpid()) (fd, filename) = tempfile.mkstemp(prefix=file_prefix, dir=directory) return filename, dirs_created, fd # Duplicated from backend/common/fileutils.py to remove dependency requirement. # If making changes make them there too. FILETYPE2CHAR = { 'file' : '-', 'directory' : 'd', 'symlink' : 'l', 'chardev' : 'c', 'blockdev' : 'b', } # Duplicated from backend/common/fileutils.py to remove dependency requirement. # If making changes make them there too. def _ifelse(cond, thenval, elseval): if cond: return thenval else: return elseval # Duplicated from backend/common/fileutils.py to remove dependency requirement. # If making changes make them there too. def ostr_to_sym(octstr, ftype): """ Convert filemode in octets (like '644') to string like "ls -l" ("-rwxrw-rw-") ftype is one of: file, directory, symlink, chardev, blockdev. """ mode = int(str(octstr), 8) symstr = FILETYPE2CHAR.get(ftype, '?') symstr += _ifelse(mode & stat.S_IRUSR, 'r', '-') symstr += _ifelse(mode & stat.S_IWUSR, 'w', '-') symstr += _ifelse(mode & stat.S_IXUSR, _ifelse(mode & stat.S_ISUID, 's', 'x'), _ifelse(mode & stat.S_ISUID, 'S', '-')) symstr += _ifelse(mode & stat.S_IRGRP, 'r', '-') symstr += _ifelse(mode & stat.S_IWGRP, 'w', '-') symstr += _ifelse(mode & stat.S_IXGRP, _ifelse(mode & stat.S_ISGID, 's', 'x'), _ifelse(mode & stat.S_ISGID, 'S', '-')) symstr += _ifelse(mode & stat.S_IROTH, 'r', '-') symstr += _ifelse(mode & stat.S_IWOTH, 'w', '-') symstr += _ifelse(mode & stat.S_IXOTH, _ifelse(mode & stat.S_ISVTX, 't', 'x'), _ifelse(mode & stat.S_ISVTX, 'T', '-')) return symstr # Duplicated from backend/common/fileutils.py to remove dependency requirement. # If making changes make them there too. def f_date(dbiDate): return "%04d-%02d-%02d %02d:%02d:%02d" % (dbiDate.year, dbiDate.month, dbiDate.day, dbiDate.hour, dbiDate.minute, dbiDate.second) def xmlrpc_time(xtime): if xtime[8] == 'T': # oracle backend: 20130304T23:19:17 timefmt='%Y%m%dT%H:%M:%S' else: # postresql backend format: 2014-02-28 18:47:31.506953+01:00 timefmt='%Y-%m-%d %H:%M:%S' xtime = xtime[:19] return time.strptime(xtime, timefmt) 07070100000028000081B40000000000000000000000015CDC5C8900000C3D000000000000000000000000000000000000002600000000mgr-cfg/config_common/handler_base.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 getpass from optparse import OptionParser, Option from config_common import rhn_log from config_common import cfg_exceptions from config_common import local_config from rhn.i18n import bstr class HandlerBase: _options_table = [] _option_parser_class = OptionParser _usage_options = "[options]" _option_class = Option def __init__(self, args, repository, mode=None, exec_name=None): self.repository = repository self.set_mode(mode) self.set_exec_name(exec_name) self.options, self.args = self._parse_args(args) def set_mode(self, mode): self.mode = mode def set_exec_name(self, exec_name): self.exec_name = exec_name def _prog(self): return "%s %s" % (sys.argv[0], self.mode or "<unknown>") def _parse_args(self, args): # Parses the arguments and returns a tuple (options, args) usage = " ".join(["%prog", self.mode, self._usage_options]) self._parser = self._option_parser_class( option_list=self._options_table, usage=usage) return self._parser.parse_args(args) def usage(self): return self._parser.print_help() def authenticate(self, username=None, password=None): # entry point for repository authentication try: self.repository.login() except cfg_exceptions.InvalidSession: if not username : username=local_config.get('username') if not password : password=local_config.get('password') if not password : (username, password) = self.get_auth_info(username) try: self.repository.login(username=username, password=password) except cfg_exceptions.InvalidSession: e = sys.exc_info()[1] rhn_log.die(1, "Session error: %s\n" % e) def get_auth_info(self, username=None): if username is None: username = self._read_username() password = getpass.getpass() return (username, password) def _read_username(self): tty = open("/dev/tty", "rb+", buffering=0) tty.write(bstr("SUSE Manager username: ")) try: username = tty.readline() except KeyboardInterrupt: tty.write(bstr("\n")) sys.exit(0) if username is None: # EOF tty.write(bstr("\n")) sys.exit(0) return username.strip() 07070100000029000081B40000000000000000000000015CDC5C890000117C000000000000000000000000000000000000002600000000mgr-cfg/config_common/local_config.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 os import sys # python2 try: import ConfigParser except: #python3 import configparser as ConfigParser from config_common import utils class rhncfgConfigParser(ConfigParser.ConfigParser): _local_config_file_name = '.rhncfgrc' _instance = None def __init__(self, section, defaults=None, config_file_override=None): """defaults is either None, or a dictionary of default values which can be overridden""" if defaults: for (k, v) in defaults.items(): if type(v) == int: defaults[k] = str(v) ConfigParser.ConfigParser.__init__(self, defaults) self.section = section self.overrides = {} self.mydefaults = self.defaults() self.config_file_override = config_file_override def read_config_files(self, overrides={}): self.overrides.clear() self.overrides.update(overrides) try: self.read(self._get_config_files()) except ConfigParser.MissingSectionHeaderError: e = sys.exc_info()[1] print("Config error: line %s, file %s: %s" % (e.lineno, e.filename, e)) sys.exit(1) def _get_config_files(self): if sys.platform.find('sunos') > -1: default_config_file = "/opt/redhat/rhn/solaris/etc/sysconfig/rhn/%s.conf" % self.section else: default_config_file = "/etc/sysconfig/rhn/%s.conf" % self.section config_file_list = [ default_config_file, os.path.join(utils.get_home_dir(), self._local_config_file_name), self._local_config_file_name, ] if self.config_file_override: config_file_list.append(self.config_file_override) return config_file_list def get_option(self, option): #6/29/05 wregglej 152388 # server_list is always in the defaults, never in the rhncfg config file. It's formed when there # are more than one server in up2date's serverURL setting. if option == 'server_list': if 'server_list' in self.mydefaults: if type(self.mydefaults['server_list']) is type([]): return self.mydefaults['server_list'] try: ret = self.get(self.section, option, vars=self.overrides) #5/25/05 wregglej - 158694 #Move the cast to an int to here from the up2date_config_parser, that way the stuff that needs to get interpolated #gets interpolated, the stuff that should be an int ends up and int, and the stuff that's neither doesn't get #messed with. try: if type(ret) != type([]): ret = int(ret) except ValueError: pass return ret except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass defaults = self.defaults() return defaults.get(option) def keys(self): return self.options(self.section) def __getitem__(self, item): return self.get_option(item) def init(section, defaults=None, config_file_override=None, **overrides): cp = rhncfgConfigParser._instance = rhncfgConfigParser(section, defaults, config_file_override=config_file_override) cp.read_config_files(overrides) def get(var): return _get_config().get_option(var) def _get_config(): if rhncfgConfigParser._instance is None: raise ValueError("Configuration not initialized") return rhncfgConfigParser._instance def instance(): return _get_config() def keys(): return list(_get_config().keys()) def main(): init('rhncfgcli') print("repository: %s" % get("repository")) print("useGPG: %s" % get("useGPG")) print("serverURL: %s" % get("serverURL")) if __name__ == '__main__': main() 0707010000002A000081B40000000000000000000000015CDC5C8900003D32000000000000000000000000000000000000002400000000mgr-cfg/config_common/repository.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 re import os import pwd import grp import sys import stat import base64 from config_common import cfg_exceptions from config_common import local_config from config_common import utils from config_common.rhn_log import log_debug from rhn import rpclib Output = rpclib.transports.Output try: # python2 PY3 = False import xmlrpclib except ImportError: # python3 PY3 = True import xmlrpc.client as xmlrpclib basestring = (str, bytes) from spacewalk.common.usix import raise_with_tb try: from selinux import lgetfilecon except: # on rhel4 we do not support selinux def lgetfilecon(path): return [0, ''] #6/29/05 rpc_wrapper implements the failover logic. from config_common import rpc_wrapper rpclib = rpc_wrapper def deci_to_octal(number): """convert a normal decimal int to another int representing the octal value""" # python 2.4/2.6: oct(420) -> '0644' # python 3.4: oct(420) -> '0o644' return int(oct(number).replace("o", "")) class Repository: _uid_cache = {} _gid_cache = {} _local_config = local_config def __init__(self): self.default_delimiters = None self.maximum_file_size = None # Helpers # Unless overridden in a subclass, per-file delimiters are the same as the # global delimiters def get_file_delimiters(self, file): "returns the default delimiters for this file" return self.get_default_delimiters() def get_default_delimiters(self): "returns the default delimiters" if self.default_delimiters is None: self.default_delimiters = self._get_default_delimiters() return self.default_delimiters def _get_default_delimiters(self): raise NotImplementedError def get_maximum_file_size(self): "returns the maximum file size" if self.maximum_file_size is None: self.maximum_file_size = self._get_maximum_file_size() return self.maximum_file_size def _get_maximum_file_size(self): "To be overwritten in subclasses" return 1024 def make_stat_info(self, path, file_stat): # Returns the stat information as required by the API ret = {} fields = { 'mode' : stat.ST_MODE, 'user' : stat.ST_UID, 'group' : stat.ST_GID, 'size' : stat.ST_SIZE, 'mtime' : stat.ST_MTIME, 'ctime' : stat.ST_CTIME, } for label, st in fields.items(): ret[label] = file_stat[st] # server expects things like 644, 700, etc. ret['mode'] = deci_to_octal(ret['mode'] & int('07777', 8)) #print ret['size'] #if ret['size'] > self.get_maximum_file_size(): # die(4, "File %s exceeds the maximum file size (%s)" % # (path, ret['size'])) uid = ret['user'] gid = ret['group'] pw_name = self._uid_cache.get(uid) if not pw_name: try: pw_name = pwd.getpwuid(uid)[0] except KeyError: print("Error looking up user id %s" % (uid, )) if pw_name: ret['user'] = pw_name self._uid_cache[uid] = pw_name gr_name = self._gid_cache.get(gid) if not gr_name: try: gr_name = grp.getgrgid(gid)[0] except KeyError: print("Error looking up group id %s" % (gid, )) if gr_name: ret['group'] = gr_name self._gid_cache[gid] = gr_name # if selinux is disabled or on RHEL4 we do not send the selinux_ctx # flag at all - see bug 644985 - SELinux context cleared from # RHEL4 rhncfg-client try: selinux_ctx = lgetfilecon(path)[1] except OSError: selinux_ctx = '' if selinux_ctx == None: selinux_ctx = '' ret['selinux_ctx'] = selinux_ctx return ret def _make_file_info(self, remote_path, local_path=None, delim_start=None, delim_end=None, load_contents=1): if not local_path: # Safe enough to assume local path is the same as the remote one local_path = remote_path try: file_stat = os.lstat(local_path) except OSError: e = sys.exc_info()[1] raise_with_tb(cfg_exceptions.RepositoryLocalFileError( "Error lstat()-ing local file: %s" % e), sys.exc_info()[2]) # Dlimiters if delim_start or delim_end: if not (delim_start and delim_end): # If only one delimiter is provided, assume the delimiters are # the same, whatever that is (or is nice) delim_start = delim_end = (delim_start or delim_end) else: # Use the default delim_start, delim_end = self.get_file_delimiters(remote_path) params = { 'path' : remote_path, 'delim_start' : delim_start, 'delim_end' : delim_end, } file_contents = None if os.path.islink(local_path): params['config_file_type_id'] = 3 params['symlink'] = os.readlink(local_path) load_contents = 0 elif os.path.isdir(local_path): params['config_file_type_id'] = 2 load_contents = 0 else: params['config_file_type_id'] = 1 if load_contents: try: file_contents = open(local_path, "rb").read() except IOError: e = sys.exc_info()[1] raise_with_tb(cfg_exceptions.RepositoryLocalFileError( "Error opening local file: %s" % e), sys.exc_info()[2]) self._add_content(file_contents, params) params.update(self.make_stat_info(local_path, file_stat)) return params def _add_content(self, file_contents, params): """Add the file contents to the params hash""" params['enc64'] = 1 params['file_contents'] = base64.encodestring(file_contents) def login(self, username=None, password=None): pass class RPC_Repository(Repository): def __init__(self, setup_network=1): Repository.__init__(self) # all this so needs to be in a seperate rhnConfig library, # shared by up2date, rhncfg*, etc. # # But, I digress. self.__server_url = self._local_config.get('server_url') # 6/29/05 wregglej 152388 # server_list contains the list of servers to failover to. self.__server_list = self._local_config.get('server_list') # 6/29/05 wregglej 152388 # Grab server_handler, which is different for rhncfg-client and rhncfg-manager # and is needed when failover occurs. During a failover, when the server object is # being set up to use a new satellite, the server_handler is added to the address so # the tool communicates with the correct xmlrpc handler. handler = self._local_config.get('server_handler') cap_handler = re.sub('[^/]+$', 'XMLRPC', handler) if not self.__server_url: raise cfg_exceptions.ConfigurationError( "Missing entry 'server_url' in the config files\n" \ "Try running as root, or configure server_url as described in the configuration file" ) log_debug(3, "server url", self.__server_url) self.__proxy_user = None self.__proxy_password = None self.__proxy_host = None self.__enable_proxy = self._local_config.get('enableProxy') self.__enable_proxy_auth = self._local_config.get('enableProxyAuth') if self.__enable_proxy: self.__proxy_host = self._local_config.get('httpProxy') if self.__enable_proxy_auth: self.__proxy_user = self._local_config.get('proxyUser') self.__proxy_password = self._local_config.get('proxyPassword') ca = self._local_config.get('sslCACert') if isinstance(ca, basestring): ca = [ca] ca_certs = ca or ["/usr/share/rhn/RHNS-CA-CERT"] # not sure if we need this or not... lang = None for env in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG': if env in os.environ: if not os.environ[env]: # sometimes unset continue lang = os.environ[env].split(':')[0] lang = lang.split('.')[0] break if setup_network: # Fetch server capabilities - we need the /XMLRPC handler #t = list(utils.parse_url(self.__server_url)) #t[2] = '/XMLRPC' #x_server_url = utils.unparse_url(t) # 6/29/05 wregglej 152388 # Fetching the server capabilities involves using the /XMLRPC handler. It's # the only place that I know of that does that. The server_url and server_list # both need to have /XMLRPC on the ends, which is what _patch_uris() does by default. x_server_url = self._patch_uris(self.__server_url, cap_handler) if self.__server_list != None: x_server_list = self._patch_uris(self.__server_list, cap_handler) else: x_server_list = None x_server = rpclib.Server(x_server_url, proxy=self.__proxy_host, username=self.__proxy_user, password=self.__proxy_password, server_list=x_server_list, rpc_handler="/XMLRPC") # Make a call to a function that can export the server's capabilities # without setting any state on the server side try: x_server.registration.welcome_message() except xmlrpclib.Fault: e = sys.exc_info()[1] sys.stderr.write("XML-RPC error while talking to %s:\n %s\n" % (self.__server_url, e)) sys.exit(2) self._server_capabilities = get_server_capability(x_server) del x_server # 6/29/05 wregglej 152388 # From here on out all communication should take place through the xmlrpc handler # that's appropriate for the tool being used. For rhncfg-client that's /CONFIG-MANAGEMENT. # For rhncfg-manager that's /CONFIG-MANAGEMENT-TOOL. No, I don't know the reasoning behind that. # First we need to patch the uris in server_list, to use the correct handler. self.__server_url = self._patch_uris(self.__server_url, handler) if self.__server_list != None: self.__server_list = self._patch_uris(self.__server_list, handler) else: self.__server_list = None self.server = rpclib.Server(self.__server_url, proxy=self.__proxy_host, username=self.__proxy_user, password=self.__proxy_password, server_list=self.__server_list, rpc_handler=handler) self._set_capabilities() self.server.set_transport_flags( transfer=Output.TRANSFER_BINARY, encoding=Output.ENCODE_GZIP ) if lang: self.server.setlang(lang) for ca_cert in ca_certs: if not os.access(ca_cert, os.R_OK): raise cfg_exceptions.ConfigurationError("Can not find CA file: %s" % ca_cert) log_debug(3, "ca cert", ca_cert) # force the validation of the SSL cert self.server.add_trusted_cert(ca_cert) # 6/29/05 wregglej 152388 # Places handler at the end of the uri. # uris can be either a uri string or a list of uri strings. def _patch_uris(self, uris, handler="/XMLRPC"): #Handles patching the uris when they're in a list. if type(uris) == type([]): ret = [] for i in range(len(uris)): t = list(utils.parse_url(uris[i])) t[2] = handler ret.append(utils.unparse_url(t)) #Handles patching the uri when it's a string. else: t = list(utils.parse_url(uris)) t[2] = handler ret = utils.unparse_url(t) return ret def _set_capabilities(self): # list of client capabilities capabilities = { 'configfiles.base64_enc' : {'version' : 1, 'value' : 1}, 'rhncfg.dirs_enabled' : {'version' : 1, 'value' : 1}, } for name, hashval in capabilities.items(): cap = "%s(%s)=%s" % (name, hashval['version'], hashval['value']) self.server.add_header("X-RHN-Client-Capability", cap) def rpc_call(self, method_name, *params): method = getattr(self.server, method_name) try: result = method(*params) except xmlrpclib.ProtocolError: e = sys.exc_info()[1] sys.stderr.write("XML-RPC call error: %s\n" % e) sys.exit(1) except xmlrpclib.Fault: # Re-raise them raise except Exception: e = sys.exc_info()[1] sys.stderr.write("XML-RPC error while talking to %s: %s\n" % ( self.__server_url, e)) sys.exit(2) return result def _get_maximum_file_size(self): return self.rpc_call('config.max_upload_fsize') def _add_content(self, file_contents, params): """Add the file contents to the params hash""" # check for the rhncfg.content.base64_decode capability and encode the # data if the server is capable of descoding it if 'rhncfg.content.base64_decode' in self._server_capabilities: params['enc64'] = 1 params['file_contents'] = base64.encodestring(file_contents) else: params['file_contents'] = file_contents return params def get_server_capability(s): headers = s.get_response_headers() if headers is None: # No request done yet return {} if PY3: cap_headers = ["X-RHN-Server-Capability: %s" % val for val in headers.get_all("X-RHN-Server-Capability")] else: cap_headers = headers.getallmatchingheaders("X-RHN-Server-Capability") if not cap_headers: return {} regexp = re.compile( r"^(?P<name>[^(]*)\((?P<version>[^)]*)\)\s*=\s*(?P<value>.*)$") vals = {} for h in cap_headers: arr = h.split(':', 1) assert len(arr) == 2 val = arr[1].strip() if not val: continue mo = regexp.match(val) if not mo: # XXX Just ignoring it, for now continue vdict = mo.groupdict() for k, v in vdict.items(): vdict[k] = v.strip() vals[vdict['name']] = vdict return vals 0707010000002B000081B40000000000000000000000015CDC5C8900000EC9000000000000000000000000000000000000002100000000mgr-cfg/config_common/rhn_log.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 os import stat import sys import time import traceback class Logger: debug_level = 1 logfile = None def log_debug(self, debug_level, *args): if debug_level <= self.debug_level: outstr = "%s %s: %s\n" % ( time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), self.get_caller(), " ".join([str(it) for it in args])) sys.stdout.write(outstr) if not self.logfile is None: self.write_to_logfile(outstr) def log_to_file(self, debug_level, *args): if debug_level <= self.debug_level: outstr = "%s %s: %s\n" % ( time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), self.get_caller(), " ".join([str(it) for it in args])) if not self.logfile is None: self.write_to_logfile(outstr) def write_to_logfile(self, logstr): if os.access(self.logfile, os.F_OK|os.R_OK|os.W_OK): logname = open(self.logfile, "a") logname.write(logstr) logname.close() else: #pkilambi: bug#179367: check permissions before writing. #non-root users will not have permissions to create the file # Set to root-RW-only if we have to create the file mode = stat.S_IRUSR | stat.S_IWUSR # octal 0o600 try: # Need to use os.open to insure initial permissions fd = os.open(self.logfile, os.O_WRONLY | os.O_CREAT, mode) logname = os.fdopen(fd, 'w') logname.write(logstr) logname.close() except: print("does not have permissions to create file %s" % (self.logfile)) sys.exit(1) def set_logfile(self, filename): Logger.logfile = filename def set_debug_level(self, debug_level): Logger.debug_level = debug_level def get_debug_level(self): return Logger.debug_level def get_caller(self, caller_offset=4): tbStack = traceback.extract_stack() callid = len(tbStack) - caller_offset module = tbStack[callid] module_file = os.path.basename(module[0]) module_file = module_file.split('.', 1)[0] return "%s.%s" % (module_file, module[2]) def log_error(self, *args): outstr = " ".join([str(it) for it in args]) sys.stderr.write(outstr) sys.stderr.write("\n") if not self.logfile is None: self.write_to_logfile(outstr) def die(self, error_code, *args): self.log_error(args) sys.exit(error_code) def set_debug_level(*args): return Logger().set_debug_level(*args) def get_debug_level(*args): return Logger().get_debug_level(*args) def set_logfile(*args): return Logger().set_logfile(*args) def log_debug(*args): return Logger().log_debug(*args) def log_to_file(*args): return Logger().log_to_file(*args) def log_error(*args): return Logger().log_error(*args) def die(error_code, *args): Logger().log_error(*args) sys.exit(error_code) 0707010000002C000081B40000000000000000000000015CDC5C890000208E000000000000000000000000000000000000002200000000mgr-cfg/config_common/rhn_main.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 os import sys from config_common import local_config from config_common import rhn_log from config_common import utils from config_common import cfg_exceptions try: # python3 from urllib.parse import urlsplit from configparser import InterpolationError except ImportError: #python2 from urlparse import urlsplit from ConfigParser import InterpolationError try: from socket import gaierror except: from socket import error gaierror = error from up2date_client import config class BaseMain: modes = [] repository_class_name = "Repository" plugins_dir = 'config_common' mode_prefix = None config_section = None def usage(self, exit_code): print("Usage: %s MODE [ --config config_file ] [ --server-name name ] [ params ]" % sys.argv[0]) print("Valid modes are:") for mode in self.modes: print("\t%s" % mode) sys.exit(exit_code) def main(self): args = [] show_help = None debug_level = 3 mode = None dict_name_opt={'--server-name': None,'--password': None,'--username': None, '--config': None,} for index in range(1,len(sys.argv)): arg=sys.argv[index] param = [x for x in dict_name_opt.items() if x[1] == 0] if param: if arg.startswith('-') or arg in self.modes: # not perfect, but at least a little bit better print("Option %s requires an argument" % dict_name_opt[param[0][0]]) return 1 dict_name_opt[param[0][0]] = arg continue if arg in ('--help', '-h'): show_help = 1 continue param = [s for s in dict_name_opt.keys() if arg.startswith(s)] if param: rarg = arg[len(param[0]):] if not rarg: dict_name_opt[param[0]] = 0 if index == len(sys.argv) - 1: print("Option %s requires an argument" % param[0]) return 1 continue if rarg[0] == '=': if len(rarg) == 1: print("Option %s requires an argument" % param[0]) return 1 dict_name_opt[param[0]] = rarg[1:] continue print("Unknown option %s" % arg) return 1 if mode is None: # This should be the mode mode = arg if mode == '': # Bad self.usage(1) if mode[0] == '-': # Mode can't be an option self.usage(1) if mode not in self.modes: print("Unknown mode %s" % mode) self.usage(1) continue args.append(arg) server_name = dict_name_opt['--server-name'] password = dict_name_opt['--password'] username = dict_name_opt['--username'] config_file_override = dict_name_opt['--config'] if config_file_override and not os.path.isfile(config_file_override): rhn_log.die(1, "Config file %s does not exist.", config_file_override) rhn_log.set_debug_level(debug_level) if mode is None: # No args were specified self.usage(0) execname = os.path.basename(sys.argv[0]) # Class names cannot have dot in them, so strip the extension execname = execname.split('.', 1)[0] mode_module = mode.replace('-', '_') module_name = "%s_%s" % (self.mode_prefix, mode_module) full_module_name = "%s.%s" % (self.plugins_dir, module_name) try: module = __import__(full_module_name) except ImportError: e = sys.exc_info()[1] rhn_log.die(1, "Unable to load plugin for mode '%s': %s" % (mode, e)) module = getattr(module, module_name) if show_help: # Display the per-module help handler = module.Handler(args, None, mode=mode, exec_name=execname) handler.usage() return 0 cfg = config.initUp2dateConfig() up2date_cfg = dict(cfg.items()) if server_name: local_config.init(self.config_section, defaults=up2date_cfg, config_file_override=config_file_override, server_url="https://" + server_name) else: local_config.init(self.config_section, defaults=up2date_cfg, config_file_override=config_file_override) try: server_name = local_config.get('server_url') except InterpolationError: e = sys.exc_info()[1] if e.option == 'server_url': server_name = config.getServerlURL() up2date_cfg['proto'] = urlsplit(server_name[0])[0] if up2date_cfg['proto'] == '': up2date_cfg['proto'] = 'https' up2date_cfg['server_list'] = [urlsplit(x)[2] for x in server_name] else: up2date_cfg['server_list'] = [urlsplit(x)[1] for x in server_name] server_name = (up2date_cfg['server_list'])[0] local_config.init(self.config_section, defaults=up2date_cfg, config_file_override=config_file_override, server_name=server_name) print("Using server name %s" % server_name) # set the debug level through the config rhn_log.set_debug_level(int(local_config.get('debug_level') or 0)) rhn_log.set_logfile(local_config.get('logfile') or "/var/log/rhncfg") # Multi-level import - __import__("foo.bar") does not return the bar # module, but the foo module with bar loaded # XXX Error checking repo_class = local_config.get('repository_type') if repo_class is None: rhn_log.die(1, "repository_type not set (missing configuration file?)") repo_module_name = "%s.%s" % (self.plugins_dir, repo_class) try: repo_module = __import__(repo_module_name) except ImportError: e = sys.exc_info()[1] rhn_log.die(1, "Unable to load repository module: %s" % e) try: repo_module = getattr(repo_module, repo_class) except AttributeError: rhn_log.die(1, "Malformed repository module") try: repo = getattr(repo_module, self.repository_class_name)() except AttributeError: rhn_log.die(1, "Missing repository class") except InterpolationError: e = sys.exc_info()[1] if e.option == 'server_url': #pkilambi: bug#179367# backtic is replaced with single quote rhn_log.die(1, "Missing 'server_url' configuration variable; please refer to the config file") raise except cfg_exceptions.ConfigurationError: e = sys.exc_info()[1] rhn_log.die(e) except gaierror: e = sys.exc_info()[1] print("Socket Error: %s" % (e.args[1],)) sys.exit(1) handler = module.Handler(args, repo, mode=mode, exec_name=execname) try: try: handler.authenticate(username, password) handler.run() except cfg_exceptions.AuthenticationError: e = sys.exc_info()[1] rhn_log.die(1, "Authentication failed: %s" % e) except Exception: raise finally: repo.cleanup() return 0 0707010000002D000081B40000000000000000000000015CDC5C890000231C000000000000000000000000000000000000002500000000mgr-cfg/config_common/rpc_wrapper.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 from rhn import rpclib from spacewalk.common.usix import raise_with_tb try: from socket import error, sslerror, herror, gaierror, timeout except ImportError: from socket import error sslerror = error herror = error gaierror = error timeout = error try: # python2 import xmlrpclib import urllib except ImportError: # python3 import xmlrpc.client as xmlrpclib import urllib.parse as urllib #This is raised when the failover stuff has gone through every server in the server list #and the error is still occurring. class NoMoreServers(Exception): pass #This was supposed to be a wrapper that contained a reference to a rpclib.Server object and delegated calls to it, #but it turns out that I needed to override _request() to make sure that all #communication errors are caught and handled here, so I changed it to be a subclass of rpclib.Server. #The problem that spurred that was when a xmlrpc function was called on an object that was an #attribute of the server class it wasn't passing through the _request that I had written here, #it was going directly to rpclib.Server's _request() and missing all of the failover logic that I had added. #The short version is that I needed to make sure this class was in the inheritance hierarchy. class Server(rpclib.Server): def __init__(self, uri, transport=None, encoding=None, verbose=0, proxy=None, username=None, password=None, refreshCallback=None, progressCallback=None, server_list=None, rpc_handler=None): self.list_of_uris = None #Contains all of the possible uris. self.current_index = 0 #index of the uri that we're currently using. #If server_list is None, then no failover systems were listed in the up2date config and #we need to use the one that was put together in the rhncfg-* config, which was passed in as #uri. if server_list is None: if type(uri) == type([]): self.list_of_uris = uri else: self.list_of_uris = [uri] else: #If the server_url passed in is the same as the first element of the server_list, then #that means all of the server info came from the up2date config (or is the same as the up2date config) #and we should use the server_list. #If they don't match then we should use the server_url passed in as uri, because it's a specific setting from #the rhncfg-*.conf file. if uri == server_list[0]: self.list_of_uris = server_list else: self.list_of_uris = [uri] self.rpc_handler = rpc_handler #Grabs the initial uri that we're going to use. init_uri = self._get_uri() #self.rpc_args = { # 'transport' : transport, # 'encoding' : encoding, # 'verbose' : verbose, # 'proxy' : proxy, # 'username' : username, # 'password' : password, # 'refreshCallback' : refreshCallback, # 'progressCallback' : progressCallback, # } #Set up the rpclib.Server stuff with the first uri. rpclib.Server.__init__(self, init_uri, transport=transport, encoding=encoding, verbose=verbose,\ proxy=proxy, username=username, password=password, refreshCallback=refreshCallback,\ progressCallback=progressCallback) #Return the uri that we should be using. def _get_uri(self): return self.list_of_uris[self.current_index] #Returns the list of uris that could be used. def get_uri_list(self): return self.list_of_uris #This is called when we failover. It re-inits the server object to use the new uri. Most of this was cribbed from #alikins' wrapper that does a similar thing for up2date. def init_server(self, myuri): #Borrowed the following from rpcServer.py #rpclib.Server.__init__(self, uri, transport=self.rpc_args['transport'], encoding=self.rpc_args['encoding'], verbose=self.rpc_args['verbose'],\ # proxy=self.rpc_args['proxy'], username=self.rpc_args['username'],\ # password=self.rpc_args['password'], refreshCallback=self.rpc_args['refreshCallback'],\ # progressCallback=self.rpc_args['progressCallback']) self._uri = myuri typ, uri = urllib.splittype(self._uri) typ = typ.lower() if typ not in ("http", "https"): raise InvalidRedirectionError( "Redirected to unsupported protocol %s" % typ) self._host, self._handler = urllib.splithost(uri) self._orig_handler = self._handler self._type = typ if not self._handler: self._handler = self.rpc_handler self._allow_redirect = 1 del self._transport self._transport = self.default_transport(typ, self._proxy, self._username, self._password) self.set_progress_callback(self._progressCallback) self.set_refresh_callback(self._refreshCallback) self.set_buffer_size(self._bufferSize) self.setlang(self._lang) if self._trusted_cert_files != [] and \ hasattr(self._transport, "add_trusted_cert"): for certfile in self._trusted_cert_files: self._transport.add_trusted_cert(certfile) #This is the logic for switching to a new server resides. def _failover(self): #The print statements are from alikins rpcServer.py. msg = "An error occurred talking to %s:\n" % self._get_uri() msg = msg + "%s\n%s\n" % (sys.exc_info()[0], sys.exc_info()[1]) #Increments the index to point to the next server in self.list_of_uris self.current_index = self.current_index + 1 #Make sure we don't try to go past the end of the list. if self.current_index > (len(self.list_of_uris) - 1): raise NoMoreServers() else: failover_uri = self._get_uri() #Grab the uri of the new server to use. msg = msg + "Trying the next serverURL: %s\n" % failover_uri print(msg) #Set up rpclib.Server to use the new uri. self.init_server(failover_uri) #This is where the magic happens. function is a function reference, arglist is the list of arguements #that get passed to the function and kwargs is the list of named arguments that get passed into the function. #I used apply() here so it will work with Python 1.5, which doesn't have the extended call syntax. def _call_function(self, function, arglist, kwargs={}): succeed = 0 while succeed == 0: try: ret = function(*arglist, **kwargs) except rpclib.InvalidRedirectionError: raise except xmlrpclib.Fault: e = sys.exc_info()[1] save_traceback = sys.exc_info()[2] try: self._failover() except NoMoreServers: f = sys.exc_info()[1] raise_with_tb(e, save_traceback) #Don't raise the NoMoreServers error, raise the error that triggered the failover. continue except (error, sslerror, herror, gaierror, timeout): e = sys.exc_info()[1] save_traceback = sys.exc_info()[2] try: self._failover() except NoMoreServers: raise_with_tb(e, save_traceback) continue succeed = 1 #If we get here then the function call eventually succeeded and we don't need to try again. return ret def _request(self, methodname, params): return self._call_function(rpclib.Server._request, (self, methodname, params)) def __getattr__(self, name): return rpclib.xmlrpclib._Method(self._request, name) 0707010000002E000081B40000000000000000000000015CDC5C890000436C000000000000000000000000000000000000002600000000mgr-cfg/config_common/transactions.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 shutil import pwd import grp import sys import errno import shutil from config_common import file_utils, utils, cfg_exceptions from config_common.rhn_log import log_debug from spacewalk.common.usix import raise_with_tb class TargetNotFile(Exception): pass class DuplicateDeployment(Exception): pass class FailedRollback(Exception): pass try: from selinux import lsetfilecon except: def lsetfilecon(temp_file_path, sectx): # Do nothing RHEL 4 return 0 BACKUP_PREFIX = '/var/lib/rhncfg/backups' BACKUP_EXTENSION = '.rhn-cfg-backup' class DeployTransaction: def __init__(self, transaction_root=None, auto_rollback=0): # rollback transaction immediately upon failure? self.auto_rollback = auto_rollback # prepend all given paths self.transaction_root = transaction_root self.files = [] self.dirs = [] self.symlinks = [] self.new_dirs = [] self.backup_by_path = {} self.newtemp_by_path = {} self.changed_dir_info = {} self.deployment_cb = None def _generate_backup_path(self, path): return "%s%s%s" % (BACKUP_PREFIX, path, BACKUP_EXTENSION) def _rename_to_backup(self, path): """renames a file to it's new backup name""" # ensure we haven't attempted to back this file up before # (protect against odd logic coming from the server) if path in self.backup_by_path: raise DuplicateDeployment("Error: attempted to backup %s twice" % path) new_path = None if os.path.exists(path): # race if os.path.isfile(path) or os.path.islink(path): new_path = self._generate_backup_path(path) log_debug(6, "renaming %s to backup %s ..." % (path, new_path)) # os.renames will fail if the path and the new_path are on different partitions # need to make sure to handle it if we catch a 'OSError: [Errno 18] Invalid cross-device link' try: log_debug(9, "trying to use os.renames") oumask = os.umask(int('022', 8)) os.renames(path, new_path) os.umask(oumask) except OSError: e = sys.exc_info()[1] if e.errno == 18: log_debug(9, "os.renames failed, using shutil functions") path_dir, path_file = os.path.split(path) new_path_dir, new_path_file = os.path.split(new_path) if os.path.isdir(new_path_dir): if os.path.islink(path): log_debug(9, "copying symlink %s to %s"% (path,new_path_dir)) linkto = os.readlink(path) if os.path.lexists(new_path): log_debug(9, "backup %s exists, removing it"% (new_path)) os.unlink(new_path) os.symlink(linkto,new_path) else: log_debug(9, "backup directory %s exists, copying %s to it" % (new_path_dir, new_path_file)) if os.path.lexists(new_path): log_debug(9, "backup %s exists, removing it"% (new_path)) os.unlink(new_path) shutil.copy(path, new_path) else: log_debug(9, "backup directory does not exist, creating the tree now") shutil.copytree(path_dir, new_path_dir, symlinks=0) shutil.copy(path, new_path) else: raise self.backup_by_path[path] = new_path log_debug(9, "backed up to %s" % new_path) else: raise TargetNotFile("Error: %s is not a valid file, cannot create backup copy" % path) return new_path def deploy_callback(self, cb): self.deployment_cb = cb def _chown_chmod_chcon(self, temp_file_path, dest_path, file_info, strict_ownership=1): if file_info['filetype'] != 'symlink': uid = file_info.get('uid') if uid is None: if 'username' in file_info: # determine uid try: user_record = pwd.getpwnam(file_info['username']) uid = user_record[2] except Exception: e = sys.exc_info()[1] #Check if username is an int try: uid = int(file_info['username']) except ValueError: raise_with_tb(cfg_exceptions.UserNotFound(file_info['username']), sys.exc_info()[2]) else: #default to root (3.2 sats) uid = 0 gid = file_info.get('gid') if gid is None: if 'groupname' in file_info: # determine gid try: group_record = grp.getgrnam(file_info['groupname']) gid = group_record[2] except Exception: e = sys.exc_info()[1] try: gid = int(file_info['groupname']) except ValueError: raise_with_tb(cfg_exceptions.GroupNotFound(file_info['groupname']), sys.exc_info()[2]) else: #default to root (3.2 sats) gid = 0 try: if file_info['filetype'] != 'symlink': os.chown(temp_file_path, uid, gid) mode = '600' if 'filemode' in file_info: if file_info['filemode'] is "": mode='000' else: mode = file_info['filemode'] mode = int(str(mode), 8) os.chmod(temp_file_path, mode) if 'selinux_ctx' in file_info: sectx = file_info.get('selinux_ctx') if sectx is not None and sectx is not "": log_debug(1, "selinux context: " + sectx); try: if lsetfilecon(temp_file_path, sectx) < 0: raise Exception("failed to set selinux context on %s" % dest_path) except OSError: e = sys.exc_info()[1] raise_with_tb(Exception("failed to set selinux context on %s" % dest_path, e), sys.exc_info()[2]) except OSError: e = sys.exc_info()[1] if e.errno == errno.EPERM and not strict_ownership: sys.stderr.write("cannonical file ownership and permissions lost on %s\n" % dest_path) else: raise def _normalize_path_to_root(self, path): if self.transaction_root: path = utils.normalize_path(self.transaction_root + os.sep + path) return path def add_preprocessed(self, dest_path, processed_file_path, file_info, dirs_created, strict_ownership=1): """preprocess the file if needed, and add the entry to the correct list""" dest_path = self._normalize_path_to_root(dest_path) log_debug(3, "preprocessing entry") # If we get any dirs that were created by mkdir_p, add them here if dirs_created: self.new_dirs.extend(dirs_created) # If the file is a directory, don't do all the file related work # Older servers will not return directories; if filetype is missing, # assume file if file_info.get('filetype') == 'directory': self.dirs.append(file_info) else: if "dest_path" in self.newtemp_by_path: raise DuplicateDeployment("Error: %s already added to transaction" % dest_path) self.newtemp_by_path[dest_path] = processed_file_path self._chown_chmod_chcon(processed_file_path, dest_path, file_info, strict_ownership=strict_ownership) def add(self, file_info): """add a file to the deploy transaction""" for k in file_utils.FileProcessor.file_struct_fields: if k not in file_info: raise Exception("needed key %s mising from file structure" % k) file_info['path'] = self._normalize_path_to_root(file_info['path']) # Older servers will not return directories; if filetype is missing, # assume file if file_info.get('filetype') == 'directory': self.dirs.append(file_info) elif file_info.get('filetype') == 'symlink': self.files.append(file_info) else: self.files.append(file_info) def rollback(self): """revert the transaction""" log_debug(3, "rolling back") # restore old file from backup asap for path in self.backup_by_path.keys(): log_debug(6, "restoring %s from %s ..." % (path, self.backup_by_path[path])) # os.rename will fail if the backup file and the old file are on different partitions # need to make sure to handle it if we catch a 'OSError: [Errno 18] Invalid cross-device link' try: os.rename(self.backup_by_path[path], path) except OSError: e = sys.exc_info()[1] if e.errno == 18: log_debug(9, "os.rename failed, using shutil.copy") shutil.copy(self.backup_by_path[path], path) else: raise log_debug(9, "%s restored" % path) # remove the temp files that we created for tmp_file_path in self.newtemp_by_path.values(): log_debug(6, "removing tmp file %s ..." % tmp_file_path) os.unlink(tmp_file_path) log_debug(9, "tmp file removed") #revert the owner/perms of any directories that we changed for d, val in self.changed_dir_info.items(): log_debug(6, "reverting owner and perms of %s" % d) self._chown_chmod_chcon(d, d, val) log_debug(9, "directory reverted") #remove any directories created by either mkdir_p or in the deploy self.new_dirs.reverse() for i in range(len(self.new_dirs)): remove_dir = self.new_dirs[i] log_debug(6, "removing directory %s that was created during transaction ..." % remove_dir) if os.path.islink(remove_dir) == True: os.remove(remove_dir) else: os.rmdir(remove_dir) log_debug(9, "directory removed") log_debug(3, "rollback successful") def deploy(self): """attempt deployment; will rollback if auto_rollback is set""" fp = file_utils.FileProcessor() log_debug(3, "deploying transaction") for dep_file in self.files: if dep_file['filetype'] == 'symlink': self.symlinks.append(dep_file) # 0. handle any dirs we need to create first # a) if the dir exists, then just change the mode and owners, # else create it and then make sure the mode and owners are correct. # b) if there are files, then continue # 1. write new version (tmp) # a) if anything breaks, remove all tmp versions and error out # 2. rename old version to backup # a) if anything breaks, rename all backed up files to original name, # then do 1-a. # 3. rename tmp to target name # a) if anything breaks, remove all deployed files, then do 2-a. # # (yes, this leaves the backup version on disk...) try: # 0. if self.dirs: for directory in self.dirs: dirname = self._normalize_path_to_root(directory['path']) dirmode = directory['filemode'] if os.path.isfile(dirname): raise cfg_exceptions.DirectoryEntryIsFile(dirname) if os.path.isdir(dirname): s = os.stat(dirname) entry = { 'filemode': "%o" % (s[0] & int('07777', 8)), 'uid': s[4], 'gid': s[5], 'filetype': 'directory', } self.changed_dir_info[dirname] = entry log_debug(3, "directory found, chowning and chmoding to %s as needed: %s" % (dirmode, dirname)) self._chown_chmod_chcon(dirname, dirname, directory) else: log_debug(3, "directory not found, creating: %s" % dirname) dirs_created = utils.mkdir_p(dirname, None, self.symlinks, self.files) self.new_dirs.extend(dirs_created) self._chown_chmod_chcon(dirname, dirname, directory) if self.deployment_cb: self.deployment_cb(dirname) log_debug(6, "changed_dir_info: %s" % self.changed_dir_info) log_debug(4, "new_dirs: ", self.new_dirs) if not self.newtemp_by_path and not self.files: log_debug(4, "directory creation complete, no files found to create") return else: log_debug(4, "done with directory creation, moving on to files") # 1. for dep_file in self.files: path = dep_file['path'] log_debug(6, "writing new version of %s to tmp file ..." % path) # make any directories needed... # # TODO: it'd be nice if this had a hook for letting me know # which ones are created... then i could clean created # dirs on rollback (directory, filename) = os.path.split(path) if os.path.isdir(path) and not os.path.islink(path): raise cfg_exceptions.FileEntryIsDirectory(path) if not os.path.exists(directory):# and os.path.isdir(directory): log_debug(7, "creating directories for %s ..." % directory) dirs_created = utils.mkdir_p(directory, None, self.symlinks, self.files) self.new_dirs.extend(dirs_created) log_debug(7, "directories created and added to list for rollback") # write the new contents to a tmp file, and store the path of the # new tmp file by it's eventual target path self.newtemp_by_path[path], temp_new_dirs = fp.process(dep_file, os.path.sep) self.new_dirs.extend(temp_new_dirs or []) # properly chown and chmod it self._chown_chmod_chcon(self.newtemp_by_path[path], path, dep_file) log_debug(9, "tempfile written: %s" % self.newtemp_by_path[path]) #paths = map(lambda x: x['path'], self.files) paths = list(self.newtemp_by_path.keys()) # 2. for path in paths: if os.path.isdir(path) and not os.path.islink(path): raise cfg_exceptions.FileEntryIsDirectory(path) else: self._rename_to_backup(path) if path in self.backup_by_path: log_debug(9, "backup file %s written" % self.backup_by_path[path]) # 3. paths.sort(key = lambda s: s.count(os.path.sep)) for path in paths: if self.deployment_cb: self.deployment_cb(path) log_debug(6, "deploying %s ..." % path) os.rename(self.newtemp_by_path[path], path) # race del self.newtemp_by_path[path] log_debug(9, "new version of %s deployed" % path) log_debug(3, "deploy transaction successful") except Exception: #log_debug(1, traceback.format_exception_only(SyntaxError, e)) #traceback.print_exc() if self.auto_rollback: self.rollback() raise 0707010000002F000081B40000000000000000000000015CDC5C89000016F5000000000000000000000000000000000000001F00000000mgr-cfg/config_common/utils.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 os # guaranteed to exist even on RHEL 5 because we now require python-hashlib import hashlib import re import shutil import pwd import sys try: # python2 import urlparse except ImportError: # python3 import urllib.parse as urlparse import inspect from config_common.rhn_log import log_debug from rhn.i18n import bstr hashlib_has_usedforsecurity = False if not hasattr(inspect, 'getfullargspec'): inspect.getfullargspec = inspect.getargspec if 'usedforsecurity' in inspect.getfullargspec(hashlib.new)[0]: hashlib_has_usedforsecurity = True _normpath_re = re.compile("^(%s)+" % os.sep) def normalize_path(path): """ os.path.normpath does not remove path separator duplicates at the beginning of the path """ return _normpath_re.sub(os.sep, os.path.normpath(path)) def join_path(*args): return normalize_path(os.sep.join(args)) def path_full_split(path): """ Given a path, it fully splits it into constituent path components (as opposed to os.path.split which splits it into trailing component and preceeding path """ path = normalize_path(path) splitpath = [] while 1: path, current = os.path.split(path) if current == '': if path: # Absolute path splitpath.append(os.sep) break splitpath.append(current) splitpath.reverse() return splitpath def copyfile_p(src, dst): """ Simple util function, copies src path to dst path, making directories as necessary. File permissions are not preserved. """ directory = os.path.split(dst)[0] try: os.makedirs(directory) except OSError: e = sys.exc_info()[1] if e.errno != 17: # not File exists raise if os.path.isdir(src): if not os.path.exists(dst): os.mkdir(dst) elif os.path.islink(src): exists = hasattr(os.path, "lexists") and os.path.lexists or os.path.exists if exists(dst): os.remove(dst) os.symlink(os.readlink(src), dst) else: shutil.copyfile(src, dst) def mkdir_p(path, mode=None, symlinks=None, allfiles=None): """ Similar to 'mkdir -p' -- makes all directories necessary to ensure the 'path' is a directory, and return the list of directories that were made as a result """ if mode is None: mode = int('0700', 8) dirs_created = [] components = path_full_split(path) for i in range(1,len(components)): d = os.path.join(*components[:i+1]) if symlinks: for symlink in symlinks: if symlink['path'] == d: # create symlink and remove it from symlink list os.symlink(symlink['symlink'], symlink['path']) symlinks.remove(symlink) allfiles.remove(symlink) dirs_created.append(symlink) continue log_debug(8, "testing", d) try: os.mkdir(d, mode) dirs_created.append(d) log_debug(8, "created", d) except OSError: e = sys.exc_info()[1] if e.errno != 17: raise else: log_debug(8, "already exists", d) log_debug(6, "dirs_created:", dirs_created) return dirs_created def rmdir_p(path, stoppath): """ if rmdir had a -p option, this would be it. remove dir and up until empty dir is hit, or stoppath is reached path and stoppath have to be absolute paths """ # First normalize both paths stoppath = normalize_path(os.sep + stoppath) path = normalize_path(os.sep + path) # stoppath has to be a prefix of path if path[:len(stoppath)] != stoppath: raise OSError("Could not remove %s: %s is not a prefix" % ( path, stoppath)) while 1: if stoppath == path: # We're done break # Try to remove the directory try: os.rmdir(path) except OSError: # Either the directory is full, or we don't have permissions; stop break path, current = os.path.split(path) if current == '': # We're done - reached the root break #returns slashstring with any trailing slash removed def rm_trailing_slash(slashstring): if slashstring[-1] == "/": slashstring = slashstring[0:-1] return slashstring def getContentChecksum(checksum_type, contents): if hashlib_has_usedforsecurity: engine = hashlib.new(checksum_type, usedforsecurity=False) else: engine = hashlib.new(checksum_type) engine.update(contents) return engine.hexdigest() def sha256_file(filename): engine = hashlib.new('sha256') fh = open(filename, "rb") while 1: buf = fh.read(4096) if not buf: break engine.update(bstr(buf)) return engine.hexdigest() def parse_url(server_url, scheme="https"): return urlparse.urlparse(server_url, scheme=scheme) def unparse_url(url_tuple): return urlparse.urlunparse(url_tuple) def get_home_dir(): uid = os.getuid() ent = pwd.getpwuid(uid) return ent[5] 07070100000030000041FD0000000000000000000000015CDC5C8900000000000000000000000000000000000000000000001A00000000mgr-cfg/config_management07070100000031000081B40000000000000000000000015CDC5C89000004AE000000000000000000000000000000000000002300000000mgr-cfg/config_management/Makefile# Makefile for spacewalk backend # # what is the backend top dir TOP = .. # Specific stuff SUBDIR = config_management EXEC_FILES = rhncfg-manager FILES = $(EXEC_FILES) __init__ \ rhncfg_add \ rhncfg_create_channel \ rhncfg_diff \ rhncfg_diff_revisions \ rhncfg_download_channel \ rhncfg_get \ rhncfg_list \ rhncfg_list_channels \ rhncfg_remove \ rhncfg_remove_channel \ rhncfg_revisions \ rhncfg_update \ rhncfg_upload_channel \ rpc_repository CONFIG_FILES = rhncfg-manager.conf # man pages DOCBOOK = $(wildcard /usr/bin/docbook2man) SGMLS = $(wildcard *.sgml) MANS = $(patsubst %.sgml,%.8,$(SGMLS)) MANDIR ?= /usr/man EXTRA_DIRS = $(BINDIR) $(CONFDIR) $(MANDIR)/man8 include $(TOP)/Makefile.defs ifneq ($(DOCBOOK),) # install man pages all :: $(MANS) install :: $(MANS) $(PREFIX)/$(MANDIR) $(INSTALL_DATA) $(MANS) $(PREFIX)/$(MANDIR)/man8 clean :: @rm -fv $(MANS) manpage.* endif install :: install-bin install-conf 07070100000032000081B40000000000000000000000015CDC5C8900000266000000000000000000000000000000000000002600000000mgr-cfg/config_management/__init__.py# # Copyright (c) 2008--2013 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. # 07070100000033000081B40000000000000000000000015CDC5C8900000518000000000000000000000000000000000000002E00000000mgr-cfg/config_management/rhncfg-manager.conf[rhncfg-manager] # increase for debugging output debug_level = 0 # don't change this... used in substitutions below. # if you get rid of the '%(server_handler)s' bits below, # the *MUST* be replaced with this value... server_handler = /CONFIG-MANAGEMENT-TOOL # don't change this, period. repository_type = rpc_repository # to use a server other than what up2date is configured to use, # do something along the lines of: # server_url = https://some.example.com%(server_handler)s # server_url = http://another.example.net:8080%(server_handler)s # server_url = https://yet.another.example.org:8081/CONFIG-MANAGEMENT-TOOL # # You may also edit this value in a ~/.rhncfgrc file override file, # which will allow you to run the rhncfg-manager program as non-root server_url = https://%(server_name)s%(server_handler)s # for your convenience, you may put your RHN username in here # to avoid being asked for it every time you run rhncfg-manager # username = your_rhn_username_here # the following fields are inherited from up2date's configuration, # but are overridable in this file, or in a ~/.rhncfgrc file # enableProxy = 1 # enableProxyAuth = 1 # httpProxy = some.proxy.example.com:3030 # proxyUser = proxy_user_name # proxyPassword = proxy_password # sslCACert = /usr/share/rhn/RHNS-CA-CERT 07070100000034000081B40000000000000000000000015CDC5C8900000502000000000000000000000000000000000000002C00000000mgr-cfg/config_management/rhncfg-manager.py#!/usr/bin/python # # Copyright (c) 2008--2013 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 from config_common.rhn_main import BaseMain class Main(BaseMain): modes = [ 'add', 'create-channel', 'diff', 'diff-revisions', 'download-channel', 'get', 'list', 'list-channels', 'remove', 'remove-channel', 'revisions', 'update', 'upload-channel', ] plugins_dir = 'config_management' config_section = 'rhncfg-manager' mode_prefix = 'rhncfg' if __name__ == '__main__': try: sys.exit(Main().main() or 0) except KeyboardInterrupt: sys.stderr.write("user interrupted\n") sys.exit(0) 07070100000035000081B40000000000000000000000015CDC5C89000033D1000000000000000000000000000000000000002E00000000mgr-cfg/config_management/rhncfg-manager.sgml<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V3.1//EN" [ <!ENTITY RHN "Spacewalk" > <!ENTITY HOSTED " or Hosted" > <!ENTITY RHNCFGM "rhncfg-manager" > ]> <refentry> <RefMeta> <RefEntryTitle>&RHNCFGM;</RefEntryTitle><manvolnum>8</manvolnum> <RefMiscInfo>Version 4.0</RefMiscInfo> </RefMeta> <RefNameDiv> <RefName><command>&RHNCFGM;</command></RefName> <RefPurpose> a tool for managing &RHN; central repository of configuration files and channels. </RefPurpose> </RefNameDiv> <RefSynopsisDiv> <Synopsis> <cmdsynopsis> <command>&RHNCFGM;</command> <arg>MODE</arg> <arg>--config <replaceable>config_file</replaceable></arg> <arg>--server-name <replaceable>name</replaceable></arg> <arg>--username <replaceable>user</replaceable></arg> <arg>--password <replaceable>pass</replaceable></arg> <arg>params</arg> </cmdsynopsis> </Synopsis> </RefSynopsisDiv> <RefSect1><Title>Description</Title> <para> The &RHN; Configuration Manager (<emphasis>&RHNCFGM;</emphasis>) is used to manage &RHN; configuration files and channels. </para> <para> This tool offers a command line alternative to the configuration management features found within the &RHN; web user interface&HOSTED;, as well as the ability to script some or all of the related maintenance. </para> <para> This tool is intended for use by the Config Administrator and therefore requires an &RHN; username and password that has the appropriate permission set. (The username and password may be specified in <filename>/etc/sysconfig/rhn/rhncfg-manager.conf</filename> or in the [rhncfg-manager] section of <filename>~/.rhncfgrc</filename> or the config file specified by <term>--config=<replaceable>config_file</replaceable></term>). </para> <para> Note that when run as root, &RHNCFGM; attempts to pull in needed configuration values from the Red Hat Update Agent (up2date). When run as something other than root, configuration changes may be needed within the <filename>~/.rhncfgrc</filename> file, or the config file specified by <term>--config=<replaceable>config_file</replaceable></term>. The session file is cached in <filename>~/.rhncfg-manager-session</filename> to prevent logging in for every command. </para> <para> The <filename>~/.rhncfgrc</filename> file overrides the values set in <filename>/etc/sysconfig/rhn/rhncfg-manager.conf</filename> which, by default, uses the values set in <filename>/etc/sysconfig/rhn/up2date</filename>. The <filename>~/.rhncfgrc</filename> file may itself be overridden by the command line option <term>--config=<replaceable>config_file</replaceable></term> Please review <filename>rhncfg-manager.conf</filename> for further details. </para> <para> The &RHN; Configuration Manager offers these primary modes: <emphasis>add</emphasis>, <emphasis>create-channel</emphasis>, <emphasis>diff</emphasis>, <emphasis>diff-revisions</emphasis>, <emphasis>download-channel</emphasis>, <emphasis>get</emphasis>, <emphasis>list</emphasis>, <emphasis>list-channels</emphasis>, <emphasis>remove</emphasis>, <emphasis>remove-channel</emphasis>, <emphasis>revisions</emphasis>, <emphasis>update</emphasis>, <emphasis>upload-channel</emphasis>. </para> <para> Context sensitive help is available for each "mode", e.g., &RHNCFGM; diff-revisions --help </para> <para> <simplelist> <member>This tool utilizes a "cvs style" command syntax: command mode options</member> <member>For example: &RHNCFGM; diff-revisions --help</member> </simplelist> </para> </RefSect1> <RefSect1><Title>modes</Title> <variablelist> <varlistentry> <term>add [ <replaceable>OPTIONS</replaceable> ] <replaceable>FILE</replaceable> [ <replaceable>FILE</replaceable> ... ]</term> <listitem> <para> Add a file to a config channel. </para> <para>-c<replaceable>CHANNEL_LABEL</replaceable>, --channel=<replaceable>CHANNEL_LABEL</replaceable> -- upload file(s) in this config channel</para> <para>-d<replaceable>DEST_FILE</replaceable>, --dest-file=<replaceable>DEST_FILE</replaceable> -- upload the file as this path</para> <para>-t<replaceable>TOPDIR</replaceable>, --topdir=<replaceable>TOPDIR</replaceable> -- make all files relative to this string</para> <para>--delim-start=<replaceable>DELIM_START</replaceable> -- start delimiter for variable interpolation</para> <para>--delim-end=<replaceable>DELIM_END</replaceable> -- end delimiter for variable interpolation</para> <para>--selinux-context=<replaceable>SELINUX_CONTEXT</replaceable> -- overwrite the SELinux context label to this string</para> </listitem> </varlistentry> <varlistentry> <term>create-channel <replaceable>CHANNEL_LABEL</replaceable> [ <replaceable>CHANNEL_LABEL ...</replaceable> ]</term> <listitem> <para> Create a config channel for your organization. </para> </listitem> </varlistentry> <varlistentry> <term>diff [ <replaceable>OPTIONS</replaceable> ] <replaceable>FILE</replaceable> [ <replaceable>FILE</replaceable> ... ]</term> <listitem> <para> Differentiating between latest configuration files in a channel and whats on disk. </para> <para>-c<replaceable>CHANNEL_LABEL</replaceable>, --channel=<replaceable>CHANNEL_LABEL</replaceable> -- get file(s) from this config channel</para> <para>-r<replaceable>REVISION</replaceable>, --revision=<replaceable>REVISION</replaceable> -- use this revision</para> <para>-d<replaceable>DEST_FILE</replaceable>, --dest-file=<replaceable>DEST_FILE</replaceable> -- upload the file as this path</para> <para>-t<replaceable>TOPDIR</replaceable>, --topdir=<replaceable>TOPDIR</replaceable> -- make all files relative to this string</para> </listitem> </varlistentry> <varlistentry> <term>diff-revisions [ <replaceable>OPTIONS</replaceable> ] <replaceable>FILE</replaceable></term> <listitem> <para> Compare different versions of a file across channels and revisions. See documentation for further details. </para> <para>-c<replaceable>CHANNEL_LABEL</replaceable>, --channel=<replaceable>CHANNEL_LABEL</replaceable> -- use this config channel</para> <para>-r<replaceable>REVISION</replaceable>, --revision=<replaceable>REVISION</replaceable> -- use this revision</para> </listitem> </varlistentry> <varlistentry> <term>download-channel (-t|--topdir=)<replaceable>TOP_LEVEL_DIRECTORY</replaceable> [ <replaceable>OPTIONS</replaceable> ] <replaceable>CHANNEL_LABEL</replaceable> [ <replaceable>CHANNEL_LABEL</replaceable> ... ]</term> <listitem> <para> Download all the configuration files in a channel to disk. </para> <para>-t<replaceable>TOPDIR</replaceable>, --topdir=<replaceable>TOPDIR</replaceable> -- directory all the file paths are relative to</para> </listitem> </varlistentry> <varlistentry> <term>get [ <replaceable>OPTIONS</replaceable> ] <replaceable>FILE</replaceable> [ <replaceable>FILE</replaceable> ... ]</term> <listitem> <para> Get the contents of a particular configuration file. Outputs to stdout. </para> <para>-c<replaceable>CHANNEL_LABEL</replaceable>, --channel=<replaceable>CHANNEL_LABEL</replaceable> -- get file(s) from this config channel</para> <para>-t<replaceable>TOPDIR</replaceable>, --topdir=<replaceable>TOPDIR</replaceable> -- make all files relative to this string</para> <para>-r<replaceable>REVISION</replaceable>, --revision=<replaceable>REVISION</replaceable> -- get this file revision</para> </listitem> </varlistentry> <varlistentry> <term>list <replaceable>CHANNEL_LABEL</replaceable></term> <listitem> <para> List all configuration files in a channel. </para> </listitem> </varlistentry> <varlistentry> <term>list-channels</term> <listitem> <para> List all configuration channels. </para> </listitem> </varlistentry> <varlistentry> <term>remove [ <replaceable>OPTIONS</replaceable> ] <replaceable>FILE</replaceable> [ <replaceable>FILE</replaceable> ... ]</term> <listitem> <para> Remove a configuration file from a channel. May be prompted for a username and password. </para> <para>-c<replaceable>CHANNEL_LABEL</replaceable>, --channel=<replaceable>CHANNEL_LABEL</replaceable> -- remove file(s) from this config channel</para> <para>-t<replaceable>TOPDIR</replaceable>, --topdir=<replaceable>TOPDIR</replaceable> -- make all files relative to this string</para> </listitem> </varlistentry> <varlistentry> <term>remove-channel <replaceable>CHANNEL_LABEL</replaceable> [ <replaceable>CHANNEL_LABEL</replaceable> ... ]</term> <listitem> <para> Remove a configuration channel. May be prompted for a username and password. </para> </listitem> </varlistentry> <varlistentry> <term>revisions [ <replaceable>OPTIONS</replaceable> ] <replaceable>FILE</replaceable> [ <replaceable>FILE</replaceable> ... ]</term> <listitem> <para> Determine the number of configuration file revisions. </para> <para>-c<replaceable>CHANNEL_LABEL</replaceable>, --channel=<replaceable>CHANNEL_LABEL</replaceable> -- use this config channel</para> </listitem> </varlistentry> <varlistentry> <term>update [ <replaceable>OPTIONS</replaceable> ] <replaceable>FILE</replaceable> [ <replaceable>FILE</replaceable> ... ]</term> <listitem> <para> Create a new revision of a file in a channel (or add the first revisions to that channel if none existed before for the given path). </para> <para>-c<replaceable>CHANNEL_LABEL</replaceable>, --channel=<replaceable>CHANNEL_LABEL</replaceable> -- upload file(s) in this config channel</para> <para>-d<replaceable>DEST_FILE</replaceable>, --dest-file=<replaceable>DEST_FILE</replaceable> -- upload the file as this path</para> <para>-t<replaceable>TOPDIR</replaceable>, --topdir=<replaceable>TOPDIR</replaceable> -- make all files relative to this string</para> <para>--delim-start=<replaceable>DELIM_START</replaceable> -- start delimiter for variable interpolation</para> <para>--delim-end=<replaceable>DELIM_END</replaceable> -- end delimiter for variable interpolation</para> <para>--selinux-context=<replaceable>SELINUX_CONTEXT</replaceable> -- overwrite the SELinux context label to this string</para> </listitem> </varlistentry> <varlistentry> <term>upload-channel [ <replaceable>OPTIONS</replaceable> ] <replaceable>CHANNEL_LABEL</replaceable> [ <replaceable>CHANNEL_LABEL</replaceable> ... ]</term> <listitem> <para> Upload multiple configuration files (a tree) to a channel from disk. </para> <para>-t<replaceable>TOPDIR</replaceable>, --topdir=<replaceable>TOPDIR</replaceable> -- directory all file paths are relative to</para> </listitem> </varlistentry> </variablelist> </RefSect1> <RefSect1><Title>others</Title> <variablelist> <varlistentry> <term>--server-name</term> <listitem> <para>&RHN; server name.</para> </listitem> </varlistentry> <varlistentry> <term>--username</term> <listitem> <para>&RHN; user name.</para> </listitem> </varlistentry> <varlistentry> <term>--password</term> <listitem> <para>&RHN; password for the specified user.</para> </listitem> </varlistentry> <varlistentry> <term>-h, --help</term> <listitem> <para>Display the help screen with a list of options.</para> <para>Use context sensitive help as well with the diffent "modes". E.g., &RHNCFGM; upload-channel --help</para> </listitem> </varlistentry> </variablelist> </RefSect1> <RefSect1><Title>See Also</Title> <simplelist> <member>rhncfg-client (8)</member> </simplelist> </RefSect1> <RefSect1><Title>Authors</Title> <simplelist> <member>Mihai Ibanescu <email>misa@redhat.com</email></member> <member>Bret McMillan <email>bretm@redhat.com</email></member> <member>Todd Warner <email>taw@redhat.com</email> (man page only)</member> <member>Laurence Rochfort <email>laurence.rochfort@oracle.com</email></member> </simplelist> </RefSect1> </RefEntry> 07070100000036000081B40000000000000000000000015CDC5C890000050F000000000000000000000000000000000000002600000000mgr-cfg/config_management/rhncfg.conf[rhncfg] # increase for debugging output debug_level = 0 # don't change this... used in substitutions below. # if you get rid of the '%(server_handler)s' bits below, # the *MUST* be replaced with this value... server_handler = /CONFIG-MANAGEMENT-TOOL # don't change this, period. repository_type = rpc_repository # to use a server other than what up2date is configured to use, # do something along the lines of: # server_url = https://some.example.com%(server_handler)s # server_url = http://another.example.net:8080%(server_handler)s # server_url = https://yet.another.example.org:8081/CONFIG-MANAGEMENT-TOOL # # You may also edit this value in a ~/.rhncfgrc file override file, # which will allow you to run the rhncfg-manager program as non-root server_url = https://%(server_name)s%(server_handler)s # for your convenience, you may put your RHN username in here # to avoid being asked for it every time you run rhncfg-manager # username = your_rhn_username_here # the following fields are inherited from up2date's configuration, # but are overridable in this file, or in a ~/.rhncfgrc file # enableProxy = 1 # enableProxyAuth = 1 # httpProxy = some.proxy.example.com:3030 # proxyUser = proxy_user_name # proxyPassword = proxy_password # sslCACert = /usr/share/rhn/RHNS-CA-CERT 07070100000037000081B40000000000000000000000015CDC5C89000015D8000000000000000000000000000000000000002800000000mgr-cfg/config_management/rhncfg_add.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 os import sys from config_common import handler_base, utils, cfg_exceptions from config_common.rhn_log import log_debug, die, log_error class Handler(handler_base.HandlerBase): _usage_options = "[options] file [ file ... ]" is_first_revision=1 _options_table = handler_base.HandlerBase._options_table + [ handler_base.HandlerBase._option_class( '-c', '--channel', action="store", help="Upload files in this config channel", ), handler_base.HandlerBase._option_class( '-d', '--dest-file', action="store", help="Upload the file as this path", ), handler_base.HandlerBase._option_class( '-t', '--topdir', action="store", help="Make all files relative to this string", ), handler_base.HandlerBase._option_class( '--delim-start', action="store", help="Start delimiter for variable interpolation", ), handler_base.HandlerBase._option_class( '--delim-end', action="store", help="End delimiter for variable interpolation", ), handler_base.HandlerBase._option_class( '-i', '--ignore-missing', action="store_true", help="Ignore missing local files", ), handler_base.HandlerBase._option_class( '--selinux-context', action="store", help="Overwrite the SELinux context", ), ] def run(self): log_debug(2) if self.options.dest_file and self.options.topdir: die(6, "Error: conflicting options --dest-file and --topdir") if len(self.args) == 0: die(0, "No files supplied (use --help for help)") channel = self.options.channel if not channel: die(6, "Config channel not specified") r = self.repository if not r.config_channel_exists(channel): die(6, "Error: config channel %s does not exist" % channel) files = [utils.normalize_path(x) for x in self.args] files_to_push = [] if self.options.dest_file: if len(files) != 1: die(7, "--dest-file accepts a single file") if not (self.options.dest_file[0] == os.sep): die(7, "--dest-file argument must begin with " + os.sep) files_to_push.append((files[0], self.options.dest_file)) elif self.options.topdir: if not os.path.isdir(self.options.topdir): die(8, "--topdir specified, but `%s' not a directory" % self.options.topdir) #5/11/05 wregglej - 141790 remove the trailing slash from topdir self.options.topdir = utils.rm_trailing_slash(self.options.topdir) for f in files: if not f.startswith(self.options.topdir): die(8, "--topdir %s specified, but file `%s' doesn't comply" % (self.options.topdir, f)) files_to_push.append((f, f[len(self.options.topdir):])) else: for f in files: #if a file is given w/o a full path, then use the abspath of the #file as name of the file to be uploaded into the channel if not (f[0] == os.sep): files_to_push.append((f, os.path.abspath(f))) else: files_to_push.append((f, f)) for (local_file, remote_file) in files_to_push: if not os.path.exists(local_file): if self.options.ignore_missing: files_to_push.remove((local_file,remote_file)) print("Local file %s does not exist. Ignoring file..." %(local_file)) else: die(9, "No such file `%s'" % local_file) print("Pushing to channel %s:" % (channel, )) delim_start = self.options.delim_start delim_end = self.options.delim_end selinux_ctx = None if type(self.options.selinux_context) != None: selinux_ctx = self.options.selinux_context for (local_file, remote_file) in files_to_push: try: r.put_file(channel, remote_file, local_file, is_first_revision=self.is_first_revision, delim_start=delim_start, delim_end=delim_end, selinux_ctx=selinux_ctx) except cfg_exceptions.RepositoryFileExistsError: e = sys.exc_info()[1] log_error("Error: %s is already in channel %s" % (remote_file, channel)) except cfg_exceptions.RepositoryFilePushError: e = sys.exc_info()[1] log_error("Error pushing file: %s" % e) else: print("Local file %s -> remote file %s" % (local_file, remote_file)) 07070100000038000081B40000000000000000000000015CDC5C890000052A000000000000000000000000000000000000003300000000mgr-cfg/config_management/rhncfg_create_channel.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. # from config_common import handler_base, cfg_exceptions from config_common.rhn_log import log_debug, die class Handler(handler_base.HandlerBase): _usage_options = "config_channel [ config_channel ... ]" def run(self): log_debug(2) r = self.repository if len(self.args) == 0: die(5, "No config channels specified") for channel in self.args: print("Creating config channel %s" % channel) try: r.create_config_channel(channel) except cfg_exceptions.ConfigChannelAlreadyExistsError: die("Config channel %s already exists" % channel) print("Config channel %s created" % channel) 07070100000039000081B40000000000000000000000015CDC5C8900002048000000000000000000000000000000000000002900000000mgr-cfg/config_management/rhncfg_diff.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 base64 from datetime import datetime import difflib import os import sys from config_common import handler_base, utils, cfg_exceptions from config_common.rhn_log import log_debug, die from config_common.file_utils import f_date, ostr_to_sym from spacewalk.common.usix import next from rhn.i18n import bstr, sstr class Handler(handler_base.HandlerBase): _usage_options = "[options] file [ file ... ]" _options_table = [ handler_base.HandlerBase._option_class( '-c', '--channel', action="store", help="Get file(s) from this config channel", ), handler_base.HandlerBase._option_class( '-r', '--revision', action="store", help="Use this revision", ), handler_base.HandlerBase._option_class( '-d', '--dest-file', action="store", help="Remote file to compare to", ), handler_base.HandlerBase._option_class( '-t', '--topdir', action="store", help="Directory to which all file paths are relative", ), ] def run(self): log_debug(2) if self.options.dest_file and self.options.topdir: die(6, "Error: conflicting options --dest-file and --topdir") if len(self.args) == 0: die(0, "No files supplied (use --help for help)") channel = self.options.channel if not channel: die(6, "Config channel not specified") r = self.repository if not r.config_channel_exists(channel): die(6, "Error: config channel %s does not exist" % channel) topdir = self.options.topdir revision = self.options.revision files_to_diff = [] files = [utils.normalize_path(x) for x in self.args] files_count = len(files) if files_count != 1 and revision is not None: die(8, "--revision can only be used with a single file") if self.options.dest_file: if files_count != 1: die(7, "--dest-file accepts a single file") files_to_diff.append((files[0], self.options.dest_file)) elif topdir: if not os.path.isdir(topdir): die(8, "--topdir specified, but `%s' not a directory" % topdir) #5/11/04 wregglej - 141790 remove trailing slash in topdir, if present. topdir = utils.rm_trailing_slash(topdir) for f in files: if not f.startswith(topdir): die(8, "--topdir %s specified, but file `%s' doesn't comply" % (topdir, f)) if os.path.isdir(f) and not os.path.islink(f): die(8, "Cannot diff %s; it is a directory" % f) files_to_diff.append((f, f[len(topdir):])) else: for f in files: if os.path.isdir(f) and not os.path.islink(f): die(8, "Cannot diff %s; it is a directory" % f) files_to_diff.append((f, f)) for (local_file, remote_file) in files_to_diff: sys.stdout.write( self.diff_file(channel, remote_file, local_file, revision)) def __attributes_differ(self, fsrc, fdst): """ Returns true if acl, ownership, type or selinux context differ. fsrc is config file retrieved from xmlrpc, fdst is output of make_stat_info() """ return (fsrc['filemode'] != fdst['mode']) or \ (fsrc['username'] != fdst['user']) or (fsrc['groupname'] != fdst['group']) or \ (fsrc['selinux_ctx'] != fdst['selinux_ctx']) def diff_file(self, channel, path, local_file, revision): r = self.repository try: info = r.get_raw_file_info(channel, path, revision) if 'encoding' in info and info['file_contents']: if info['encoding'] == 'base64': info['file_contents'] = base64.decodestring(bstr(info['file_contents'])) else: die(9, 'Error: unknown encoding %s' % info['encoding']) except cfg_exceptions.RepositoryFileMissingError: die(2, "Error: no such file %s (revision %s) in config channel %s" % (path, revision, channel)) if os.path.islink(local_file) and info['filetype'] != 'symlink' : die(8, "Cannot diff %s; the file on the system is a symbolic link while the file in the channel is not. " % local_file) if info['filetype'] == 'symlink' and not os.path.islink(local_file) : die(8, "Cannot diff %s; the file on the system is not a symbolic link while the file in the channel is. " % local_file) if info['filetype'] == 'symlink': src_link = info['symlink'] dest_link = os.readlink(local_file) if src_link != os.readlink(local_file): return "Symbolic links differ. Channel: '%s' -> '%s' System: '%s' -> '%s' \n " % (path,src_link, path, dest_link) return "" response_output = "" content_differs = False if 'is_binary' in info and info['is_binary'] == 'Y': from_content = info['file_contents'] to_file = open(local_file, 'rb') to_content = to_file.read() to_file.close() if len(from_content) != len(to_content): content_differs = True else: for i in range(len(from_content)): if from_content[i] != to_content[i]: content_differs = True break if content_differs: response_output = "Binary file content differs\n" else: fromlines = sstr(info['file_contents']).splitlines(1) tofile = open(local_file, 'r') tolines = tofile.readlines() tofile.close() diff_output = difflib.unified_diff(fromlines, tolines, info['path'], local_file) first_row = second_row = '' try: first_row = next(diff_output) # if content was same, exception thrown so following # lines don't execute content_differs = True second_row = next(diff_output) response_output = ''.join(list(diff_output)) except StopIteration: pass file_stat = os.lstat(local_file) local_info = r.make_stat_info(local_file, file_stat) # rhel4 do not support selinux if not 'selinux_ctx' in local_info: local_info['selinux_ctx'] = '' if 'selinux_ctx' not in info: info['selinux_ctx'] = '' if not content_differs and not self.__attributes_differ(info, local_info): return "" else: template = "%s %s\t%s\tattributes: %s %s %s %s\tconfig channel: %s\trevision: %s" if 'modified' not in info: info['modified'] = '' first_row = template % ('---', path, str(info['modified']), ostr_to_sym(info['filemode'], info['filetype']), info['username'], info['groupname'], info['selinux_ctx'], channel, info['revision'], ) second_row = template % ('+++', local_file, f_date(datetime.fromtimestamp(local_info['mtime'])), ostr_to_sym(local_info['mode'], 'file'), local_info['user'], local_info['group'], local_info['selinux_ctx'], 'local file', None ) return first_row + '\n' + second_row + '\n' + response_output 0707010000003A000081B40000000000000000000000015CDC5C8900000ACD000000000000000000000000000000000000003300000000mgr-cfg/config_management/rhncfg_diff_revisions.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 from config_common import handler_base, cfg_exceptions from config_common.rhn_log import log_debug, die class Handler(handler_base.HandlerBase): _usage_options = "[options] file" _options_table = [ handler_base.HandlerBase._option_class( '-c', '--channel', action="append", help="Use this config channel", ), handler_base.HandlerBase._option_class( '-r', '--revision', action="append", help="Use this revision", ), ] def run(self): log_debug(2) r = self.repository if len(self.args) != 1: die(3, "One file needs to be specified") path = self.args[0] channel_dst = None ns_count = len(self.options.channel or []) if ns_count == 0: die(3, "At least one config channel has to be specified") channel_src = self.options.channel[0] if ns_count > 2: die(3, "At most two config channels can be specified") if not r.config_channel_exists(channel_src): die(4, "Source config channel %s does not exist" % channel_src) if ns_count == 2: channel_dst = self.options.channel[1] if not r.config_channel_exists(channel_dst): die(4, "Config channel %s does not exist" % channel_dst) revision_dst = None rev_count = len(self.options.revision or []) if rev_count == 0: die(3, "At least one revision has to be specified") revision_src = self.options.revision[0] if rev_count > 2: die(3, "At most two revisions can be specified") if rev_count == 2: revision_dst = self.options.revision[1] try: result = r.diff_file_revisions(path, channel_src, revision_src, channel_dst, revision_dst) except cfg_exceptions.RepositoryFileMissingError: e = sys.exc_info()[1] die(2, e[0]) except cfg_exceptions.BinaryFileDiffError: e = sys.exc_info()[1] die(3, e[0]) sys.stdout.write(result) 0707010000003B000081B40000000000000000000000015CDC5C8900000734000000000000000000000000000000000000003500000000mgr-cfg/config_management/rhncfg_download_channel.py# # Copyright (c) 2008--2013 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 from config_common import handler_base, utils from config_common.deploy import deploy_files from config_common.rhn_log import log_debug, die class Handler(handler_base.HandlerBase): _usage_options = "(-t|--topdir=)<top-level-directory> [options] config_channel [ config_channel ... ]" _options_table = handler_base.HandlerBase._options_table + [ handler_base.HandlerBase._option_class( '-t', '--topdir', action="store", help="Directory all the file paths are relative to. This option must be set.", ), ] def run(self): log_debug(2) r = self.repository if not self.args: die(6, "No config channels specified") topdir = self.options.topdir if not topdir: die(7, "--topdir not specified") if not os.path.isdir(topdir): die(8, "--topdir specified, but `%s' not a directory" % topdir) for ns in self.args: if not r.config_channel_exists(ns): die(6, "Error: config channel %s does not exist" % ns) deploy_files(utils.join_path(topdir, ns), r, r.list_files(ns), config_channel=ns) 0707010000003C000081B40000000000000000000000015CDC5C89000011EE000000000000000000000000000000000000002800000000mgr-cfg/config_management/rhncfg_get.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 os import sys import tempfile from config_common import handler_base, utils, cfg_exceptions from config_common.rhn_log import log_debug, die from config_common.transactions import DeployTransaction, FailedRollback from spacewalk.common.usix import raise_with_tb def deploying_mesg_callback(path): print("Deploying %s" % path) class Handler(handler_base.HandlerBase): _usage_options = "[options] file [ file ... ]" _options_table = handler_base.HandlerBase._options_table + [ handler_base.HandlerBase._option_class( '-c', '--channel', action="store", help="Get file(s) from this config channel", ), handler_base.HandlerBase._option_class( '-t', '--topdir', action="store", help="Make all files relative to this string", ), handler_base.HandlerBase._option_class( '-r', '--revision', action="store", help="Get this file revision", ), ] def run(self): log_debug(2) r = self.repository channel = self.options.channel if not channel: die(6, "Config channel not specified") topdir = self.options.topdir if topdir: if not os.path.isdir(self.options.topdir): die(8, "--topdir specified, but `%s' not a directory" % self.options.topdir) if not self.args: die(7, "No files specified") revision = self.options.revision if revision: if len(self.args) > 1: die(9, "--revision specified with multiple files") dep_trans = None if topdir: dep_trans = DeployTransaction(transaction_root=topdir) dep_trans.deploy_callback(deploying_mesg_callback) for f in self.args: try: directory = topdir or tempfile.gettempdir() #5/11/05 wregglej - 157066 dirs_created is returned from get_file_info. (temp_file, info, dirs_created) = r.get_file_info(channel, f, revision=revision, auto_delete=0, dest_directory=directory) except cfg_exceptions.RepositoryFileMissingError: if revision is not None: die(2, "Error: file %s (revision %s) not in config " "channel %s" % (f, revision, channel)) else: die(2, "Error: file %s not in config channel %s" % (f, channel)) if topdir: #5/11/05 wregglej - 157066 dirs_created now gets passed into add_preprocessed. dep_trans.add_preprocessed(f, temp_file, info, dirs_created, strict_ownership=0) continue elif info.get('filetype') == 'symlink': print("%s -> %s" % (info['path'], info['symlink'])) continue elif info.get('filetype') == 'directory': print("%s is a directory entry, nothing to get" % info['path']) continue else: print(open(temp_file).read()) os.unlink(temp_file) if topdir: try: dep_trans.deploy() except Exception: try: dep_trans.rollback() except FailedRollback: raise_with_tb(FailedRollback("FAILED ROLLBACK: "), sys.exc_info()[2]) #5/3/05 wregglej - 136415 Added exception stuff for missing user info. except cfg_exceptions.UserNotFound: raise #5/5/05 wregglej - 136415 Added exception handling for unknown group. except cfg_exceptions.GroupNotFound: raise else: raise_with_tb(Exception("Deploy failed, rollback successful: "), sys.exc_info()[2]) 0707010000003D000081B40000000000000000000000015CDC5C89000004C2000000000000000000000000000000000000002900000000mgr-cfg/config_management/rhncfg_list.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. # from config_common import handler_base from config_common.rhn_log import log_debug, die class Handler(handler_base.HandlerBase): _usage_options = "config_channel" def run(self): log_debug(2) r = self.repository if len(self.args) != 1: die(5, "Exactly one argument required") channel = self.args[0] print("Files in config channel '%s':" % channel) files = r.list_files(channel) if not files: print("(no files in config channel)") else: for file in files: print(" %s" % file) 0707010000003E000081B40000000000000000000000015CDC5C8900000404000000000000000000000000000000000000003200000000mgr-cfg/config_management/rhncfg_list_channels.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. # from config_common import handler_base from config_common.rhn_log import log_debug, die class Handler(handler_base.HandlerBase): _usage_options = "" def run(self): log_debug(2) r = self.repository if len(self.args): die(5, "No arguments required") print("Available config channels:") for ns in r.list_config_channels(): print(" %s" % ns) 0707010000003F000081B40000000000000000000000015CDC5C8900000B71000000000000000000000000000000000000002B00000000mgr-cfg/config_management/rhncfg_remove.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 os import sys from config_common import handler_base, utils from config_common.rhn_log import log_debug, die try: # python2 import xmlrpclib except ImportError: # python3 import xmlrpc.client as xmlrpclib class Handler(handler_base.HandlerBase): _usage_options = "[options] file [ file ... ]" _options_table = handler_base.HandlerBase._options_table + [ handler_base.HandlerBase._option_class( '-c', '--channel', action="store", help="Remove files from this config channel", ), handler_base.HandlerBase._option_class( '-t', '--topdir', action="store", help="Make all files relative to this string", ), ] def run(self): log_debug(2) r = self.repository if len(self.args) == 0: die(0, "No files supplied (use --help for help)") channel = self.options.channel if not channel: die(6, "Config channel not specified") r = self.repository if not r.config_channel_exists(channel): die(6, "Error: config channel %s does not exist" % channel) files = [utils.normalize_path(x) for x in self.args] files_to_remove = [] if self.options.topdir: if not os.path.isdir(self.options.topdir): die(8, "--topdir specified, but `%s' not a directory" % self.options.topdir) for f in files: if not f.startswith(self.options.topdir): die(8, "--topdir %s specified, but file `%s' doesn't comply" % (self.options.topdir, f)) files_to_remove.append((f, f[len(self.options.topdir):])) else: for f in files: files_to_remove.append((f, f)) print("Removing from config channel %s" % channel) for (local_file, remote_file) in files_to_remove: try: r.remove_file(channel, remote_file) except xmlrpclib.Fault: e = sys.exc_info()[1] if e.faultCode == -4011: print("%s does not exist" % remote_file) continue raise else: print("%s removed" % remote_file) 07070100000040000081B40000000000000000000000015CDC5C89000005EB000000000000000000000000000000000000003300000000mgr-cfg/config_management/rhncfg_remove_channel.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. # from config_common import handler_base, cfg_exceptions from config_common.rhn_log import log_debug, die class Handler(handler_base.HandlerBase): _usage_options = "config_channel [ config_channel ... ]" def run(self): log_debug(2) r = self.repository if len(self.args) == 0: die(5, "No config channels specified") for config_channel in self.args: print("Removing config channel %s" % config_channel) try: r.remove_config_channel(config_channel) except cfg_exceptions.ConfigChannelNotInRepo: die("Config channel %s does not exist" % config_channel) except cfg_exceptions.ConfigChannelNotEmptyError: die("Could not remove non-empty config channel %s" % config_channel) print("Config channel %s removed" % config_channel) 07070100000041000081B40000000000000000000000015CDC5C8900000720000000000000000000000000000000000000002E00000000mgr-cfg/config_management/rhncfg_revisions.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. # from config_common import handler_base, cfg_exceptions from config_common.rhn_log import log_debug, die class Handler(handler_base.HandlerBase): _usage_options = "[options] file [ file ... ]" _options_table = handler_base.HandlerBase._options_table + [ handler_base.HandlerBase._option_class( '-c', '--channel', action="store", help="Use this config channel", ), ] def run(self): log_debug(2) r = self.repository channel = self.options.channel if not channel: die(6, "Config channel not specified") if not self.args: die(7, "No files specified") print("Analyzing files in config channel %s" % channel) for f in self.args: if not r.has_file(channel, f): die(8, "Config channel %s does not contain file %s" % (channel, f)) try: revisions = r.get_file_revisions(channel, f) except cfg_exceptions.RepositoryFileMissingError: print("%s: not in config channel" % f) continue print("%s: %s" % (f, " ".join([str(x) for x in revisions]))) 07070100000042000081B40000000000000000000000015CDC5C89000002B4000000000000000000000000000000000000002B00000000mgr-cfg/config_management/rhncfg_update.py# # Copyright (c) 2008--2013 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 rhncfg_add class Handler(rhncfg_add.Handler): is_first_revision=0 07070100000043000081B40000000000000000000000015CDC5C89000014CF000000000000000000000000000000000000003300000000mgr-cfg/config_management/rhncfg_upload_channel.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 os from config_common import handler_base, utils, cfg_exceptions from config_common.rhn_log import log_debug, log_error, die class Handler(handler_base.HandlerBase): _usage_options = "[options] [ config_channel ... ]" _options_table = handler_base.HandlerBase._options_table + [ handler_base.HandlerBase._option_class( '-t', '--topdir', action="store", default="./", help="Directory all the file paths are relative to", ), handler_base.HandlerBase._option_class( '-c', '--channel', action='store', default=None, help="List of channels the config info will be uploaded into. Channels delimited by ','.\nExample: --channel=foo,bar,baz", ), handler_base.HandlerBase._option_class( '', '--disable-selinux', action='store_true', default=None, help="Don't upload SELinux contexts", ), ] def run(self): log_debug(2) #5/12/05 wregglej - 149034 changed r into a instance variable self.r = self.repository topdir = self.options.topdir if not topdir: die(7, "--topdir not specified") if not os.path.isdir(topdir): die(8, "--topdir specified, but `%s' not a directory" % topdir) topdir = utils.normalize_path(topdir) #5/12/05 wregglej - 149034 allowing the channel name and the directory name to vary independently. if not self.options.channel is None: #Get the list of channels with leading and trailing whitespace removed. channels = [x.strip() for x in self.options.channel.split(',') if x] #Get the list of directories to upload. At this point it's the list of arguments. dirs = self.args elif not self.args: #If we get to this point, then --channel wasn't used and nothing was included as arguments. #Assumes that the directories in topdir are the ones we want to upload, and since no channels were #specified that each directory is it's own channel. channels = os.listdir(topdir) dirs = None print("No config channels specified, using %s" % channels) else: #At this point, --channel wasn't used but there was something included as an argument. #The name of the channel is assumed to be the same as the name of the directory. channels = self.args dirs = None #If dirs isn't None, then each directory needs to be uploaded into each channel. if dirs: for channel in channels: for directory in dirs: self.upload_config_channel(topdir, channel, directory) #If dirs is None, then each channel is it's own channel. else: for channel in channels: self.upload_config_channel(topdir, channel, channel) def upload_config_channel(self, topdir, channel, directory_name): if not self.r.config_channel_exists(channel): die(6, "Error: config channel %s does not exist" % channel) if self.options.disable_selinux: selinux_ctx = '' else: selinux_ctx = None print("Using config channel %s" % channel) channel_dir = utils.join_path(topdir, directory_name) if not os.path.exists(channel_dir): die(6, "Error: channel directory %s does not exist" % channel_dir) flist = list_files_recursive(channel_dir) for (dirname, filenames) in flist: assert dirname.startswith(channel_dir) remote_dirname = dirname[len(channel_dir):] for f in filenames: local_file = utils.join_path(dirname, f) remote_file = utils.join_path(remote_dirname, f) print("Uploading %s from %s" % (remote_file, local_file)) try: self.r.put_file(channel, remote_file, local_file, is_first_revision=0, selinux_ctx = selinux_ctx) except cfg_exceptions.RepositoryFilePushError: e = sys.exc_info()[1] log_error(e) def is_file_or_link(dirname, basename): return os.path.isfile(os.path.join(dirname, basename)) or \ os.path.islink(os.path.join(dirname, basename)) def list_files_recursive(d): file_list = [] for dirname, dirs, names in os.walk(d): file_list.append((dirname, filter(lambda x, d=dirname: is_file_or_link(d, x), names))) return file_list 07070100000044000081B40000000000000000000000015CDC5C8900003038000000000000000000000000000000000000002C00000000mgr-cfg/config_management/rpc_repository.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 os import sys import tempfile from spacewalk.common.usix import raise_with_tb try: # python2 import xmlrpclib except ImportError: # python3 import xmlrpc.client as xmlrpclib from config_common import cfg_exceptions, repository, utils, file_utils from config_common.rhn_log import log_debug class Repository(repository.RPC_Repository): _session_file = '.rhncfg-manager-session' def __init__(self, setup_network=1): log_debug(2) repository.RPC_Repository.__init__(self, setup_network) self.files_to_delete = [] self.username = None self.password = None self.session = None def login(self, username=None, password=None): self._get_session() if not self.session and not (username and password): raise cfg_exceptions.InvalidSession() if self.session and not username: test = self.rpc_call('config.test_session', {'session' : self.session}) if not test: raise cfg_exceptions.InvalidSession('Session is either expired or invalid') else: self.username = username self.password = password try: self.session = self.rpc_call('config.rhn_login', { 'username' : self.username, 'password' : self.password, }) except xmlrpclib.Fault: e = sys.exc_info()[1] fault_code, fault_string = e.faultCode, e.faultString if fault_code == -2: raise_with_tb(cfg_exceptions.AuthenticationError( "Invalid username or incorrect password"), sys.exc_info()[2]) raise_with_tb(cfg_exceptions.InvalidSession(fault_code, fault_string), sys.exc_info()[2]) self._save_session() if not self.session: raise cfg_exceptions.InvalidSession self.assert_repo_health() def cleanup(self): log_debug(4) for file in self.files_to_delete: if not os.path.isdir(file): os.unlink(file) def get_raw_file_info(self, config_channel, repopath, revision=None): """ given a namepath, return the raw data passed by the server """ log_debug(5) params = { 'session' : self.session, 'config_channel' : config_channel, 'path' : repopath, } if revision is not None: params['revision'] = revision try: result = self.rpc_call('config.management.get_file', params) except xmlrpclib.Fault: e = sys.exc_info()[1] if e.faultCode == -4011: # File not present raise_with_tb(cfg_exceptions.RepositoryFileMissingError(config_channel, repopath), sys.exc_info()[2]) raise return result def get_file_info(self, config_channel, repopath, revision=None, auto_delete=1, dest_directory=tempfile.gettempdir()): """ given a namepath, return the filename and the rest of the info passed by the server """ result = self.get_raw_file_info(config_channel, repopath, revision) fp = file_utils.FileProcessor() fullpath, dirs_created = fp.process(result, directory=dest_directory, strict_ownership=0) if auto_delete: self.files_to_delete.append(fullpath) del result['file_contents'] return fullpath, result, dirs_created def has_file(self, config_channel, repopath): params = { 'session' : self.session, 'config_channel' : config_channel, 'path' : repopath, } return self.rpc_call('config.management.has_file', params) def remove_file(self, config_channel, repopath): """ remove a given file from the repo """ log_debug(4) params = { 'session' : self.session, 'config_channel' : config_channel, 'path' : repopath, } return self.rpc_call('config.management.remove_file', params) def put_file(self, config_channel, repopath, localfile=None, is_first_revision=None, old_revision=None, delim_start=None, delim_end=None, selinux_ctx=None): """ Insert a given file into the repo, overwriting if necessary. localfile defaults to the repopath """ log_debug(4) params = self._make_file_info(repopath, localfile, delim_start=delim_start, delim_end=delim_end) max_file_size = self.get_maximum_file_size() if params['size'] > max_file_size: error_msg = "%s too large (%s bytes, %s bytes max allowed)" raise cfg_exceptions.ConfigFileTooLargeError(error_msg % (localfile, params['size'], max_file_size)) if selinux_ctx is not None: params.update({ 'selinux_ctx' : selinux_ctx, }) params.update({ 'session' : self.session, 'config_channel' : config_channel, }) if is_first_revision: params['is_first_revision'] = 1 elif old_revision: params['old_revision'] = int(old_revision) try: result = self.rpc_call('config.management.put_file', params) except xmlrpclib.Fault: e = sys.exc_info()[1] fault_code, fault_string = e.faultCode, e.faultString if is_first_revision and fault_code == -4013: raise_with_tb(cfg_exceptions.RepositoryFileExistsError(fault_string), sys.exc_info()[2]) if old_revision and fault_code == -4012: raise_with_tb(cfg_exceptions.RepositoryFileVersionMismatchError(fault_string), sys.exc_info()[2]) if fault_code == -4003: raise_with_tb(cfg_exceptions.ConfigFileTooLargeError(fault_string), sys.exc_info()[2]) if fault_code == -4014: raise_with_tb(cfg_exceptions.QuotaExceeded(fault_string), sys.exc_info()[2]) raise_with_tb(cfg_exceptions.RepositoryFilePushError(fault_code, fault_string), sys.exc_info()[2]) return result def config_channel_exists(self, config_channel): log_debug(4, config_channel) return (config_channel in self.list_config_channels()) def list_files(self, config_channel, repopath = None, recurse = 1): """ list files in a repo, recursing if requested; repopath is not used yet """ log_debug(4) files = self.rpc_call('config.management.list_files', {'session' : self.session, 'config_channel' : config_channel}) return [p['path'] for p in files] def get_file_revisions(self, config_channel, repopath): """ Fetch the file's revisions """ log_debug(4) params = { 'session' : self.session, 'config_channel' : config_channel, 'path' : repopath, } try: revisions = self.rpc_call('config.management.list_file_revisions', params) except xmlrpclib.Fault: e = sys.exc_info()[1] if e.faultCode == -4011: # File not present raise_with_tb(cfg_exceptions.RepositoryFileMissingError( config_channel, repopath), sys.exc_info()[2]) raise return revisions def list_config_channels(self): "List config channels" log_debug(4) if hasattr(self, 'config_channels'): return self.config_channels self.config_channels = self.rpc_call( 'config.management.list_config_channels', {'session' : self.session} ) or [] return self.config_channels def create_config_channel(self, config_channel): "creates a configuration channel" log_debug(4, config_channel) try: return self.rpc_call('config.management.create_config_channel', {'session' : self.session, 'config_channel' : config_channel}) except xmlrpclib.Fault: e = sys.exc_info()[1] if e.faultCode == -4010: raise_with_tb(cfg_exceptions.ConfigChannelAlreadyExistsError(config_channel), sys.exc_info()[2]) raise def remove_config_channel(self, config_channel): "Removes a configuration channel" log_debug(4, config_channel) try: return self.rpc_call('config.management.remove_config_channel', {'session' : self.session, 'config_channel' : config_channel}) except xmlrpclib.Fault: e = sys.exc_info()[1] if e.faultCode == -4009: raise_with_tb(cfg_exceptions.ConfigChannelNotInRepo(config_channel), sys.exc_info()[2]) if e.faultCode == -4005: raise_with_tb(cfg_exceptions.ConfigChannelNotEmptyError(config_channel), sys.exc_info()[2]) raise def _get_default_delimiters(self): "retrieves the default delimiters from the server" log_debug(4) result = self.rpc_call('config.management.get_default_delimiters', {'session' : self.session}) return result.get('delim_start'), result.get('delim_end') def _get_maximum_file_size(self): "get the maximum file size from the server" log_debug(4) result = self.rpc_call('config.management.get_maximum_file_size', {'session' : self.session}) return result def assert_repo_health(self): log_debug(4) pass def diff_file_revisions(self, path, config_channel_src, revision_src, config_channel_dst, revision_dst): log_debug(4) params = { 'session' : self.session, 'path' : path, 'config_channel_src': config_channel_src, 'revision_src' : revision_src, } if config_channel_dst is not None: params['config_channel_dst'] = config_channel_dst if revision_dst is not None: params['revision_dst'] = revision_dst try: ret = self.rpc_call('config.management.diff', params) except xmlrpclib.Fault: e = sys.exc_info()[1] if e.faultCode == -4011: # File not present raise_with_tb(cfg_exceptions.RepositoryFileMissingError(e.faultString), sys.exc_info()[2]) if e.faultCode == -4004: # Binary file requested raise_with_tb(cfg_exceptions.BinaryFileDiffError(e.faultString), sys.exc_info()[2]) raise return ret def _get_session(self): session_path = self._get_session_path() try: fh = open(session_path, 'r') self.session = fh.read() fh.close() except IOError: # session file not there... self.session = None return self.session def _save_session(self): if not self.session: self._remove_session() session_path = self._get_session_path() fh = open(session_path, "w+") fh.write(self.session) fh.close() def _remove_session(self): p = self._get_session_path() try: os.unlink(p) except OSError: pass def _get_session_path(self): return os.path.join(utils.get_home_dir(), self._session_file) 07070100000045000081B40000000000000000000000015CDC5C890000003C000000000000000000000000000000000000001A00000000mgr-cfg/mgr-cfg-rpmlintrcaddFilter("suse-filelist-forbidden-sysconfig .*/sysconfig") 07070100000046000081B40000000000000000000000015CDC5C8900003255000000000000000000000000000000000000001800000000mgr-cfg/mgr-cfg.changes------------------------------------------------------------------- Wed May 15 20:06:15 CEST 2019 - jgonzalez@suse.com - version 4.0.8-1 - Obsolete newer versions of rhncfg ------------------------------------------------------------------- Wed May 15 14:58:23 CEST 2019 - jgonzalez@suse.com - version 4.0.7-1 - SPEC cleanup - Fix wrong bugzilla entry at changelog ------------------------------------------------------------------- Mon Apr 22 14:40:08 CEST 2019 - jgonzalez@suse.com - version 4.0.6-1 - Fix build for non SUSE/openSUSE distributions ------------------------------------------------------------------- Mon Apr 22 12:00:20 CEST 2019 - jgonzalez@suse.com - version 4.0.5-1 - create client tools compat links also for non-SUSE systems (bsc#1131408) - Add makefile and configuration for pylint ------------------------------------------------------------------- Wed Feb 27 12:57:48 CET 2019 - jgonzalez@suse.com - version 4.0.4-1 - Sync with Spacewalk - make filemod more readable - 1665858 - diff expects string not bytes ------------------------------------------------------------------- Mon Dec 17 14:31:58 CET 2018 - jgonzalez@suse.com - version 4.0.3-1 - Fix python2 compilation on openSUSE ------------------------------------------------------------------- Fri Oct 26 09:52:13 CEST 2018 - jgonzalez@suse.com - version 4.0.2-1 - use rpm for debian packaging ------------------------------------------------------------------- Fri Aug 10 16:00:11 CEST 2018 - jgonzalez@suse.com - version 4.0.1-1 - Bump version to 4.0.0 (bsc#1104034) - Rename package to mgr-cfg to allow version 4.0.0 (bsc#1104034) - Fix copyright for the package specfile (bsc#1103696) ------------------------------------------------------------------- Wed May 23 08:59:19 CEST 2018 - jgonzalez@suse.com - version 5.10.122.2-1 - format the file mode in unified way(bsc#1093529) ------------------------------------------------------------------- Mon Mar 26 08:36:31 CEST 2018 - jgonzalez@suse.com - version 5.10.122.1-1 - Sync with upstream (bsc#1083294) - Build python2 packages on SUSE systems ------------------------------------------------------------------- Mon Mar 05 08:39:27 CET 2018 - jgonzalez@suse.com - version 5.10.121.2-1 - remove empty clean section from spec (bsc#1083294) ------------------------------------------------------------------- Fri Feb 23 10:43:55 CET 2018 - jgonzalez@suse.com - version 5.10.121.1-1 - Sync with upstream ------------------------------------------------------------------- Fri Feb 02 11:54:33 CET 2018 - jgonzalez@suse.com - version 5.10.119.2-1 - Improve webui for comparing files (bsc#1076201) ------------------------------------------------------------------- Wed Jan 17 11:10:05 CET 2018 - jgonzalez@suse.com - version 5.10.119.1-1 - Fix update mechanism when updating the updateservice (bsc#1073619) - Add --config option to rhncfg-manager and rhncfg-client - Add better handling of interrupted system calls ------------------------------------------------------------------- Fri Nov 10 16:22:55 CET 2017 - mc@suse.de - version 5.10.117.1-1 - rhncfg: add missing dirs to filelist ------------------------------------------------------------------- Thu Oct 26 17:14:23 CEST 2017 - mc@suse.de - version 5.10.116.1-1 - move files into proper python2/python3 subpackages - store output in the action file so partial output can arrive to server - print different message if file does not exist - print a name of file which does not exist during diff - tell user which file differs - add password config option to rhncfg-manager - execute remote commands in clean environment ------------------------------------------------------------------- Tue May 23 07:55:23 CEST 2017 - mc@suse.de - version 5.10.103.2-1 - Add password config option to rhncfg-manager. - require rhnlib version with i18n module (bsc#1038483) ------------------------------------------------------------------- Wed May 03 16:43:52 CEST 2017 - michele.bologna@suse.com - version 5.10.103.1-1 ------------------------------------------------------------------- Fri Mar 31 10:00:10 CEST 2017 - mc@suse.de - version 5.10.102.2-1 - fix missing import in rhncfg ------------------------------------------------------------------- Tue Mar 07 16:01:22 CET 2017 - mc@suse.de - version 5.10.102.1-1 - Updated links to github in spec files - Symlink target overwritten when the symlink is replaced by a file managed by rhncfg-client - require spacewalk-usix indead of spacewalk-backend-usix ------------------------------------------------------------------- Wed Jan 11 15:26:05 CET 2017 - michele.bologna@suse.com - version 5.10.100.1-1 - Version bump to 5.10.100 ------------------------------------------------------------------- Mon Mar 21 16:40:54 CET 2016 - mc@suse.de - version 5.10.88.2-1 - fix file permissions (bsc#970550) ------------------------------------------------------------------- Wed Mar 02 12:14:47 CET 2016 - mc@suse.de - version 5.10.88.1-1 - fixing removing of temporary files during transaction rollback for rhncfg-manager. - fixing removing directories which rhncfg-manager didn't create. ------------------------------------------------------------------- Mon Nov 30 11:16:55 CET 2015 - mc@suse.de - version 5.10.87.1-1 - python 2.4 compatibility - remove temporary files when exception occurs ------------------------------------------------------------------- Wed Oct 07 15:51:28 CEST 2015 - mc@suse.de - version 5.10.85.1-1 - make rhncfg support sha256 and use it by default - fix for assigning all groups user belongs to running process - show server modified time with rhncfg-client diff ------------------------------------------------------------------- Tue Mar 31 14:51:34 CEST 2015 - mc@suse.de - version 5.10.65.11-1 - fix config diff - Avoid addition of None and str (bsc#920897) ------------------------------------------------------------------- Tue Feb 03 12:03:24 CET 2015 - mc@suse.de - version 5.10.65.10-1 - Normalize path sooner - Fix directory creation - Getting rid of Tabs and trailing spaces ------------------------------------------------------------------- Thu Dec 04 13:22:40 CET 2014 - mc@suse.de - version 5.10.65.9-1 - fix compare config files by checking permissions on the correct file (bsc#900498) ------------------------------------------------------------------- Fri Nov 07 13:25:00 CET 2014 - mc@suse.de - version 5.10.65.8-1 - fix errror in rhncfg if selinux off ------------------------------------------------------------------- Fri Sep 12 16:06:20 CEST 2014 - mc@suse.de - version 5.10.65.7-1 - validate the content of config files before deploying ------------------------------------------------------------------- Wed Sep 3 01:41:38 CEST 2014 - ro@suse.de - sanitize release line in specfile ------------------------------------------------------------------- Mon Sep 01 17:06:59 CEST 2014 - mc@suse.de - version 5.10.65.6-1 - Avoid traceback with a configfiles upload action with no selinux context - make webui config dir diff work - ensure webui config file diff looks at owner and permissions - make sure webui doesn't say there are diffs if there aren't ------------------------------------------------------------------- Tue Jun 17 10:15:27 CEST 2014 - jrenner@suse.de - version 5.10.65.5-1 - list/elist: allow to specify list of files ------------------------------------------------------------------- Tue May 06 15:23:18 CEST 2014 - mc@suse.de - version 5.10.65.4-1 - fix for assigning all groups user belongs to running process ------------------------------------------------------------------- Thu Apr 10 17:29:31 CEST 2014 - mc@suse.de - version 5.10.65.3-1 - fix uncaught exception in config deploy (bnc#871549) ------------------------------------------------------------------- Thu Apr 10 17:29:01 CEST 2014 - mc@suse.de - fix release in specfile for SLE12 (bnc#872970) ------------------------------------------------------------------- Thu Mar 27 10:07:15 CET 2014 - fcastelli@suse.com - version 5.10.65.2-1 - show server modified time with rhncfg-client diff ------------------------------------------------------------------- Mon Dec 09 16:17:50 CET 2013 - mc@suse.de - version 5.10.65.1-1 - switch to 2.1 ------------------------------------------------------------------- Wed Aug 21 15:46:06 CEST 2013 - mc@suse.de - version 5.10.27.12-1 - rhncfg-* --server-name now overwrites rhncfg-*.conf - unicode support for Remote Command scripts ------------------------------------------------------------------- Wed Jun 12 13:41:14 CEST 2013 - mc@suse.de - version 5.10.27.11-1 - make diffs initiated from another spacewalk server obey display_diff config option ------------------------------------------------------------------- Mon Apr 22 15:54:45 CEST 2013 - mc@suse.de - version 5.10.27.10-1 - simplify rhncfg API (bnc#815460) ------------------------------------------------------------------- Fri Feb 08 11:06:39 CET 2013 - mc@suse.de - version 5.10.27.9-1 - Fix Web UI config diff, which always shows 'binary files differ' - make rhncfg diff output configurable ------------------------------------------------------------------- Mon Oct 15 12:21:31 CEST 2012 - mc@suse.de - version 5.10.27.8-1 - recompile python files only on SUSE ------------------------------------------------------------------- Fri Sep 28 15:57:46 CEST 2012 - mc@suse.de - version 5.10.27.7-1 - precompile python code to prevent verification issues (bnc#776356) - If there's symlink in file deployment path it will be created ------------------------------------------------------------------- Mon Jul 16 15:16:08 CEST 2012 - ug@suse.de - version 5.10.27.6-1 - check symlink not target file existence - value of selinux context is important ------------------------------------------------------------------- Thu Jun 21 11:16:28 CEST 2012 - jrenner@suse.de - version 5.10.27.5-1 ------------------------------------------------------------------- Sun Jun 17 18:07:24 CEST 2012 - mc@suse.de - fixed insecure permissions used for /var/log/rhncfg-actions file CVE-2012-2679 (bnc#766148) ------------------------------------------------------------------- Thu May 31 10:52:19 CEST 2012 - mc@suse.de - version 5.10.27.4-1 - fixed command synopsis ------------------------------------------------------------------- Mon May 14 10:49:40 CEST 2012 - mc@suse.de - version 5.10.27.3-1 - honor rhncfg-specific settings ------------------------------------------------------------------- Fri Apr 27 16:29:34 CEST 2012 - mc@suse.de - version 5.10.27.2-1 - accept server name without protocol ------------------------------------------------------------------- Wed Mar 21 18:00:07 CET 2012 - mc@suse.de - version 5.10.27.1-1 - Bumping package version ------------------------------------------------------------------- Thu Nov 3 19:25:06 CET 2011 - ma@suse.de - Adapt dependencies to renamed packages (bnc#722052) ------------------------------------------------------------------- Thu Sep 15 17:02:28 CEST 2011 - mc@suse.de - do not require selinux on SLES10 ------------------------------------------------------------------- Mon May 16 15:00:51 CEST 2011 - iartarisi@suse.cz - fix staticmethod Syntax Error on systems with python < 2.4 (e.g. RHEL4) (bnc#694028) ------------------------------------------------------------------- Mon May 16 09:14:56 CEST 2011 - iartarisi@suse.cz - fix usage documentation messages for topdir and dest-file (bnc#684390) ------------------------------------------------------------------- Fri Mar 18 13:00:20 CET 2011 - iartarisi@suse.cz - pulled a few commits from the master branch to fix bnc#679716 and a more general problem we had with uploading config files without a SELinux context ------------------------------------------------------------------- Thu Mar 17 15:35:32 CET 2011 - mc@suse.de - debrand rhncfg-manager (bnc#678489) ------------------------------------------------------------------- Sun Jan 30 15:27:43 CET 2011 - mc@suse.de - backport upstrem fixes ------------------------------------------------------------------- Mon Jan 17 13:44:41 CET 2011 - mantel@suse.de - add mgr-* symlinks (BNC #660789) ------------------------------------------------------------------- Wed Nov 3 09:49:28 CET 2010 - mantel@suse.de - fix %dir in filelist ------------------------------------------------------------------- Tue Nov 2 17:30:40 CET 2010 - mantel@suse.de - don't ignore post-build-checks ------------------------------------------------------------------- Wed Sep 15 12:13:17 CEST 2010 - mantel@suse.de - fix post-build-checks ------------------------------------------------------------------- Tue Sep 14 17:47:16 CEST 2010 - mantel@suse.de - Initial release of rhncfg ------------------------------------------------------------------- 07070100000047000081B40000000000000000000000015CDC5C8900003BF2000000000000000000000000000000000000001500000000mgr-cfg/mgr-cfg.spec# # spec file for package mgr-cfg # # 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/ # # package renaming fun :( %define rhn_client_tools spacewalk-client-tools %define rhn_setup spacewalk-client-setup %define rhn_check spacewalk-check %define rhnsd spacewalksd # Old name and version+1 before renaming to mgr-cfg %define oldname rhncfg %define oldversion 5.10.123 # %global rhnroot %{_datadir}/rhn %global rhnconf %{_sysconfdir}/sysconfig/rhn %global client_caps_dir %{rhnconf}/clientCaps.d %if 0%{?fedora} || 0%{?suse_version} > 1320 || 0%{?rhel} >= 8 %global build_py3 1 %global default_py3 1 %endif %if ( 0%{?fedora} && 0%{?fedora} < 28 ) || ( 0%{?rhel} && 0%{?rhel} < 8 ) || 0%{?suse_version} || 0%{?ubuntu} || 0%{?debian} %global build_py2 1 %endif # ------------------------------- Python macros for debian ---------------------------------------- %{!?__python2:%global __python2 /usr/bin/python2} %{!?__python3:%global __python3 /usr/bin/python3} %if %{undefined python2_version} %global python2_version %(%{__python2} -Esc "import sys; sys.stdout.write('{0.major}.{0.minor}'.format(sys.version_info))") %endif %if %{undefined python3_version} %global python3_version %(%{__python3} -Ic "import sys; sys.stdout.write(sys.version[:3])") %endif %if %{undefined python2_sitelib} %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") %endif %if %{undefined python3_sitelib} %global python3_sitelib %(%{__python3} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") %endif # --------------------------- End Python macros for debian ---------------------------------------- %define pythonX %{?default_py3: python3}%{!?default_py3: python2} %if %{_vendor} == "debbuild" # Bash constructs in scriptlets don't play nice with Debian's default shell, dash %global _buildshell /bin/bash %endif Name: mgr-cfg Version: 4.0.8 Provides: %{oldname} = %{oldversion} Obsoletes: %{oldname} < %{oldversion} Release: 1%{?dist} Summary: Spacewalk Configuration Client Libraries License: GPL-2.0-only %if %{_vendor} == "debbuild" Group: admin Packager: Uyuni Project <uyuni-devel@opensuse.org> %else Group: Applications/System %endif Url: https://github.com/uyuni-project/uyuni 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 BuildRequires: docbook-utils Requires: %{pythonX}-%{name} = %{version}-%{release} %if %{_vendor} != "debbuild" %if 0%{?suse_version} # provide rhn directories and no selinux on suse BuildRequires: spacewalk-client-tools %if %{suse_version} >= 1110 # Only on SLES11 Requires: python-selinux %endif %else Requires: libselinux-python %endif %endif %if %{_vendor} == "debbuild" %if 0%{?build_py2} Requires: python-selinux %endif %if 0%{?build_py3} Requires: python3-selinux %endif %endif %description The base libraries and functions needed by all mgr-cfg-* packages. %if 0%{?build_py2} %package -n python2-%{name} Summary: Spacewalk Configuration Client Libraries Group: Applications/System Provides: python-%{name} = %{oldversion} Obsoletes: python-%{name} < %{oldversion} Provides: python-%{oldname} = %{oldversion} Obsoletes: python-%{oldname} < %{oldversion} Requires: %{name} = %{version}-%{release} Requires: python Requires: python2-rhn-client-tools >= 2.8.4 Requires: rhnlib >= 2.8.3 Requires: spacewalk-usix %if 0%{?rhel} && 0%{?rhel} <= 5 Requires: python-hashlib %endif BuildRequires: python %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python-minimal Requires(post): python-minimal %endif %description -n python2-%{name} Python 2 specific files for %{name}. %endif %if 0%{?build_py3} %package -n python3-%{name} Summary: Spacewalk Configuration Client Libraries Group: Applications/System Requires: %{name} = %{version}-%{release} Provides: python3-%{oldname} = %{oldversion} Obsoletes: python3-%{oldname} < %{oldversion} Requires: python3 Requires: python3-rhn-client-tools >= 2.8.4 Requires: python3-rhnlib >= 2.8.3 Requires: python3-spacewalk-usix BuildRequires: python3 %if %{_vendor} != "debbuild" BuildRequires: python3-rpm-macros %endif %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python3-minimal Requires(post): python3-minimal %endif %description -n python3-%{name} Python 3 specific files for %{name}. %endif %package client Summary: Spacewalk Configuration Client Group: Applications/System Provides: %{oldname}-client = %{oldversion} Obsoletes: %{oldname}-client < %{oldversion} Requires: %{name} = %{version}-%{release} Requires: %{pythonX}-%{name}-client = %{version}-%{release} %description client A command line interface to the client features of the Spacewalk Configuration Management system. %if 0%{?build_py2} %package -n python2-%{name}-client Summary: Spacewalk Configuration Client Group: Applications/System Provides: python-%{name}-client = %{oldversion} Obsoletes: python-%{name}-client < %{oldversion} Provides: python-%{oldname}-client = %{oldversion} Obsoletes: python-%{oldname}-client < %{oldversion} Requires: %{name}-client = %{version}-%{release} %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python-minimal Requires(post): python-minimal %endif %description -n python2-%{name}-client Python 2 specific files for %{name}-client. %endif %if 0%{?build_py3} %package -n python3-%{name}-client Summary: Spacewalk Configuration Client Group: Applications/System Provides: python3-%{oldname}-client = %{oldversion} Obsoletes: python3-%{oldname}-client < %{oldversion} Requires: %{name}-client = %{version}-%{release} %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python3-minimal Requires(post): python3-minimal %endif %description -n python3-%{name}-client Python 3 specific files for %{name}-client. %endif %package management Summary: Spacewalk Configuration Management Client Group: Applications/System Provides: %{oldname}-management = %{oldversion} Obsoletes: %{oldname}-management < %{oldversion} Requires: %{name} = %{version}-%{release} Requires: %{pythonX}-%{name}-management = %{version}-%{release} %description management A command line interface used to manage Spacewalk configuration. %if 0%{?build_py2} %package -n python2-%{name}-management Summary: Spacewalk Configuration Management Client Group: Applications/System Provides: python-%{name}-management = %{oldversion} Obsoletes: python-%{name}-management < %{oldversion} Provides: python-%{oldname}-management = %{oldversion} Obsoletes: python-%{oldname}-management < %{oldversion} Requires: %{name}-management = %{version}-%{release} %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python-minimal Requires(post): python-minimal %endif %description -n python2-%{name}-management Python 2 specific files for python2-%{name}-management. %endif %if 0%{?build_py3} %package -n python3-%{name}-management Summary: Spacewalk Configuration Management Client Group: Applications/System Provides: python3-%{oldname}-management = %{oldversion} Obsoletes: python3-%{oldname}-management < %{oldversion} Requires: %{name}-management = %{version}-%{release} %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python3-minimal Requires(post): python3-minimal %endif %description -n python3-%{name}-management Python 2 specific files for python3-%{name}-management. %endif %package actions Summary: Spacewalk Configuration Client Actions Group: Applications/System Provides: %{oldname}-actions = %{oldversion} Obsoletes: %{oldname}-actions < %{oldversion} Requires: %{name} = %{version}-%{release} Requires: %{pythonX}-%{name}-actions = %{version}-%{release} %description actions The code required to run configuration actions scheduled via Spacewalk. %if 0%{?build_py2} %package -n python2-%{name}-actions Summary: Spacewalk Configuration Client Actions Group: Applications/System Provides: python-%{name}-actions = %{oldversion} Obsoletes: python-%{name}-actions < %{oldversion} Provides: python-%{oldname}-actions = %{oldversion} Obsoletes: python-%{oldname}-actions < %{oldversion} Requires: %{name}-actions = %{version}-%{release} Requires: python2-%{name}-client %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python-minimal Requires(post): python-minimal %endif %description -n python2-%{name}-actions Python 2 specific files for python2-%{name}-actions. %endif %if 0%{?build_py3} %package -n python3-%{name}-actions Summary: Spacewalk Configuration Client Actions Group: Applications/System Provides: python3-%{oldname}-actions = %{oldversion} Obsoletes: python3-%{oldname}-actions < %{oldversion} Requires: %{name}-actions = %{version}-%{release} Requires: python3-%{name}-client %if %{_vendor} == "debbuild" # For scriptlets Requires(preun): python3-minimal Requires(post): python3-minimal %endif %description -n python3-%{name}-actions Python 3 specific files for python2-%{name}-actions. %endif %prep %setup -q %build make -f Makefile.rhncfg all %install install -d $RPM_BUILD_ROOT/%{python_sitelib} %if 0%{?build_py2} make -f Makefile.rhncfg install PREFIX=$RPM_BUILD_ROOT ROOT=%{python_sitelib} \ MANDIR=%{_mandir} PYTHONVERSION=%{python_version} %endif %if 0%{?build_py3} install -d $RPM_BUILD_ROOT/%{python3_sitelib} sed -i 's|#!/usr/bin/python|#!/usr/bin/python3|' config_*/*.py actions/*.py make -f Makefile.rhncfg install PREFIX=$RPM_BUILD_ROOT ROOT=%{python3_sitelib} \ MANDIR=%{_mandir} PYTHONVERSION=%{python3_version} %endif mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/spool/rhn mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/log touch $RPM_BUILD_ROOT/%{_localstatedir}/log/rhncfg-actions # create links to default script version %define default_suffix %{?default_py3:-%{python3_version}}%{!?default_py3:-%{python_version}} for i in \ /usr/bin/rhncfg-client \ /usr/bin/rhncfg-manager \ /usr/bin/rhn-actions-control \ ; do ln -s $(basename "$i")%{default_suffix} "$RPM_BUILD_ROOT$i" done ln -s rhncfg-manager $RPM_BUILD_ROOT/%{_bindir}/mgrcfg-manager ln -s rhncfg-client $RPM_BUILD_ROOT/%{_bindir}/mgrcfg-client ln -s rhn-actions-control $RPM_BUILD_ROOT/%{_bindir}/mgr-actions-control %if 0%{?suse_version} %if 0%{?build_py2} %py_compile -O %{buildroot}/%{python_sitelib} %endif %if 0%{?build_py3} %py3_compile -O %{buildroot}/%{python3_sitelib} %endif %endif %post if [ -f %{_localstatedir}/log/rhncfg-actions ] then chown root %{_localstatedir}/log/rhncfg-actions chmod 600 %{_localstatedir}/log/rhncfg-actions fi %if %{_vendor} == "debbuild" # Debian requires: # post: Do bytecompilation after install # preun: Remove any *.py[co] files %if 0%{?build_py2} %post -n python2-%{name} pycompile python2-%{name} -V -3.0 %preun -n python2-%{name} pyclean -p python2-%{name} %post -n python2-%{name}-client pycompile python2-%{name}-client -V -3.0 %preun -n python2-%{name}-client pyclean -p python2-%{name}-client %post -n python2-%{name}-management pycompile python2-%{name}-management -V -3.0 %preun -n python2-%{name}-management pyclean -p python2-%{name}-management %post -n python2-%{name}-actions pycompile python2-%{name}-actions -V -3.0 %preun -n python2-%{name}-actions pyclean -p python2-%{name}-actions %endif %if 0%{?build_py3} %post -n python3-%{name} py3compile python3-%{name} -V -4.0 %preun -n python3-%{name} py3clean -p python3-%{name} %post -n python3-%{name}-client py3compile python3-%{name}-client -V -4.0 %preun -n python3-%{name}-client py3clean -p python3-%{name}-client %post -n python3-%{name}-management py3compile python3-%{name}-management -V -4.0 %preun -n python3-%{name}-management py3clean -p python3-%{name}-management %post -n python3-%{name}-actions py3compile python3-%{name}-actions -V -4.0 %preun -n python3-%{name}-actions py3clean -p python3-%{name}-actions %endif %endif %files %defattr(-,root,root,-) %dir %{_localstatedir}/spool/rhn %doc LICENSE %if 0%{?build_py2} %files -n python2-%{name} %defattr(-,root,root,-) %{python_sitelib}/config_common %endif %if 0%{?build_py3} %files -n python3-%{name} %defattr(-,root,root,-) %{python3_sitelib}/config_common %endif %files client %defattr(-,root,root,-) %{_bindir}/rhncfg-client %{_bindir}/mgrcfg-client %attr(644,root,root) %config(noreplace) %{rhnconf}/rhncfg-client.conf %{_mandir}/man8/rhncfg-client.8* %if 0%{?build_py2} %files -n python2-%{name}-client %defattr(-,root,root,-) %{python_sitelib}/config_client %{_bindir}/rhncfg-client-%{python_version} %endif %if 0%{?build_py3} %files -n python3-%{name}-client %defattr(-,root,root,-) %{python3_sitelib}/config_client %{_bindir}/rhncfg-client-%{python3_version} %endif %files management %defattr(-,root,root,-) %{_bindir}/mgrcfg-manager %{_bindir}/rhncfg-manager %attr(644,root,root) %config(noreplace) %{rhnconf}/rhncfg-manager.conf %{_mandir}/man8/rhncfg-manager.8* %if 0%{?build_py2} %files -n python2-%{name}-management %defattr(-,root,root,-) %{python_sitelib}/config_management %{_bindir}/rhncfg-manager-%{python_version} %endif %if 0%{?build_py3} %files -n python3-%{name}-management %defattr(-,root,root,-) %{python3_sitelib}/config_management %{_bindir}/rhncfg-manager-%{python3_version} %endif %files actions %defattr(-,root,root,-) %{_bindir}/mgr-actions-control %{_bindir}/rhn-actions-control %config(noreplace) %{client_caps_dir}/* %{_mandir}/man8/rhn-actions-control.8* %ghost %attr(600,root,root) %{_localstatedir}/log/rhncfg-actions %if 0%{?build_py2} %files -n python2-%{name}-actions %defattr(-,root,root,-) %dir %{python_sitelib}/rhn %{python_sitelib}/rhn/actions %{_bindir}/rhn-actions-control-%{python_version} %if 0%{?suse_version} %dir %{python_sitelib}/rhn %endif %endif %if 0%{?build_py3} %files -n python3-%{name}-actions %defattr(-,root,root,-) %dir %{python3_sitelib}/rhn %{python3_sitelib}/rhn/actions %{_bindir}/rhn-actions-control-%{python3_version} %if 0%{?suse_version} %dir %{python3_sitelib}/rhn %endif %endif %changelog 07070100000048000081B40000000000000000000000015CDC5C8900001395000000000000000000000000000000000000001100000000mgr-cfg/pylintrc# mgr-cfg 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= 07070100000049000041FD0000000000000000000000015CDC5C8900000000000000000000000000000000000000000000001000000000mgr-cfg/solaris0707010000004A000081FD0000000000000000000000015CDC5C890000073B000000000000000000000000000000000000002100000000mgr-cfg/solaris/build-solaris.sh#!/usr/bin/bash # clean up junk... make clean rm Prototype rm -rf /tmp/RHATrncfg* rm -rf /test-bretm/build-target # where you want stuff to land DEST_PREFIX=/opt/redhat/rhn/solaris export DEST_PREFIX # think of this as the buildroot, should be some temp dir BUILD_ROOT=/test-bretm/build-target export BUILD_ROOT # PREFIX used by the makefiles... PREFIX="$BUILD_ROOT$DEST_PREFIX" export PREFIX INSTALL='/usr/ucb/install -c' export INSTALL LN='ln -sf' export LN rm -rf temp_src mkdir temp_src cp * temp_src/ cp -R actions temp_src/ cp -R config_* temp_src/ cd temp_src perl -pi -e "s|/usr/bin/diff|$DEST_PREFIX/bin/diff|" \ ./config_client/rhncfgcli_diff.py \ ./config_common/file_utils.py \ ./config_management/rhncfg_diff.py perl -pi -e "s|/usr/share/rhn|$DEST_PREFIX/usr/share/rhn|" \ ./config_client/* \ ./config_management/* perl -pi -e "s|/etc/sysconfig/rhn/allowed-actions|$DEST_PREFIX/etc/sysconfig/rhn/allowed-actions|" \ ./actions/configfiles.py perl -pi -e "s|/etc/sysconfig/rhn/systemid|$DEST_PREFIX/etc/sysconfig/rhn/systemid|" \ ./config_client/* perl -pi -e "s|/etc/sysconfig/rhn/up2date|$DEST_PREFIX/etc/sysconfig/rhn/up2date|" \ ./config_common/up2date_config_parser.py perl -pi -e "s|os.path.join\(os.sep|os.path.join\('$DEST_PREFIX'|" \ ./config_common/local_config.py perl -pi -e "s|^#!/usr/bin/python|#!$DEST_PREFIX/bin/python|" \ ./config_client/rhncfg-client.py \ ./config_management/rhncfg-manager.py make make install echo "i pkginfo" > Prototype #echo "i postinstall" >> Prototype #echo "i checkinstall" >> Prototype find $PREFIX -print | pkgproto | sed "s#$PREFIX#$DEST_PREFIX#g" >> Prototype pkgmk -o -r $BUILD_ROOT -d /tmp -f Prototype cd /tmp # tar -cf - RHATrncfg | gzip -9 -c > RHATrncfg-1.0.sparc.pkg.tar.gz pkgtrans -s /tmp RHATrncfg-1.0.pkg RHATrncfg 0707010000004B000081B40000000000000000000000015CDC5C89000000A6000000000000000000000000000000000000001800000000mgr-cfg/solaris/pkginfoPKG="RHATrncfg" NAME="Spacewalk configuration client" VERSION="1.0" ARCH="sparc" CLASSES="none" CATEGORY="utility" VENDOR="RHAT" EMAIL="bretm@redhat.com" BASEDIR="/" 0707010000004C000041FD0000000000000000000000015CDC5C8900000000000000000000000000000000000000000000000D00000000mgr-cfg/test0707010000004D000081B40000000000000000000000015CDC5C89000009D9000000000000000000000000000000000000002300000000mgr-cfg/test/test_action_deploy.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 os import fnmatch from actions import configfiles from config_common import local_config, rhn_log local_config.init("rhncfg") rhn_log.set_debug_level(local_config.get('debug')) def test(msg, test): ok_str = 'NOT OK' if test: ok_str = 'ok' print("%s: %s" % (msg, ok_str)) def stray_files(path): (directory, filename) = os.path.split(path) for path in os.listdir(directory): if fnmatch.fnmatch(path, "%s*" % filename): return 1 return 0 files = { 'files' : [ { 'path' : "/etc/googah", 'namespace' : "foo1", 'file_contents' : "This is 1 and this is 2\nAnd this is 3", 'md5sum' : "0abcabcabcabc", 'delim_start' : "[|", 'delim_end' : "|]", }, { 'path' : "/var/tmp/ggg", 'namespace' : "foo1", 'file_contents' : "That is 1 and this is 2\nAnd this is 3", 'md5sum' : "0abcabcabcabc", 'delim_start' : "[|", 'delim_end' : "|]", }, ]} try: for file in files['files']: os.unlink(file['path']) except OSError: pass test("testing a clean new deployment", configfiles.deploy(files)[0] == 0) file = files['files'][0]['path'] os.system("chattr +i %s" % file) test("testing deployment over read-only file (no files deployed, remove backups)", configfiles.deploy(files)[0] == 43) os.system("chattr -i %s" % file) test("testing redeployment w/ backups", configfiles.deploy(files)[0] == 0) file = files['files'][1]['path'] os.system("chattr +i %s" % file) test("testing deployment over read-only file (partial deployment, restore from then remove backups)", configfiles.deploy(files)[0] == 43 and os.path.exists(file)) os.system("chattr -i %s" % file) test("testing redeployment w/ backups (again)", configfiles.deploy(files)[0] == 0) 0707010000004E000081B40000000000000000000000015CDC5C89000005D8000000000000000000000000000000000000002100000000mgr-cfg/test/test_action_diff.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. # from actions import configfiles print(configfiles.diff([ { 'path' : "/etc/googah", 'namespace' : "foo1", 'file_contents' : "This is 1 and this is 2\nAnd this is 3", 'md5sum' : "0abcabcabcabc", 'delim_start' : "[|", 'delim_end' : "|]", }, { 'path' : "/var/tmp/ggg", 'namespace' : "foo1", 'file_contents' : "That is 1 and this is 2\nAnd this is 3", 'md5sum' : "0abcabcabcabc", 'delim_start' : "[|", 'delim_end' : "|]", }, { 'path' : "/etc/issue", 'namespace' : "foo1", 'file_contents' : "Red Hat Linux release 7.3 Evaluation (Valhalla)\nKernel \\r on an \\m", 'md5sum' : "0abcabcabcabc", 'delim_start' : "[|", 'delim_end' : "|]", }, ])) 0707010000004F000081B40000000000000000000000015CDC5C8900000392000000000000000000000000000000000000002300000000mgr-cfg/test/test_action_script.py#!/usr/bin/python # # 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. # from actions import script print(script.run(1, { 'username' : 'misa', 'groupname' : 'misa', 'now' : '2004-02-20 09:00:00', 'script' : """\ #!/usr/bin/python import sys import time sys.stdout.write("1\\n") sys.stderr.write("2\\n") time.sleep(10) """, })) 07070100000050000081B40000000000000000000000015CDC5C89000002EE000000000000000000000000000000000000002300000000mgr-cfg/test/test_action_upload.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. # from actions import configfiles from config_common import local_config local_config.init('rhncfgcli') print(configfiles.upload("foo")) 07070100000051000081B40000000000000000000000015CDC5C89000003D0000000000000000000000000000000000000001E00000000mgr-cfg/test/test_maketemp.py#!/usr/bin/python # # 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 os from config_common import file_utils filepath, fd = file_utils.maketemp("/tmp/my-file-", mode=int("0700", 8)) buf = "0123456789" * 100 print("Writing", len(buf), "to", filepath) os.write(fd, buf) assert(len(buf) == f.tell()) os.close(fd) os.unlink(filepath) filepath, fd = file_utils.maketemp() os.close(fd) os.unlink(filepath) 07070100000052000081B40000000000000000000000015CDC5C89000006E3000000000000000000000000000000000000001C00000000mgr-cfg/test/test_upload.py#!/usr/bin/python # # 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. # from rhn import rpclib #systemid_file = '/home/devel/misa/playpen/systemid-devel' systemid_file = '/home/devel/misa/cvs/rhn/test/backend/checks/systemid-farm06' server = "coyote.devel.redhat.com" #server = "rhnxml.back-webdev.redhat.com" s = rpclib.Server("http://%s/CONFIG-MANAGEMENT" % server) files = [ { 'path' : '/etc/motd', 'file_contents' : 'This system will not work today\nCause I like it so\n', 'delim_start' : '{|', 'delim_end' : '|}', 'file_stat' : { 'size' : 1234, 'mode' : int("0755", 8), 'user' : 'misa', 'group' : 'misa', }, }, { 'path' : '/etc/voodoo', 'file_contents' : 'If you read this file, your computer will reboot', 'delim_start' : '{|', 'delim_end' : '|}', 'file_stat' : { 'size' : 1234, 'mode' : int("0755", 8), 'user' : 'misa', 'group' : 'misa', }, }, ] systemid = open(systemid_file).read() s.config.client.upload_files(systemid, 11921200, files) 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor