Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP2
spacecmd.12115
spacecmd-git-0.d70ab50.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File spacecmd-git-0.d70ab50.obscpio of Package spacecmd.12115
07070100000000000041FD0000000000000000000000015D65A5B200000000000000000000000000000000000000000000000900000000spacecmd07070100000001000081B40000000000000000000000015D65A5B200000010000000000000000000000000000000000000001400000000spacecmd/.gitignore*~ *.pyc *.swp 07070100000002000081B40000000000000000000000015D65A5B20000048A000000000000000000000000000000000000001900000000spacecmd/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 __pytest :: $(call update_pip_env) $(call install_pytest) $(call install_by_setup, '.') cd tests pytest --disable-warnings --tb=native --color=yes -v docker_pylint :: docker run --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/sh -c "cd /manager/spacecmd; make -f Makefile.python __pylint" docker_pytest :: docker run --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/sh -c "cd /manager/spacecmd; make -f Makefile.python __pytest" docker_shell :: docker run -t -i --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/bash 07070100000003000081B40000000000000000000000015D65A5B200000359000000000000000000000000000000000000001B00000000spacecmd/Makefile.spacecmdTHIS_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=/manager/client/rhel/rhnlib/:/manager/client/rhel/rhn-client-tools/src" DOCKER_VOLUMES = -v "$(CURDIR)/../:/manager" __pylint :: $(call update_pip_env) pylint --rcfile=spacecmd-pylintrc src/* > reports/pylint.log || : docker_pylint :: docker run --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/sh -c "cd /manager/spacecmd; make -f Makefile.spacecmd __pylint" docker_shell :: docker run -t -i --rm -e $(DOCKER_RUN_EXPORT) $(DOCKER_VOLUMES) $(DOCKER_REGISTRY)/$(DOCKER_CONTAINER_BASE)-pgsql /bin/bash 07070100000004000081B40000000000000000000000015D65A5B200001396000000000000000000000000000000000000001200000000spacecmd/pylintrc# spacecmd 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= 07070100000005000081B40000000000000000000000015D65A5B20000032B000000000000000000000000000000000000001200000000spacecmd/setup.py# coding: utf-8 """ Setup file. """ import os from setuptools import setup, find_packages def get_version_changelog(): """ Get a version from the current changelog. """ changelog = None version = "4.0.14" for fname in os.listdir(os.path.dirname(os.path.abspath(__file__))): if fname.endswith(".changes"): changelog = fname break if changelog: with open(changelog, "r") as hcl: for line in hcl.readlines(): if "version" in line: version = line.split(" ")[-1] # Typically version is the last one break return version setup( name='spacecmd', version=get_version_changelog(), packages=find_packages(where="src"), package_dir={ "": "src", } ) 07070100000006000081B40000000000000000000000015D65A5B20000138F000000000000000000000000000000000000001B00000000spacecmd/spacecmd-pylintrc# spacewalk 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= 07070100000007000081B40000000000000000000000015D65A5B200005178000000000000000000000000000000000000001A00000000spacecmd/spacecmd.changes------------------------------------------------------------------- Tue Aug 27 23:49:29 CEST 2019 - jgonzalez@suse.com - version 4.0.14-1 - Fix missing runtime dependencies that made spacecmd return old versions of packages in some cases, even if newer ones were available (bsc#1148311) ------------------------------------------------------------------- Thu Jun 06 15:58:50 CEST 2019 - jgonzalez@suse.com - version 4.0.13-1 - Bugfix: referenced variable before assignment. ------------------------------------------------------------------- Thu May 30 10:54:16 CEST 2019 - jgonzalez@suse.com - version 4.0.12-1 - Bugfix: 'dict' object has no attribute 'iteritems' (bsc#1135881) - Add unit tests for custominfo, snippet, scap, ssm, cryptokey and distribution ------------------------------------------------------------------- Wed May 15 15:05:51 CEST 2019 - jgonzalez@suse.com - version 4.0.11-1 - SPEC cleanup ------------------------------------------------------------------- Mon Apr 22 12:05:58 CEST 2019 - jgonzalez@suse.com - version 4.0.10-1 - add unit tests for spacecmd.api, spacecmd.activationkey and spacecmd.filepreservation - add unit tests for spacecmd.shell - Save SSM list on system delete and update cache (bsc#1130077, bsc#1125744) - add makefile and pylint configuration ------------------------------------------------------------------- Mon Mar 25 16:40:32 CET 2019 - jgonzalez@suse.com - version 4.0.9-1 - Add Pylint setup - Replace iteritems with items for python2/3 compat (bsc#1129243) ------------------------------------------------------------------- Mon Mar 04 09:53:28 CET 2019 - jgonzalez@suse.com - version 4.0.8-1 - fix python 3 bytes issue when handling config channels ------------------------------------------------------------------- Sat Mar 02 00:09:22 CET 2019 - jgonzalez@suse.com - version 4.0.7-1 - Add '--force', '-f' option to regenerateYumCache (bsc#1127389) ------------------------------------------------------------------- Wed Feb 27 12:59:05 CET 2019 - jgonzalez@suse.com - version 4.0.6-1 - Prevent spacecmd crashing when piping the output in Python 3 (bsc#1125610) ------------------------------------------------------------------- Thu Jan 31 16:34:26 CET 2019 - jgonzalez@suse.com - version 4.0.5-1 - Fix compatibility with Python 3 ------------------------------------------------------------------- Wed Jan 16 12:16:54 CET 2019 - jgonzalez@suse.com - version 4.0.4-1 - Fix importing state channels using configchannel_import - Fix getting file info for latest revision (via configchannel_filedetails) ------------------------------------------------------------------- Mon Dec 17 14:33:04 CET 2018 - jgonzalez@suse.com - version 4.0.3-1 - Add function to merge errata and packages through spacecmd (bsc#987798) - show group id on group_details (bsc#1111542) - State channels handling: Existing commands configchannel_create and configchannel_import were updated while system_scheduleapplyconfigchannels and configchannel_updateinitsls were added. ------------------------------------------------------------------- Fri Oct 26 10:03:04 CEST 2018 - jgonzalez@suse.com - version 4.0.2-1 - add summary to softwarechannel.clone when calling older API versions (bsc#1109023) - New function/Update old functions to handle state channels as well ------------------------------------------------------------------- Fri Aug 10 15:10:30 CEST 2018 - jgonzalez@suse.com - version 4.0.1-1 - Bump version to 4.0.0 (bsc#1104034) - Fix copyright for the package specfile (bsc#1103696) - Suggest not to use password option for spacecmd (bsc#1103090) ------------------------------------------------------------------- Wed May 23 09:00:54 CEST 2018 - jgonzalez@suse.com - version 2.8.25.4-1 - add option to set cleanup type for system_delete (bsc#1094190) ------------------------------------------------------------------- Wed May 16 17:20:02 CEST 2018 - jgonzalez@suse.com - version 2.8.25.3-1 - Sync with upstream (bsc#1083294) ------------------------------------------------------------------- Mon Apr 23 08:55:55 CEST 2018 - jgonzalez@suse.com - version 2.8.25.2-1 - Sync with upstream (bsc#1083294) - 1539878 - add save_cache to do_ssm_intersect - Fix softwarechannel_listsyncschedule ------------------------------------------------------------------- Wed Apr 04 14:29:37 CEST 2018 - jgonzalez@suse.com - version 2.8.21.2-1 - Disable pylint for python2 and RES < 8 (bsc#1088070) ------------------------------------------------------------------- Mon Mar 26 08:42:35 CEST 2018 - jgonzalez@suse.com - version 2.8.21.1-1 - Sync with upstream (bsc#1083294) - Connect to API using FQDN instead of hostname to avoid SSL validation problems (bsc#1085667) ------------------------------------------------------------------- Mon Mar 05 08:41:11 CET 2018 - jgonzalez@suse.com - version 2.8.20.1-1 - 1536484 - Command spacecmd supports utf8 name of systems - 1484056 - updatefile and addfile are basically same calls - 1484056 - make configchannel_addfile fully non-interactive - 1445725 - display all checksum types, not just MD5 - remove clean section from spec (bsc#1083294) - Added function to update software channel. Moreover, some refactoring has been done(bsc#1076578) ------------------------------------------------------------------- Fri Feb 23 12:14:59 CET 2018 - jgonzalez@suse.com - version 2.8.17.2-1 - add more python3 compatibility changes ------------------------------------------------------------------- Fri Feb 23 10:30:03 CET 2018 - jgonzalez@suse.com - version 2.8.17.1-1 - Compatibility with Python 3 - Fix typo (bsc#1081151) - Configure gpg_flag via spacecmd creating a channel (bsc#1080290) ------------------------------------------------------------------- Mon Feb 05 12:44:39 CET 2018 - jgonzalez@suse.com - version 2.8.15.3-1 - Allow scheduling the change of software channels as an action. The previous channels remain accessible to the registered system until the action is executed. to the registered system until the action is executed. ------------------------------------------------------------------- Fri Feb 02 11:58:16 CET 2018 - jgonzalez@suse.com - version 2.8.15.2-1 - support multiple FQDNs per system (bsc#1063419) ------------------------------------------------------------------- Wed Jan 17 17:34:42 CET 2018 - jgonzalez@suse.com - version 2.8.13.2-1 - Fix bsc number for change 'configchannel export binary flag to json' ------------------------------------------------------------------- Wed Jan 17 11:14:54 CET 2018 - jgonzalez@suse.com - version 2.8.13.1-1 - add --config option to spacecmd - Added custom JSON encoder in order to parse date fields correctly (bsc#1070372) ------------------------------------------------------------------- Fri Nov 10 16:28:48 CET 2017 - mc@suse.de - version 2.8.10.1-1 - pylint - fix intendation ------------------------------------------------------------------- Thu Oct 26 17:00:51 CEST 2017 - mc@suse.de - version 2.8.9.1-1 - fix build with python 3 - show list of arches for channel - allow softwarechannel_setsyncschedule to disable schedule - add softwarechannel_setsyncschedule --latest - in case of system named by id, let id take precedence - Make spacecmd prompt for password when overriding config file user - show less output of common packages in selected channels - adding softwarechannel_listmanageablechannels ------------------------------------------------------------------- Wed Aug 30 16:05:32 CEST 2017 - mc@suse.de - version 2.7.8.7-1 - Switched logging from warning to debug ------------------------------------------------------------------- Tue Aug 08 11:06:21 CEST 2017 - fkobzik@suse.de - version 2.7.8.6-1 - configchannel export binary flag to json (bsc#1044719) ------------------------------------------------------------------- Mon Jun 12 09:10:45 CEST 2017 - mc@suse.de - version 2.7.8.5-1 - spacecmd report_outofdatesystems: avoid one XMLRPC call per system (bsc#1015882) ------------------------------------------------------------------- Mon May 29 15:38:31 CEST 2017 - mc@suse.de - version 2.7.8.4-1 - Remove debug logging from softwarechannel_sync function ------------------------------------------------------------------- Tue May 23 07:59:56 CEST 2017 - mc@suse.de - version 2.7.8.3-1 - Remove get_certificateexpiration support in spacecmd (bsc#1013876) ------------------------------------------------------------------- Wed May 03 15:58:13 CEST 2017 - michele.bologna@suse.com - version 2.7.8.2-1 - Adding softwarechannel_listmanageablechannels ------------------------------------------------------------------- Mon Apr 03 14:55:20 CEST 2017 - mc@suse.de - version 2.7.8.1-1 - fix syntax error ------------------------------------------------------------------- Fri Mar 31 09:53:45 CEST 2017 - mc@suse.de - version 2.7.7.1-1 - make sure to know if we get into default function and exit accordingly ------------------------------------------------------------------- Tue Mar 07 15:13:15 CET 2017 - mc@suse.de - version 2.7.6.1-1 - exit with 1 with incorrect command, wrong server, etc. - Updated links to github in spec files - print also systemdid with system name - improve output on error for listrepo (bsc#1027426) - print profile_name instead of string we're searching for - Fix: reword spacecmd removal msg (bsc#1024406) - Fix interactive mode - Add a type parameter to repo_create ------------------------------------------------------------------- Tue Feb 07 15:11:39 CET 2017 - michele.bologna@suse.com - version 2.7.3.2-1 - Removed obsolete code (bsc#1013938) ------------------------------------------------------------------- Wed Jan 11 15:44:53 CET 2017 - michele.bologna@suse.com - version 2.7.3.1-1 - Version 2.7.3-1 ------------------------------------------------------------------- Mon Nov 07 11:30:38 CET 2016 - michele.bologna@suse.com - version 2.5.5.3-1 - Make exception class more generic and code fixup (bsc#1003449) - Handle exceptions raised by listChannels (bsc#1003449) - Alert if a non-unique package ID is detected ------------------------------------------------------------------- Tue May 24 14:46:06 CEST 2016 - kwalter@suse.com - version 2.5.5.2-1 - make spacecmd createRepo compatible with SUSE Manager 2.1 API (bsc#977264) ------------------------------------------------------------------- Wed Feb 10 08:40:17 CET 2016 - mc@suse.de - version 2.5.5.1-1 - mimetype detection to set the binary flag requires 'file' tool - Text description missing for remote command by Spacecmd ------------------------------------------------------------------- Tue Jan 26 14:02:05 CET 2016 - mc@suse.de - version 2.5.2.1-1 - spacecmd: repo_details show 'None' if repository doesn't have SSL Certtificate - spacecmd: Added functions to add/edit SSL certificates for repositories ------------------------------------------------------------------- Tue Jan 05 15:54:16 CET 2016 - mc@suse.de - version 2.5.1.2-1 - build spacecmd noarch only on new systems ------------------------------------------------------------------- Mon Nov 30 11:02:34 CET 2015 - mc@suse.de - version 2.5.1.1-1 - mimetype detection to set the binary flag requires 'file' tool - fix export/cloning: always base64 - Always base64 encode to avoid trim() bugs in the XML-RPC library. ------------------------------------------------------------------- Thu Nov 19 14:07:08 UTC 2015 - dmacvicar@suse.de - set binary mode on uploaded files based on content (bsc#948245) ------------------------------------------------------------------- Wed Oct 07 14:26:44 CEST 2015 - mc@suse.de - version 2.5.0.1-1 - drop monitoring - replace upstream subscription counting with new subscription matching (FATE#311619) ------------------------------------------------------------------- Wed Sep 23 15:05:03 CEST 2015 - mc@suse.de - version 2.1.25.10-1 - Revert "1207606 - do not return one package multiple times" (bsc#945380) - check for existence of device description in spacecmd system_listhardware (bsc#932288) ------------------------------------------------------------------- Mon Jun 22 15:57:11 CEST 2015 - jrenner@suse.de - version 2.1.25.9-1 - do not escape spacecmd command arguments - do not return one package multiple times - add system_setcontactmethod (FATE#314858) - add activationkey_setcontactmethod (FATE#314858) - show contact method with activationkey_details and system_details - clone config files without loosing trailing new lines (bsc#926318) ------------------------------------------------------------------- Tue Mar 31 14:38:08 CEST 2015 - mc@suse.de - version 2.1.25.8-1 - sanitize data from export ------------------------------------------------------------------- Thu Jan 29 15:51:44 CET 2015 - mc@suse.de - version 2.1.25.7-1 - fix configchannel export - do not create 'contents' key for directories (bsc#908849) - fix patch summary printing - code cleanup - add new function kickstart_getsoftwaredetails - Added feature to get installed packageversion of a system or systems managed by ssm to spacecmd ------------------------------------------------------------------- Thu Dec 04 13:27:13 CET 2014 - mc@suse.de - version 2.1.25.6-1 - call listAutoinstallableChannels() for listing distributions (bsc#887879) - Fix spacecmd schedule listing (bsc#902494) - Teach spacecmd report_errata to process all-errata in the absence of further args ------------------------------------------------------------------- Tue Dec 2 15:13:52 CET 2014 - mc@suse.de - fix call of setCustomOptions() during kickstart_importjson (bsc#879904) ------------------------------------------------------------------- Fri Nov 07 13:10:33 CET 2014 - mc@suse.de - version 2.1.25.5-1 - spacecmd: fix listupgrades [bnc#892707] ------------------------------------------------------------------- Fri Aug 01 10:14:56 CEST 2014 - mc@suse.de - version 2.1.25.4-1 - make print_result a static method of SpacewalkShell (bnc#889605) ------------------------------------------------------------------- Tue Jun 17 10:29:00 CEST 2014 - jrenner@suse.de - version 2.1.25.3-1 - Added option to force deployment of a config channel to all subscribed systems - Added last boot message in system_details command - Updated kickstart_import documentation - Added kickstart_import_raw command ------------------------------------------------------------------- Tue May 06 15:14:55 CEST 2014 - mc@suse.de - version 2.1.25.2-1 - set output encoding when stdout is not a tty ------------------------------------------------------------------- Thu Feb 27 15:35:56 CET 2014 - fcastelli@suse.com - version 2.1.25.1-1 - make file_needs_b64_enc work for both str and unicode inputs ------------------------------------------------------------------- Thu Feb 13 15:34:44 CET 2014 - mc@suse.de - version 2.1.24.1-1 - Updating the copyright years info ------------------------------------------------------------------- Mon Jan 13 09:42:30 CET 2014 - mc@suse.de - version 2.1.22.1-1 - fix spacecmd, so it does not expect package id within the system.listPackages API call - fix binary file detection - added function package_listdependencies ------------------------------------------------------------------- Wed Dec 18 13:51:21 CET 2013 - mc@suse.de - version 2.1.20.1-1 - don't attempt to write out 'None' - fix system listing when identified by system id ------------------------------------------------------------------- Mon Dec 09 16:42:53 CET 2013 - mc@suse.de - version 2.1.18.1-1 - switch to 2.1 ------------------------------------------------------------------- Wed Aug 21 15:54:06 CEST 2013 - mc@suse.de - version 1.7.7.11-1 - fixing spacecmd ssm 'list' has no attribute 'keys' error ------------------------------------------------------------------- Wed Jun 12 13:37:52 CEST 2013 - mc@suse.de - version 1.7.7.10-1 - spacecmd errors out when trying to add script to kickstart - Make spacecmd able to specify config channel label ------------------------------------------------------------------- Thu Apr 04 15:29:13 CEST 2013 - mc@suse.de - version 1.7.7.9-1 - fix directory export in configchannel_export - use 755 as default permissions for directories in configfile_getinfo - fix directory creation in configchannel_addfile - print the list of systems in system_runscript - print the list of systems in system_reboot - return a unique set from expand_systems - print a clearer error message when duplicate system names are found - standardize the behavior for when a system ID is not returned - add a delay before regenerating the system cache after a delete - handle binary files correctly in configfile_getinfo - print the name in the confirmation message of snippet_create - don't reuse variable names in parse_arguments - print the function's help message when -h in the argument list - print file path in package_details - fixing broken export of configchannels with symlinks ------------------------------------------------------------------- Fri Sep 28 16:15:30 CEST 2012 - mc@suse.de - version 1.7.7.8-1 - prevent outputting escape sequences to non-terminals - Fixed small typo in spacecmd/src/lib/kickstart.py - do not quote argument of the help command (bnc#776615) ------------------------------------------------------------------- Mon Jul 16 15:27:39 CEST 2012 - ug@suse.de - version 1.7.7.7-1 - Fix kickstart_export with old API versions ------------------------------------------------------------------- Fri Jul 6 09:46:42 CEST 2012 - ug@suse.de - command line parameter for "distribution path" was documented wrong in help text (bnc#769106) ------------------------------------------------------------------- Thu Jul 5 11:34:41 CEST 2012 - ug@suse.de - "suse" was missing in the helptext of the CLI for distributions (bnc#769108) ------------------------------------------------------------------- Mon Jun 25 10:23:03 CEST 2012 - mc@suse.de - version 1.7.7.6-1 - enhancement add configchannel_sync - enhancement add softwarechannel_sync ------------------------------------------------------------------- Thu Jun 21 11:19:29 CEST 2012 - jrenner@suse.de - version 1.7.7.5-1 - fixing chroot option for addscript ------------------------------------------------------------------- Thu May 31 10:53:52 CEST 2012 - mc@suse.de - version 1.7.7.4-1 - kickstart_getcontents fix character encoding error - activationkey_import don't add empty package/group lists - fix activationkey_import when no base-channel specified - Fix reference to non-existent variable - improve configchannel_export operation on old API versions - *diff functions allow python 2.4 compatibility - changed get_string_diff_dicts to better fitting replacement method - remove reference to stage function - add do_SPACEWALKCOMPONENT_diff functions - system_comparewithchannel filter system packagelist - argument validation needed for configchannel_addfile - configchannel_addfile don't display b64 file contents ------------------------------------------------------------------- Fri Apr 27 16:11:14 CEST 2012 - mc@suse.de - version 1.7.7.3-1 - enhancement add system_addconfigfile - Fix usage for configchannel_addfile - enhancement Add system_listconfigfiles - add option to allow templating for spacecmd kickstarting ------------------------------------------------------------------- Fri Mar 30 15:00:21 CEST 2012 - mc@suse.de - version 1.7.7.2-1 - softwarechannel_clone avoid ISE on duplicate name - softwarechannel_adderrata mergeErrata should be cloneErrataAsOriginal - Add globbing support to distribution_details - Add globbing support to distribution_delete - Cleanup some typos in comments - custominfo_details add support for globbing key names - custominfo_deletekey add support for globbing key names - Add cryptokey_details globbing support - cryptokey_delete add support for globbing - Workaround missing date key in recent spacewalk listErrata - Add validation to softwarechannel_adderrata channel args - softwarechannel_adderrata add --skip mode - Add --quick mode to softwarechannel_adderrata - Allow config-channel export of b64 encoded files - Update the spacecmd copyright years ------------------------------------------------------------------- Wed Mar 21 17:47:00 CET 2012 - mc@suse.de - version 1.7.7.1-1 - Bumping package version ------------------------------------------------------------------- Fri Feb 11 16:24:52 CET 2011 - mantel@suse.de - debranding ------------------------------------------------------------------- Sun Jan 30 15:31:06 CET 2011 - mc@suse.de - backport upstrem fixes ------------------------------------------------------------------- Wed Sep 15 08:32:37 CEST 2010 - mantel@suse.de - Initial release of spacecmd ------------------------------------------------------------------- 07070100000008000081B40000000000000000000000015D65A5B2000010AA000000000000000000000000000000000000001700000000spacecmd/spacecmd.spec# # spec file for package spacecmd # # Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # Copyright (c) 2008-2018 Red Hat, Inc. # Copyright (c) 2011 Aron Parsons <aronparsons@gmail.com> # # 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/ # %if ! (0%{?fedora} || 0%{?rhel} > 5) %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} %{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} %endif %if 0%{?fedora} || 0%{?rhel} >= 8 %{!?pylint_check: %global pylint_check 1} %endif %if 0%{?fedora} || 0%{?suse_version} > 1320 %global build_py3 1 %global python_sitelib %{python3_sitelib} %endif Name: spacecmd Version: 4.0.14 Release: 1%{?dist} Summary: Command-line interface to Spacewalk and Red Hat Satellite servers License: GPL-3.0-or-later Group: Applications/System Url: https://github.com/uyuni-project/uyuni Source: https://github.com/spacewalkproject/spacewalk/archive/%{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-build %if 0%{?fedora} || 0%{?rhel} || 0%{?suse_version} >= 1210 BuildArch: noarch %endif %if 0%{?pylint_check} %if 0%{?build_py3} BuildRequires: spacewalk-python3-pylint %else BuildRequires: spacewalk-python2-pylint %endif %endif %if 0%{?build_py3} BuildRequires: python3 BuildRequires: python3-devel Requires: python3-rpm Requires: python3-simplejson Requires: python3 %else BuildRequires: python BuildRequires: python-devel Requires: python-simplejson Requires: rpm-python Requires: python %if 0%{?suse_version} BuildRequires: python-xml Requires: python-xml %endif %endif %if 0%{?rhel} == 5 BuildRequires: python-json %endif %if 0%{?rhel} == 5 Requires: python-simplejson %endif Requires: file %description spacecmd is a command-line interface to Spacewalk and Red Hat Satellite servers %prep %setup -q %build # nothing to build %install %{__mkdir_p} %{buildroot}/%{_bindir} %if 0%{?build_py3} sed -i 's|#!/usr/bin/python|#!/usr/bin/python3|' ./src/bin/spacecmd %endif %{__install} -p -m0755 src/bin/spacecmd %{buildroot}/%{_bindir}/ %{__mkdir_p} %{buildroot}/%{_sysconfdir} touch %{buildroot}/%{_sysconfdir}/spacecmd.conf %{__mkdir_p} %{buildroot}/%{_sysconfdir}/bash_completion.d %{__install} -p -m0644 src/misc/spacecmd-bash-completion %{buildroot}/%{_sysconfdir}/bash_completion.d/spacecmd %{__mkdir_p} %{buildroot}/%{python_sitelib}/spacecmd %{__install} -p -m0644 src/spacecmd/*.py %{buildroot}/%{python_sitelib}/spacecmd/ %{__mkdir_p} %{buildroot}/%{_mandir}/man1 %{__gzip} -c src/doc/spacecmd.1 > %{buildroot}/%{_mandir}/man1/spacecmd.1.gz touch %{buildroot}/%{python_sitelib}/spacecmd/__init__.py %{__chmod} 0644 %{buildroot}/%{python_sitelib}/spacecmd/__init__.py %if 0%{?suse_version} %if 0%{?build_py3} %py3_compile -O %{buildroot}/%{python_sitelib} %else %py_compile -O %{buildroot}/%{python_sitelib} %endif %endif %check %if 0%{?pylint_check} %if 0%{?build_py3} PYTHONPATH=$RPM_BUILD_ROOT%{python_sitelib} \ spacewalk-python3-pylint $RPM_BUILD_ROOT%{python_sitelib}/spacecmd %else PYTHONPATH=$RPM_BUILD_ROOT%{python_sitelib} \ spacewalk-python2-pylint $RPM_BUILD_ROOT%{python_sitelib}/spacecmd %endif %endif %files %defattr(-,root,root) %{_bindir}/spacecmd %{python_sitelib}/spacecmd/ %ghost %config %{_sysconfdir}/spacecmd.conf %dir %{_sysconfdir}/bash_completion.d %{_sysconfdir}/bash_completion.d/spacecmd %doc src/doc/README src/doc/COPYING %doc %{_mandir}/man1/spacecmd.1.gz %changelog 07070100000009000041FD0000000000000000000000015D65A5B200000000000000000000000000000000000000000000000D00000000spacecmd/src0707010000000A000041FD0000000000000000000000015D65A5B200000000000000000000000000000000000000000000001100000000spacecmd/src/bin0707010000000B000081FD0000000000000000000000015D65A5B200001EBC000000000000000000000000000000000000001A00000000spacecmd/src/bin/spacecmd#!/usr/bin/python # # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # """ spacecmd - a command line interface to Spacewalk """ import logging import os import re import sys try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib import codecs import locale import argparse try: # python 3 from configparser import SafeConfigParser except ImportError: # python 2 from ConfigParser import SafeConfigParser from socket import getfqdn _INTRO = '''Welcome to spacecmd, a command-line interface to Spacewalk. Type: 'help' for a list of commands 'help <cmd>' for command-specific help 'quit' to quit ''' _SYSTEM_CONF_FILE = '/etc/spacecmd.conf' if __name__ == '__main__': # disable no-member error message # pylint: disable=E1101 if not sys.stdout.isatty(): sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout.buffer) usage = '%(prog)s [options] [command]' parser = argparse.ArgumentParser(usage=usage) parser.add_argument('-c', '--config', help='config file to use [default: ~/.spacecmd/config]') parser.add_argument('-u', '--username', help='use this username to connect to the server') parser.add_argument('-p', '--password', help='use this password to connect to the server (insecure). Use config instead or spacecmd will ask.') parser.add_argument('-s', '--server', help='connect to this server [default: local hostname]') parser.add_argument('--nossl', action='store_true', help='use HTTP instead of HTTPS') parser.add_argument('--nohistory', action='store_true', help='do not store command history') parser.add_argument('-y', '--yes', action='store_true', help='answer yes for all questions') parser.add_argument('-q', '--quiet', action='store_true', help='print only error messages') parser.add_argument('-d', '--debug', action='count', default=0, help='print debug messages (can be passed multiple times)') parser.add_argument('command', nargs='*', help=argparse.SUPPRESS) options = parser.parse_args() if options.command: args = options.command else: args = [] # determine the logging level if options.debug: level = logging.DEBUG elif options.quiet: level = logging.ERROR else: level = logging.INFO # configure logging logging.basicConfig(level=level, format='%(levelname)s: %(message)s') # files are loaded from ~/.spacecmd/ conf_dir = os.path.expanduser('~/.spacecmd') user_conf_file = os.path.join(conf_dir, 'config') # server-specifics will be loaded from the configuration file later config = SafeConfigParser() # prevent readline from outputting escape sequences to non-terminals if not sys.stdout.isatty(): logging.debug('stdout is not a TTY, setting TERM=dumb') os.environ['TERM'] = 'dumb' # import our Cmd subclass after we settle our TERM value from spacecmd.shell import SpacewalkShell, UnknownCallException # create an instance of the shell shell = SpacewalkShell(options, conf_dir, config) # set the default server to local hostname if shell.options.server: shell.config['server'] = shell.options.server else: shell.config['server'] = getfqdn() # don't automatically create config files passed via --config if shell.options.config: if not os.path.isfile(shell.options.config): logging.error('Config file %s does not exist.', shell.options.config) sys.exit(1) else: # create an empty configuration file if one's not present if not os.path.isfile(user_conf_file): try: # create ~/.spacecmd if not os.path.isdir(conf_dir): logging.debug('Creating %s', conf_dir) os.mkdir(conf_dir, int('0700', 8)) # create a template configuration file logging.debug('Creating configuration file: %s', user_conf_file) handle = open(user_conf_file, 'w') handle.write('[spacecmd]\n') handle.close() except IOError: logging.error('Could not create %s', user_conf_file) # load options from configuration files if shell.options.config: files_read = config.read([_SYSTEM_CONF_FILE, user_conf_file, shell.options.config]) else: files_read = config.read([_SYSTEM_CONF_FILE, user_conf_file]) for item in files_read: logging.debug('Read configuration from %s', item) # load the default configuration section shell.load_config_section('spacecmd') # run a single command from the command line if len(args): try: # rebuild the command and quote all arguments to be safe # except for help command command = args[0] if command == 'help': command = ' '.join(args) if len(args) > 1: command += ' %s' % ' '.join('%s' % s if not True in [ c.isspace() for c in s ] else "'%s'" % s for s in args[1:]) # run the command precmd = shell.precmd(command) if precmd == '': sys.exit(1) shell.print_result(shell.onecmd(precmd), precmd) except KeyboardInterrupt: print print('User Interrupt') except UnknownCallException: sys.exit(1) except Exception as detail: # get the relevant part of a XML-RPC fault if isinstance(detail, xmlrpclib.Fault): detail = detail.faultString if shell.options.debug: # print(the traceback when debugging) logging.exception(detail) else: logging.error(detail) sys.exit(1) else: if not shell.options.quiet: print(_INTRO) if not shell.do_login(''): sys.exit(1) # stay in the interactive shell forever while True: try: shell.cmdloop() except KeyboardInterrupt: print except SystemExit: sys.exit(0) except UnknownCallException: pass except Exception as detail: # get the relevant part of a XML-RPC fault if isinstance(detail, xmlrpclib.Fault): detail = detail.faultString # the session expired if re.search('Could not find session', detail, re.I): shell.session = '' if shell.options.debug: # print(the traceback when debugging) logging.exception(detail) else: logging.error(detail) 0707010000000C000041FD0000000000000000000000015D65A5B200000000000000000000000000000000000000000000001100000000spacecmd/src/doc0707010000000D000081B40000000000000000000000015D65A5B20000894B000000000000000000000000000000000000001900000000spacecmd/src/doc/COPYING GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state 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 3 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, see <http://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: <program> Copyright (C) <year> <name of author> This program 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, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>. The GNU 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. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>. 0707010000000E000081B40000000000000000000000015D65A5B200000D04000000000000000000000000000000000000001800000000spacecmd/src/doc/READMEspacecmd - a command line interface to Spacewalk and Satellite servers Author: Aron Parsons <aronparsons@gmail.com> Homepage: https://github.com/spacewalkproject/spacewalk/wiki/spacecmd # Requirements - >= python-2.4 - a Spacewalk or Satellite server with an API version of 10.8 or higher # Highlights - view and manage nearly every aspect of Satellite - tab completion - can be passed single commands (useful for shell scripts) - can be used as an interactive shell - advanced searching of systems allows systems to be managed without having to create system groups - all functionality implemented via the Satellite API # Examples # # add and remove packages from the commandline # [user@sat]$ spacecmd -y system_installpackage www* mod_python Scheduled 6 system(s) [user@sat]$ spacecmd -y system_removepackage wiki02 mod_perl Scheduled 1 system(s) # # apply errata from the command line # [user@sat]$ spacecmd -y errata_apply RHSA-2010:0423 Scheduled 42 system(s) [user@sat]$ spacecmd -y system_applyerrata group:web_servers RHSA-2010:0040 Scheduled 16 system(s) # # quickly generate reports # spacecmd> system_listerrata ldap03 System: ldap03 Security Errata: RHSA-2010:0458 Moderate: perl security update 6/7/10 RHSA-2010:0449 Moderate: rhn-client-tools security update 6/1/10 RHSA-2010:0423 Important: krb5 security update 5/18/10 spacecmd> report_errata # Systems Errata --------- ------ CLA-2010:0474 88 CLA-2010:0475 6 CLA-2010:0488 183 CLA-2010:0490 273 CLA-2010:0500 4 CLA-2010:0501 5 RHBA-2010:0402 1 RHSA-2010:0474 2 RHSA-2010:0488 1 RHSA-2010:0490 5 spacecmd> report_outofdatesystems System Packages ------ -------- monkey 310 shark 63 hedgehog 39 pomeranian 4 spacecmd> report_ipaddresses System Hostname IP ------ -------- -- dns01 dns01.dmz.example.com 192.168.254.53 www01 www01.dmz.example.com 192.168.254.80 ztest ztest.test.example.com 192.168.42.111 spacecmd> report_kernels System Kernel ------ ------ system01 2.6.9-89.0.25.ELsmp system02 2.6.9-89.0.3.ELsmp system03 2.6.9-89.0.26.ELsmp # # make temporary groups on-the-fly # spacecmd> ssm_add search:driver:bnx2 Systems Selected: 111 spacecmd> ssm_add search:device:vmware Systems Selected: 285 spacecmd> ssm_add search:hostname:external.example.com Systems Selected: 16 # # tab completion of everything # spacecmd> system_installpackage ssm vmware-tools [tab] vmware-tools vmware-tools-kmod vmware-tools-common vmware-tools-nox # # easily view system information # spacecmd> system_details www01.example.com Name: www01.example.com System ID: 1000010001 Locked: False Registered: 20100311 19:31:36 Last Checkin: 20100621 18:31:53 OSA Status: online Hostname: www01.example.com IP Address: 192.168.1.80 Kernel: 2.6.18-164.el5 Software Channels: custom-rhel-i386-server-5 |-- custom-extras-i386-rhel5 |-- clone-rhn-tools-rhel-i386-server-5 Configuration Channels: sudoers base base-rhel5 Entitlements: Management Provisioning System Groups: all_linux_systems all_linux_VMs rhel5-i386 0707010000000F000081B40000000000000000000000015D65A5B2000017BC000000000000000000000000000000000000001C00000000spacecmd/src/doc/spacecmd.1.TH "spacecmd" "1" "" "Aron Parsons" "" .SH NAME spacecmd \- a command-line interface to Spacewalk and Satellite servers .SH SYNOPSIS \fBspacecmd\fP [\fIoptions\fP] [\fIcommand\fP] .SH OVERVIEW .nf spacecmd is a command-line interface to Spacewalk and Satellite servers. It is written in Python and uses the XML-RPC API provided by the server. Nearly all aspects of the Satellite can be managed by spacecmd. These include managing systems, software channels, configuration channels, activation keys, Kickstarts and users. spacecmd can be run as an interactive shell by running it without a command. It can also run a single command by passing the command and the required arguments on the command line. Run 'spacecmd help' to list the actions that are availble. Run 'spacecmd -- <command> --help' to get help for a particular command. .fi .SH RUNNING SINGLE COMMANDS .nf When running spacecmd non-interactively, you must take care to escape arguments passed to the spacecmd functions. This involves putting a '--' before the command starts so that the arguments to the function are not treated as global arguments to spacecmd. You must also escape any quotes that you pass to the functions so that the shell does not interpret them. .P .B Example: .nf spacecmd -s server1 -- softwarechannel_create -n \\'My Channel\\' \\ -l channel1 \\ -a x86_64 .fi .SH OPTIONS .TP .B \-c CONFIG_FILE, \-\-config=CONFIG_FILE config file to use [default: ~/.spacecmd/config] .TP .B \-u USERNAME, \-\-username=USERNAME use this username to connect to the server .TP .B \-p PASSWORD, \-\-password=PASSWORD use this password to connect to the server (insecure). .nf This option is insecure because process listing tools like ps will show it as part of command line visible for all user. If no password is provided, spacecmd will try to read it from the config file or ask for it. .fi .TP .B \-s SERVER, \-\-server=SERVER connect to this server [default: localhost] .TP .B \-\-nossl use HTTP instead of HTTPS .TP .B \-\-nohistory do not store command history .TP .B \-y, \-\-yes answer yes for all questions .TP .B \-q, \-\-quiet print only error messages .TP .B \-d, \-\-debug print debug messages .TP .B \-h, \-\-help show this help message and exit .fi .SH CONFIGURATION FILES .nf Configuration files are loaded from /etc/spacecmd.conf and ~/.spacecmd/config, unless overridden by the --config option. The default section is [spacecmd]. It can then be overridden with server specific sections. Command-line arguments always override options in the configuration files. .fi .P .nf [spacecmd] server=localhost username=admin password=redhat nossl=0 [satellite.example.com] username=joe password=secret nossl=1 .fi .SH EXAMPLES .P .B Make temporary groups on-the-fly .nf spacecmd> ssm_add search:driver:bnx2 Systems Selected: 111 spacecmd> ssm_add search:device:vmware Systems Selected: 285 spacecmd> ssm_add search:hostname:external.example.com Systems Selected: 16 .fi .P .B Add and remove packages from the commandline .nf [user@sat]$ spacecmd -y system_installpackage www* mod_python Scheduled 6 system(s) [user@sat]$ spacecmd -y system_removepackage wiki02 mod_perl Scheduled 1 system(s) .fi .P .B Schedule reboots from the commandline .nf [user@sat]$ spacecmd -y -- system_reboot ldap* -s +6h Start Time: 20160106T07:01:00 Systems ------- ldap01 ldap02 ldap03 [user@sat]$ spacecmd -y system_reboot www01 Start Time: 20160106T02:01:00 Systems ------- www01 .fi .P .B Apply errata from the command line .nf [user@sat]$ spacecmd -y errata_apply RHSA-2010:0423 Scheduled 42 system(s) [user@sat]$ spacecmd -y system_applyerrata group:web_servers RHSA-2010:0040 Scheduled 16 system(s) .fi .P .B Quickly generate reports .nf spacecmd> system_listerrata ldap03 System: ldap03 Security Errata: RHSA-2010:0458 Moderate: perl security update 6/7/10 RHSA-2010:0449 Moderate: rhn-client-tools security update 6/1/10 RHSA-2010:0423 Important: krb5 security update 5/18/10 spacecmd> report_errata # Systems Errata --------- ------ CLA-2010:0474 88 CLA-2010:0475 6 CLA-2010:0488 183 CLA-2010:0490 273 CLA-2010:0500 4 CLA-2010:0501 5 RHBA-2010:0402 1 RHSA-2010:0474 2 RHSA-2010:0488 1 RHSA-2010:0490 5 spacecmd> report_outofdatesystems System Packages ------ -------- monkey 310 shark 63 hedgehog 39 pomeranian 4 spacecmd> report_ipaddresses System Hostname IP ------ -------- -- dns01 dns01.dmz.example.com 192.168.254.53 www01 www01.dmz.example.com 192.168.254.80 ztest ztest.test.example.com 192.168.42.111 spacecmd> report_kernels System Kernel ------ ------ system01 2.6.9-89.0.25.ELsmp system02 2.6.9-89.0.3.ELsmp system03 2.6.9-89.0.26.ELsmp .fi .P .B Tab completion of everything .nf spacecmd> system_installpackage ssm vmware-tools [tab] vmware-tools vmware-tools-kmod vmware-tools-common vmware-tools-nox .fi .P .B Easily view system information .nf spacecmd> system_details www01.example.com Name: www01.example.com System ID: 1000010001 Locked: False Registered: 20100311 19:31:36 Last Checkin: 20100621 18:31:53 OSA Status: online Hostname: www01.example.com IP Address: 192.168.1.80 Kernel: 2.6.18-164.el5 Software Channels: custom-rhel-i386-server-5 |-- custom-extras-i386-rhel5 |-- clone-rhn-tools-rhel-i386-server-5 Configuration Channels: sudoers base base-rhel5 Entitlements: Management Provisioning System Groups: all_linux_systems all_linux_VMs rhel5-i386 .nf .SH BUGS .nf Please report any bugs to https://bugzilla.redhat.com/ under the "spacecmd" component. .nf .SH HOMEPAGE https://github.com/spacewalkproject/spacewalk/wiki/spacecmd .nf .fi .SH AUTHOR spacecmd was written by Aron Parsons <aronparsons@gmail.com> 07070100000010000041FD0000000000000000000000015D65A5B200000000000000000000000000000000000000000000001200000000spacecmd/src/misc07070100000011000081B40000000000000000000000015D65A5B2000001E9000000000000000000000000000000000000002B00000000spacecmd/src/misc/spacecmd-bash-completion_spacecmd() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts=`for i in $(spacecmd help | tail -n +4 | head -n -5 ); do echo $i; done` if [[ ${cur} == * ]]; then if [[ ${#COMP_WORDS[@]} -gt 2 ]]; then return 0 else COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi fi } complete -F _spacecmd spacecmd 07070100000012000041FD0000000000000000000000015D65A5B200000000000000000000000000000000000000000000001600000000spacecmd/src/spacecmd07070100000013000081B40000000000000000000000015D65A5B200000029000000000000000000000000000000000000002200000000spacecmd/src/spacecmd/__init__.py# coding: utf-8 """ Package spacecmd """ 07070100000014000081B40000000000000000000000015D65A5B20000DF3A000000000000000000000000000000000000002700000000spacecmd/src/spacecmd/activationkey.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2011--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import re import shlex try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_activationkey_addpackages(self): print('activationkey_addpackages: Add packages to an activation key') print('usage: activationkey_addpackages KEY <PACKAGE ...>') def complete_activationkey_addpackages(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: return tab_completer(self.get_package_names(), text) def do_activationkey_addpackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_addpackages() return key = args.pop(0) packages = [{'name': a} for a in args] self.client.activationkey.addPackages(self.session, key, packages) #################### def help_activationkey_removepackages(self): print('activationkey_removepackages: Remove packages from an ' + 'activation key') print('usage: activationkey_removepackages KEY <PACKAGE ...>') def complete_activationkey_removepackages(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: details = self.client.activationkey.getDetails(self.session, parts[1]) packages = [p['name'] for p in details.get('packages')] return tab_completer(packages, text) def do_activationkey_removepackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_removepackages() return key = args.pop(0) packages = [{'name': a} for a in args] self.client.activationkey.removePackages(self.session, key, packages) #################### def help_activationkey_addgroups(self): print('activationkey_addgroups: Add groups to an activation key') print('usage: activationkey_addgroups KEY <GROUP ...>') def complete_activationkey_addgroups(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_group_list('', True), parts[-1]) def do_activationkey_addgroups(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_addgroups() return key = args.pop(0) groups = [] for a in args: details = self.client.systemgroup.getDetails(self.session, a) groups.append(details.get('id')) self.client.activationkey.addServerGroups(self.session, key, groups) #################### def help_activationkey_removegroups(self): print('activationkey_removegroups: Remove groups from an activation key') print('usage: activationkey_removegroups KEY <GROUP ...>') def complete_activationkey_removegroups(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: key_details = self.client.activationkey.getDetails(self.session, parts[-1]) groups = [] for group in key_details.get('server_group_ids'): details = self.client.systemgroup.getDetails(self.session, group) groups.append(details.get('name')) return tab_completer(groups, text) def do_activationkey_removegroups(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_removegroups() return key = args.pop(0) groups = [] for a in args: details = self.client.systemgroup.getDetails(self.session, a) groups.append(details.get('id')) self.client.activationkey.removeServerGroups(self.session, key, groups) #################### def help_activationkey_addentitlements(self): print('activationkey_addentitlements: Add entitlements to an ' + 'activation key') print('usage: activationkey_addentitlements KEY <ENTITLEMENT ...>') def complete_activationkey_addentitlements(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: return tab_completer(self.ENTITLEMENTS, text) def do_activationkey_addentitlements(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_addentitlements() return key = args.pop(0) entitlements = args self.client.activationkey.addEntitlements(self.session, key, entitlements) #################### def help_activationkey_removeentitlements(self): print('activationkey_removeentitlements: Remove entitlements from an ' + 'activation key') print('usage: activationkey_removeentitlements KEY <ENTITLEMENT ...>') def complete_activationkey_removeentitlements(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: details = \ self.client.activationkey.getDetails(self.session, parts[1]) entitlements = details.get('entitlements') return tab_completer(entitlements, text) def do_activationkey_removeentitlements(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_removeentitlements() return key = args.pop(0) entitlements = args self.client.activationkey.removeEntitlements(self.session, key, entitlements) #################### def help_activationkey_addchildchannels(self): print('activationkey_addchildchannels: Add child channels to an ' + 'activation key') print('usage: activationkey_addchildchannels KEY <CHANNEL ...>') def complete_activationkey_addchildchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: key_details = \ self.client.activationkey.getDetails(self.session, parts[1]) base_channel = key_details.get('base_channel_label') all_channels = \ self.client.channel.listSoftwareChannels(self.session) child_channels = [] for c in all_channels: if base_channel == 'none': # this gets all child channels if c.get('parent_label'): child_channels.append(c.get('label')) else: if c.get('parent_label') == base_channel: child_channels.append(c.get('label')) return tab_completer(child_channels, text) def do_activationkey_addchildchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_addchildchannels() return key = args.pop(0) channels = args self.client.activationkey.addChildChannels(self.session, key, channels) #################### def help_activationkey_removechildchannels(self): print('activationkey_removechildchannels: Remove child channels from ' + 'an activation key') print('usage: activationkey_removechildchannels KEY <CHANNEL ...>') def complete_activationkey_removechildchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: key_details = \ self.client.activationkey.getDetails(self.session, parts[1]) return tab_completer(key_details.get('child_channel_labels'), text) def do_activationkey_removechildchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_removechildchannels() return key = args.pop(0) channels = args self.client.activationkey.removeChildChannels(self.session, key, channels) #################### def help_activationkey_listchildchannels(self): print('activationkey_listchildchannels: List the child channels ' + 'for an activation key') print('usage: activationkey_listchildchannels KEY') def complete_activationkey_listchildchannels(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_listchildchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_listchildchannels() return key = args[0] details = self.client.activationkey.getDetails(self.session, key) if details.get('child_channel_labels'): print('\n'.join(sorted(details.get('child_channel_labels')))) #################### def help_activationkey_listbasechannel(self): print('activationkey_listbasechannel: List the base channels ' + 'for an activation key') print('usage: activationkey_listbasechannel KEY') def complete_activationkey_listbasechannel(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_listbasechannel(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_listbasechannel() return key = args[0] details = self.client.activationkey.getDetails(self.session, key) print(details.get('base_channel_label')) #################### def help_activationkey_listgroups(self): print('activationkey_listgroups: List the groups for an ' + 'activation key') print('usage: activationkey_listgroups KEY') def complete_activationkey_listgroups(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_listgroups(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_listgroups() return key = args[0] details = self.client.activationkey.getDetails(self.session, key) for group in details.get('server_group_ids'): group_details = self.client.systemgroup.getDetails(self.session, group) print(group_details.get('name')) #################### def help_activationkey_listentitlements(self): print('activationkey_listentitlements: List the entitlements ' + 'for an activation key') print('usage: activationkey_listentitlements KEY') def complete_activationkey_listentitlements(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_listentitlements(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_listentitlements() return key = args[0] details = self.client.activationkey.getDetails(self.session, key) if details.get('entitlements'): print('\n'.join(details.get('entitlements'))) #################### def help_activationkey_listpackages(self): print('activationkey_listpackages: List the packages for an ' + 'activation key') print('usage: activationkey_listpackages KEY') def complete_activationkey_listpackages(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_listpackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_listpackages() return key = args[0] details = self.client.activationkey.getDetails(self.session, key) for package in details.get('packages'): if 'arch' in package: print('%s.%s' % (package['name'], package['arch'])) else: print(package['name']) #################### def help_activationkey_listconfigchannels(self): print('activationkey_listconfigchannels: List the configuration ' + 'channels for an activation key') print('usage: activationkey_listconfigchannels KEY') def complete_activationkey_listconfigchannels(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_listconfigchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_listconfigchannels() return key = args[0] channels = \ self.client.activationkey.listConfigChannels(self.session, key) channels = sorted([c.get('label') for c in channels]) if channels: print('\n'.join(channels)) #################### def help_activationkey_addconfigchannels(self): print('activationkey_addconfigchannels: Add config channels ' + 'to an activation key') print('''usage: activationkey_addconfigchannels KEY <CHANNEL ...> [options]) options: -t add channels to the top of the list -b add channels to the bottom of the list''') def complete_activationkey_addconfigchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_configchannel_list('', True), text) def do_activationkey_addconfigchannels(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-t', '--top', action='store_true') arg_parser.add_argument('-b', '--bottom', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_activationkey_addconfigchannels() return key = [args.pop(0)] channels = args if is_interactive(options): answer = prompt_user('Add to top or bottom? [T/b]:') if re.match('b', answer, re.I): options.top = False else: options.top = True else: if options.bottom: options.top = False else: options.top = True self.client.activationkey.addConfigChannels(self.session, key, channels, options.top) #################### def help_activationkey_removeconfigchannels(self): print('activationkey_removeconfigchannels: Remove config channels ' + 'from an activation key') print('usage: activationkey_removeconfigchannels KEY <CHANNEL ...>') def complete_activationkey_removeconfigchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: key_channels = \ self.client.activationkey.listConfigChannels(self.session, parts[1]) config_channels = [c.get('label') for c in key_channels] return tab_completer(config_channels, text) def do_activationkey_removeconfigchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_removeconfigchannels() return key = [args.pop(0)] channels = args self.client.activationkey.removeConfigChannels(self.session, key, channels) #################### def help_activationkey_setconfigchannelorder(self): print('activationkey_setconfigchannelorder: Set the ranked order of ' + 'configuration channels') print('usage: activationkey_setconfigchannelorder KEY') def complete_activationkey_setconfigchannelorder(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_setconfigchannelorder(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_activationkey_setconfigchannelorder() return key = args[0] # get the current configuration channels from the first activationkey # in the list new_channels = \ self.client.activationkey.listConfigChannels(self.session, key) new_channels = [c.get('label') for c in new_channels] # call an interface for the user to make selections all_channels = self.do_configchannel_list('', True) new_channels = config_channel_order(all_channels, new_channels) print('') print('New Configuration Channels:') for i, new_channel in enumerate(new_channels, 1): print('[%i] %s' % (i, new_channel)) self.client.activationkey.setConfigChannels(self.session, [key], new_channels) #################### def help_activationkey_create(self): print('activationkey_create: Create an activation key') print('''usage: activationkey_create [options]) options: -n NAME -d DESCRIPTION -b BASE_CHANNEL -u set key as universal default -e [enterprise_entitled,virtualization_host]''') def do_activationkey_create(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-d', '--description') arg_parser.add_argument('-b', '--base-channel') arg_parser.add_argument('-e', '--entitlements') arg_parser.add_argument('-u', '--universal', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.name = prompt_user('Name (blank to autogenerate):') options.description = prompt_user('Description [None]:') print('') print('Base Channels') print('-------------') print('\n'.join(sorted(self.list_base_channels()))) print('') options.base_channel = prompt_user('Base Channel (blank for default):') options.entitlements = [] for e in self.ENTITLEMENTS: if e == 'enterprise_entitled': continue if self.user_confirm('%s Entitlement [y/N]:' % e, ignore_yes=True): options.entitlements.append(e) options.universal = self.user_confirm('Universal Default [y/N]:', ignore_yes=True) else: if not options.name: options.name = '' if not options.description: options.description = '' if not options.base_channel: options.base_channel = '' if not options.universal: options.universal = False if options.entitlements: options.entitlements = options.entitlements.split(',') # remove empty strings from the list if '' in options.entitlements: options.entitlements.remove('') else: options.entitlements = [] new_key = self.client.activationkey.create(self.session, options.name, options.description, options.base_channel, options.entitlements, options.universal) logging.info('Created activation key %s' % new_key) #################### def help_activationkey_delete(self): print('activationkey_delete: Delete an activation key') print('usage: activationkey_delete KEY') def complete_activationkey_delete(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_delete() return # allow globbing of activationkey names keys = filter_results(self.do_activationkey_list('', True), args) logging.debug("activationkey_delete called with args %s, keys=%s" % (args, keys)) if not keys: logging.error("No keys matched argument %s" % args) return # Print the keys prior to the confimation print('\n'.join(sorted(keys))) if not self.user_confirm('Delete activation key(s) [y/N]:'): return for key in keys: logging.debug("Deleting key %s" % key) self.client.activationkey.delete(self.session, key) #################### def help_activationkey_list(self): print('activationkey_list: List all activation keys') print('usage: activationkey_list') def do_activationkey_list(self, args, doreturn=False): all_keys = self.client.activationkey.listActivationKeys(self.session) keys = [] for k in all_keys: # don't list auto-generated re-activation keys if not re.match('Kickstart re-activation', k.get('description')): keys.append(k.get('key')) if doreturn: return keys else: if keys: print('\n'.join(sorted(keys))) #################### def help_activationkey_listsystems(self): print('activationkey_listsystems: List systems registered with a key') print('usage: activationkey_listsystems KEY') def complete_activationkey_listsystems(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_listsystems(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_listsystems() return key = args[0] try: systems = \ self.client.activationkey.listActivatedSystems(self.session, key) except xmlrpclib.Fault: logging.warning('%s is not a valid activation key' % key) return systems = sorted([s.get('hostname') for s in systems]) if systems: print('\n'.join(systems)) #################### def help_activationkey_details(self): print('activationkey_details: Show the details of an activation key') print('usage: activationkey_details KEY ...') def complete_activationkey_details(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_details() return add_separator = False result = [] for key in args: try: details = self.client.activationkey.getDetails(self.session, key) config_channels = \ self.client.activationkey.listConfigChannels( self.session, key) config_channel_deploy = \ self.client.activationkey.checkConfigDeployment( self.session, key) # API returns 0/1 instead of boolean config_channel_deploy = config_channel_deploy == 1 except xmlrpclib.Fault: logging.warning('%s is not a valid activation key' % key) return groups = [] for group in details.get('server_group_ids'): group_details = self.client.systemgroup.getDetails(self.session, group) groups.append(group_details.get('name')) if add_separator: print(self.SEPARATOR) add_separator = True result.append('Key: %s' % details.get('key')) result.append('Description: %s' % details.get('description')) result.append('Universal Default: %s' % details.get('universal_default')) result.append('Usage Limit: %s' % details.get('usage_limit')) result.append('Deploy Config Channels: %s' % config_channel_deploy) if 'contact_method' in details: result.append('Contact Method: %s' % details.get('contact_method')) result.append('') result.append('Software Channels') result.append('-----------------') result.append(details.get('base_channel_label')) for channel in sorted(details.get('child_channel_labels')): result.append(' |-- %s' % channel) result.append('') result.append('Configuration Channels') result.append('----------------------') for channel in config_channels: result.append(channel.get('label')) result.append('') result.append('Entitlements') result.append('------------') result.append('\n'.join(sorted(details.get('entitlements')))) result.append('') result.append('System Groups') result.append('-------------') result.append('\n'.join(sorted(groups))) result.append('') result.append('Packages') result.append('--------') for package in sorted(details.get('packages')): name = package.get('name') if package.get('arch'): name += '.%s' % package.get('arch') result.append(name) return result #################### def help_activationkey_enableconfigdeployment(self): print('activationkey_enableconfigdeployment: Enable config ' + 'channel deployment') print('usage: activationkey_enableconfigdeployment KEY') def complete_activationkey_enableconfigdeployment(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_enableconfigdeployment(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_enableconfigdeployment() return for key in args: logging.debug('Enabling config file deployment for %s' % key) self.client.activationkey.enableConfigDeployment(self.session, key) #################### def help_activationkey_disableconfigdeployment(self): print('activationkey_disableconfigdeployment: Disable config ' + 'channel deployment') print('usage: activationkey_disableconfigdeployment KEY') def complete_activationkey_disableconfigdeployment(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_disableconfigdeployment(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_disableconfigdeployment() return for key in args: logging.debug('Disabling config file deployment for %s' % key) self.client.activationkey.disableConfigDeployment(self.session, key) #################### def help_activationkey_setbasechannel(self): print('activationkey_setbasechannel: Set the base channel of an ' + 'activation key') print('usage: activationkey_setbasechannel KEY CHANNEL') def complete_activationkey_setbasechannel(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: return tab_completer(self.list_base_channels(), text) def do_activationkey_setbasechannel(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_setbasechannel() return key = args.pop(0) channel = args[0] current_details = self.client.activationkey.getDetails(self.session, key) details = {'description': current_details.get('description'), 'base_channel_label': channel, 'usage_limit': current_details.get('usage_limit'), 'universal_default': current_details.get('universal_default')} # getDetails returns a usage_limit of 0 unlimited, which is then # interpreted literally as zero when passed into setDetails, doh! # Setting it to -1 seems to keep the usage limit unlimited if details['usage_limit'] == 0: details['usage_limit'] = -1 self.client.activationkey.setDetails(self.session, key, details) #################### def help_activationkey_setusagelimit(self): print('activationkey_setusagelimit: Set the usage limit of an ' + 'activation key, can be a number or \"unlimited\"') print('usage: activationkey_setusagelimit KEY <usage limit>') print('usage: activationkey_setusagelimit KEY unlimited ') def complete_activationkey_setusagelimit(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) elif len(parts) > 2: return "unlimited" def do_activationkey_setusagelimit(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_setusagelimit() return key = args.pop(0) usage_limit = -1 if args[0] == 'unlimited': logging.debug("Setting usage for key %s unlimited" % key) else: try: usage_limit = int(args[0]) logging.debug("Setting usage for key %s to %d" % (key, usage_limit)) except ValueError: logging.error("Couldn't convert argument %s to an integer" % args[0]) self.help_activationkey_setusagelimit() return current_details = self.client.activationkey.getDetails(self.session, key) details = {'description': current_details.get('description'), 'base_channel_label': current_details.get('base_channel_label'), 'usage_limit': usage_limit, 'universal_default': current_details.get('universal_default')} self.client.activationkey.setDetails(self.session, key, details) #################### def help_activationkey_setuniversaldefault(self): print('activationkey_setuniversaldefault: Set this key as the ' + 'universal default') print('usage: activationkey_setuniversaldefault KEY') def complete_activationkey_setuniversaldefault(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_setuniversaldefault(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_activationkey_setuniversaldefault() return key = args.pop(0) current_details = self.client.activationkey.getDetails(self.session, key) details = {'description': current_details.get('description'), 'base_channel_label': current_details.get('base_channel_label'), 'usage_limit': current_details.get('usage_limit'), 'universal_default': True} # getDetails returns a usage_limit of 0 unlimited, which is then # interpreted literally as zero when passed into setDetails, doh! # Setting it to -1 seems to keep the usage limit unlimited if details['usage_limit'] == 0: details['usage_limit'] = -1 self.client.activationkey.setDetails(self.session, key, details) #################### def help_activationkey_export(self): print('activationkey_export: Export activation key(s) to JSON format file') print('''usage: activationkey_export [options] [<KEY> ...]) options: -f outfile.json : specify an output filename, defaults to <KEY>.json if exporting a single key, akeys.json for multiple keys, or akey_all.json if no KEY specified (export ALL) Note : KEY list is optional, default is to export ALL keys ''') def complete_activationkey_export(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def export_activationkey_getdetails(self, key): # Get the key details logging.info("Getting activation key details for %s" % key) details = self.client.activationkey.getDetails(self.session, key) # Get the key config-channel data, add it to the existing details logging.debug("activationkey.listConfigChannels %s" % key) ccdlist = [] try: ccdlist = self.client.activationkey.listConfigChannels(self.session, key) except xmlrpclib.Fault: logging.debug("activationkey.listConfigChannel threw an exeception, setting config_channels=False") cclist = [c['label'] for c in ccdlist] logging.debug("Got config channel label list of %s" % cclist) details['config_channels'] = cclist logging.debug("activationkey.checkConfigDeployment %s" % key) details['config_deploy'] = \ self.client.activationkey.checkConfigDeployment(self.session, key) # Get group details, as the server group IDs are not necessarily the same # across servers, so we need the group name on import details['server_groups'] = [] if details['server_group_ids']: grp_detail_list = [] for grp in details['server_group_ids']: grp_details = self.client.systemgroup.getDetails(self.session, grp) if grp_details: grp_detail_list.append(grp_details) details['server_groups'] = [g['name'] for g in grp_detail_list] # Now append the details dict describing the key to the specified file return details def do_activationkey_export(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) filename = "" if options.file != None: logging.debug("Passed filename do_activationkey_export %s" % options.file) filename = options.file # Get the list of keys to export and sort out the filename if required keys = [] if not args: if not filename: filename = "akey_all.json" logging.info("Exporting ALL activation keys to %s" % filename) keys = self.do_activationkey_list('', True) else: # allow globbing of activationkey names keys = filter_results(self.do_activationkey_list('', True), args) logging.debug("activationkey_export called with args %s, keys=%s" % (args, keys)) if not keys: logging.error("Invalid activation key passed") return if not filename: # No filename arg, so we try to do something sensible: # If we are exporting exactly one key, we default to keyname.json # otherwise, generic akeys.json name if len(keys) == 1: filename = "%s.json" % keys[0] else: filename = "akeys.json" # Dump as a list of dict keydetails_list = [] for k in keys: logging.info("Exporting key %s to %s" % (k, filename)) keydetails_list.append(self.export_activationkey_getdetails(k)) logging.debug("About to dump %d keys to %s" % (len(keydetails_list), filename)) # Check if filepath exists, if it is an existing file # we prompt the user for confirmation if os.path.isfile(filename): if not self.user_confirm("File %s exists, confirm overwrite file? (y/n)" % filename): return if json_dump_to_file(keydetails_list, filename) != True: logging.error("Failed to save exported keys to file: {}".format(filename)) return #################### def help_activationkey_import(self): print('activationkey_import: import activation key(s) from JSON file(s)') print('''usage: activationkey_import <JSONFILE ...>''') def do_activationkey_import(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: logging.error("No filename passed") self.help_activationkey_import() return for filename in args: logging.debug("Passed filename do_activationkey_import %s" % filename) keydetails_list = json_read_from_file(filename) if not keydetails_list: logging.error("Could not read json data from %s" % filename) return for keydetails in keydetails_list: if self.import_activationkey_fromdetails(keydetails) != True: logging.error("Failed to import key %s" % keydetails['key']) # create a new key based on the dict from export_activationkey_getdetails def import_activationkey_fromdetails(self, keydetails): # First we check that an existing key with the same name does not exist existing_keys = self.do_activationkey_list('', True) if keydetails['key'] in existing_keys: logging.warning("%s already exists! Skipping!" % keydetails['key']) return False else: # create the key, we need to drop the org prefix from the key name keyname = re.sub('^[0-9]-', '', keydetails['key']) logging.debug("Found key %s, importing as %s" % (keydetails['key'], keyname)) # Channel label must be an empty-string for "Red Hat Satellite Default" # The export to json maps this to a unicode string "none" # To avoid changing the json format now, just fix it up here... if keydetails['base_channel_label'] == "none": keydetails['base_channel_label'] = '' if keydetails['usage_limit'] != 0: newkey = self.client.activationkey.create(self.session, keyname, keydetails['description'], keydetails['base_channel_label'], keydetails['usage_limit'], keydetails['entitlements'], keydetails['universal_default']) else: newkey = self.client.activationkey.create(self.session, keyname, keydetails['description'], keydetails['base_channel_label'], keydetails['entitlements'], keydetails['universal_default']) if not newkey: logging.error("Failed to import key %s" % keyname) return False # add child channels self.client.activationkey.addChildChannels(self.session, newkey, keydetails['child_channel_labels']) # set config channel options and channels (missing are skipped) if keydetails['config_deploy'] != 0: self.client.activationkey.enableConfigDeployment(self.session, newkey) else: self.client.activationkey.disableConfigDeployment(self.session, newkey) if keydetails['config_channels']: self.client.activationkey.addConfigChannels(self.session, [newkey], keydetails['config_channels'], False) # set groups (missing groups are created) gids = [] for grp in keydetails['server_groups']: grpdetails = self.client.systemgroup.getDetails(self.session, grp) if grpdetails is None: logging.info("System group %s doesn't exist, creating" % grp) grpdetails = self.client.systemgroup.create(self.session, grp, grp) gids.append(grpdetails.get('id')) if gids: logging.debug("Adding groups %s to key %s" % (gids, newkey)) self.client.activationkey.addServerGroups(self.session, newkey, gids) # Finally add the package list if keydetails['packages']: self.client.activationkey.addPackages(self.session, newkey, keydetails['packages']) return True #################### def help_activationkey_clone(self): print('activationkey_clone: Clone an activation key') print('''usage examples:) activationkey_clone foo_key -c bar_key activationkey_clone foo_key1 foo_key2 -c prefix activationkey_clone foo_key -x "s/foo/bar" activationkey_clone foo_key1 foo_key2 -x "s/foo/bar" options: -c CLONE_NAME : Name of the resulting key, treated as a prefix for multiple keys -x "s/foo/bar" : Optional regex replacement, replaces foo with bar in the clone description, base-channel label, child-channel labels, config-channel names ''') def complete_activationkey_clone(self, text, line, beg, end): return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_clone(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-c', '--clonename') arg_parser.add_argument('-x', '--regex') (args, options) = parse_command_arguments(args, arg_parser) allkeys = self.do_activationkey_list('', True) if is_interactive(options): print('') print('Activation Keys') print('------------------') print('\n'.join(sorted(allkeys))) print('') if len(args) == 1: print("Key to clone: %s" % args[0]) else: # Clear out any args as interactive doesn't handle multiple keys args = [] args.append(prompt_user('Original Key:', noblank=True)) options.clonename = prompt_user('Cloned Key:', noblank=True) else: if not options.clonename and not options.regex: logging.error("Error - must specify either -c or -x options!") self.help_activationkey_clone() return if options.clonename in allkeys: logging.error("Key %s already exists" % options.clonename) return if not args: logging.error("Error no activationkey to clone passed!") self.help_activationkey_clone() return logging.debug("Got args=%s %d" % (args, len(args))) # allow globbing of configchannel channel names akeys = filter_results(allkeys, args) logging.debug("Filtered akeys %s" % akeys) logging.debug("all akeys %s" % allkeys) for ak in akeys: logging.debug("Cloning %s" % ak) # Replace the key-name with the clonename specified by the user keydetails = self.export_activationkey_getdetails(ak) # If the -x/--regex option is passed, do a sed-style replacement over # everything contained by the key. This makes it easier to clone when # content is based on a known naming convention if options.regex: # formatted like a sed-replacement, s/foo/bar findstr = options.regex.split("/")[1] replacestr = options.regex.split("/")[2] logging.debug("Regex option with %s, replacing %s with %s" % (options.regex, findstr, replacestr)) # First we do the key name newkey = re.sub(findstr, replacestr, keydetails['key']) keydetails['key'] = newkey # Then the description newdesc = re.sub(findstr, replacestr, keydetails['description']) keydetails['description'] = newdesc # Then the base-channel label newbasech = re.sub(findstr, replacestr, keydetails['base_channel_label']) if newbasech in self.list_base_channels(): keydetails['base_channel_label'] = newbasech # Now iterate over any child-channel labels # we have the new base-channel, we can check if the new child # label exists under the new base-channel: # If it doesn't we can only skip it and print(a warning) all_childch = self.list_child_channels(system=None, parent=newbasech, subscribed=False) new_child_channel_labels = [] for c in keydetails['child_channel_labels']: newc = re.sub(findstr, replacestr, c) if newc in all_childch: logging.debug("Found child channel %s for key %s, " % (c, keydetails['key']) + "replacing with %s" % newc) new_child_channel_labels.append(newc) else: logging.warning("Found child channel %s key %s, %s" % (c, keydetails['key'], newc) + " does not exist, skipping!") logging.debug("Processed all child channels, " + "new_child_channel_labels=%s" % new_child_channel_labels) keydetails['child_channel_labels'] = new_child_channel_labels else: logging.error("Regex-replacement results in new " + "base-channel %s which does not exist!" % newbasech) # Finally, any config-channels new_config_channels = [] allccs = self.do_configchannel_list('', True) for cc in keydetails['config_channels']: newcc = re.sub(findstr, replacestr, cc) if newcc in allccs: logging.debug("Found config channel %s for key %s, " % (cc, keydetails['key']) + "replacing with %s" % newcc) new_config_channels.append(newcc) else: logging.warning("Found config channel %s for key %s, %s " % (cc, keydetails['key'], newcc) + "does not exist, skipping!") logging.debug("Processed all config channels, " + "new_config_channels = %s" % new_config_channels) keydetails['config_channels'] = new_config_channels # Not regex mode elif options.clonename: if len(akeys) > 1: # We treat the clonename as a prefix for multiple keys # However we need to insert the prefix after the org- newkey = re.sub(r'^([0-9]-)', r'\1' + options.clonename, keydetails['key']) keydetails['key'] = newkey else: keydetails['key'] = options.clonename logging.info("Cloning key %s as %s" % (ak, keydetails['key'])) # Finally : import the key from the modified keydetails dict if self.import_activationkey_fromdetails(keydetails) != True: logging.error("Failed to clone %s to %s" % (ak, keydetails['key'])) #################### # activationkey helper def is_activationkey(self, name): if not name: return return name in self.do_activationkey_list(name, True) def check_activationkey(self, name): if not name: logging.error("no activationkey label given") return False if not self.is_activationkey(name): logging.error("invalid activationkey label " + name) return False return True def dump_activationkey(self, name, replacedict=None, excludes=None): content = self.do_activationkey_details(name) if not excludes: excludes = ["Universal Default:"] content = get_normalized_text(content, replacedict=replacedict, excludes=excludes) return content #################### def help_activationkey_diff(self): print('activationkey_diff: Diff activation keys') print('') print('usage: activationkey_diff SOURCE_KEY TARGET_KEY') def complete_activationkey_diff(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_activationkey_list('', True), text) if args == 3: return tab_completer(self.do_activationkey_list('', True), text) return [] def do_activationkey_diff(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) not in [1, 2]: self.help_activationkey_diff() return source_channel = args[0] if not self.check_activationkey(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_activationkey_getcorresponding"): # can a corresponding channel name be found automatically? target_channel = self.do_activationkey_getcorresponding(source_channel) if not self.check_activationkey(target_channel): return source_replacedict, target_replacedict = get_string_diff_dicts(source_channel, target_channel) source_data = self.dump_activationkey(source_channel, source_replacedict) target_data = self.dump_activationkey(target_channel, target_replacedict) return diff(source_data, target_data, source_channel, target_channel) #################### def help_activationkey_disable(self): print('activationkey_disable: Disable an activation key') print('') print('usage: activationkey_disable KEY [KEY ...]') def complete_activationkey_disable(self, text, line, beg, end): parts = line.split(' ') if len(parts) >= 2: return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_disable(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 1: self.help_activationkey_disable() return keys = filter_results(self.do_activationkey_list('', True), args) details = {'disabled': True} for akey in keys: self.client.activationkey.setDetails(self.session, akey, details) #################### def help_activationkey_enable(self): print('activationkey_enable: Enable an activation key') print('') print('usage: activationkey_enable KEY [KEY ...]') def complete_activationkey_enable(self, text, line, beg, end): parts = line.split(' ') if len(parts) >= 2: return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_enable(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 1: self.help_activationkey_enable() return keys = filter_results(self.do_activationkey_list('', True), args) details = {'disabled': False} for akey in keys: self.client.activationkey.setDetails(self.session, akey, details) #################### def help_activationkey_setdescription(self): print('activationkey_setdescription: Set the activation key description') print('') print('usage: activationkey_setdescription KEY DESCRIPTION') def complete_activationkey_setdescription(self, text, line, beg, end): parts = line.split(' ') if len(parts) <= 2: return tab_completer(self.do_activationkey_list('', True), text) def do_activationkey_setdescription(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_activationkey_setdescription() return akey = args.pop(0) description = ' '.join(args) details = {'description': description} self.client.activationkey.setDetails(self.session, akey, details) #################### def help_activationkey_setcontactmethod(self): print('activationkey_setcontactmethod: Set the contact method to use for ' \ 'systems registered with this key.') print('Available contact methods: ' + str(self.CONTACT_METHODS)) print('usage: activationkey_setcontactmethod KEY CONTACT_METHOD') def complete_activationkey_setcontactmethod(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_activationkey_list('', True), text) else: return tab_completer(self.CONTACT_METHODS, text) def do_activationkey_setcontactmethod(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) == 2: self.help_activationkey_setcontactmethod() return details = {'contact_method': args.pop()} akey = args.pop() self.client.activationkey.setDetails(self.session, akey, details) 07070100000015000081B40000000000000000000000015D65A5B200000B67000000000000000000000000000000000000001D00000000spacecmd/src/spacecmd/api.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2011 Satoru SATOH <ssato@redhat.com> # # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 import codecs import logging import sys try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_api(self): print('api: call RHN API with arguements directly') print('''usage: api [options] API_STRING) options: -A, --args Arguments for the API other than session id in comma separated strings or JSON expression -F, --format Output format -o, --output Output file examples: api api.getApiCallList api --args "sysgroup_A" systemgroup.listSystems api -A "rhel-i386-server-5,2011-04-01,2011-05-01" -F "%(name)s" \\ channel.software.listAllPackages ''') def do_api(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-A', '--args', default='') arg_parser.add_argument('-F', '--format', default='') arg_parser.add_argument('-o', '--output', default='') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_api() return api_name = args[0] api_args = parse_api_args(options.args) if options.output: try: output = open(options.output, "w") except IOError: logging.warning("Could not open to write: " + options.output) logging.info("Fallback output to stdout") output = sys.stdout else: output = sys.stdout api = getattr(self.client, api_name, None) if not callable(api): logging.warning("No such API: " + api_name) return try: res = api(self.session, *api_args) if not isinstance(res, list): res = [res] if options.format: for r in res: output.write(options.format % r + "\n") else: json_dump(res, output, indent=2, cls=CustomJsonEncoder) if (output != sys.stdout): output.close() except xmlrpclib.Fault: if (output != sys.stdout): output.close() 07070100000016000081B40000000000000000000000015D65A5B20000054C000000000000000000000000000000000000002800000000spacecmd/src/spacecmd/argumentparser.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2011--2018 Red Hat, Inc. # # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from argparse import ArgumentParser # argparse by default will exit when there is an error. when spacecmd # is in an interactive shell, we don't want to exit. instead, just # raise an exception that will printed for the user to read. class SpacecmdArgumentParser(ArgumentParser): def error(self, message): raise Exception(message) 07070100000017000081B40000000000000000000000015D65A5B20000E780000000000000000000000000000000000000002700000000spacecmd/src/spacecmd/configchannel.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2011--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from datetime import datetime import base64 try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_configchannel_list(self): print('configchannel_list: List all configuration channels') print('usage: configchannel_list') def do_configchannel_list(self, args, doreturn=False): channels = self.client.configchannel.listGlobals(self.session) channels = [c.get('label') for c in channels] if doreturn: return channels else: if channels: print('\n'.join(sorted(channels))) #################### def help_configchannel_listsystems(self): print('configchannel_listsystems: List the systems subscribed to a') print(' configuration channel') print('usage: configchannel_listsystems CHANNEL') def complete_configchannel_listsystems(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_listsystems(self, args): if not self.check_api_version('10.11'): logging.warning("This version of the API doesn't support this method") return arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_configchannel_listsystems() return channel = args[0] systems = self.client.configchannel.listSubscribedSystems(self.session, channel) systems = sorted([s.get('name') for s in systems]) if systems: print('\n'.join(systems)) #################### def help_configchannel_listfiles(self): print('configchannel_listfiles: List the files in a config channel') print('usage: configchannel_listfiles CHANNEL ...') def complete_configchannel_listfiles(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_listfiles(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_configchannel_listfiles() return [] for channel in args: files = self.client.configchannel.listFiles(self.session, channel) files = [f.get('path') for f in files] if doreturn: return files else: if files: print('\n'.join(sorted(files))) #################### def help_configchannel_forcedeploy(self): print('configchannel_forcedeploy: Forces a redeployment') print(' of files within this channel') print(' on all subscribed systems') print('usage: configchannel_forcedeploy CHANNEL') def complete_configchannel_forcedeploy(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_forcedeploy(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_configchannel_forcedeploy() return channel = args[0] files = self.client.configchannel.listFiles(self.session, channel) files = [f.get('path') for f in files] if not files: print('No files within selected configchannel.') return else: systems = self.client.configchannel.listSubscribedSystems(self.session, channel) systems = sorted([s.get('name') for s in systems]) if not systems: print('Channel has no subscribed Systems') return else: print('Force deployment of the following configfiles:') print('==============================================') print('\n'.join(files)) print('\nOn these systems:') print('=================') print('\n'.join(systems)) if self.user_confirm('Really force deployment [y/N]:'): self.client.configchannel.deployAllSystems(self.session, channel) #################### def help_configchannel_filedetails(self): print('configchannel_filedetails: Show the details of a file') print('in a configuration channel') print('usage: configchannel_filedetails CHANNEL FILE [REVISION]') def complete_configchannel_filedetails(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_configchannel_list('', True), text) if len(parts) > 2: return tab_completer( self.do_configchannel_listfiles(parts[1], True), text) return [] def do_configchannel_filedetails(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_configchannel_filedetails() return channel = args[0] filename = args[1] revision = None try: revision = int(args[2]) except (ValueError, IndexError): pass # the server return a null exception if an invalid file is passed valid_files = self.do_configchannel_listfiles(channel, True) if not filename in valid_files: logging.warning('%s is not in this configuration channel' % filename) return if revision: details = self.client.configchannel.lookupFileInfo(self.session, channel, filename, revision) else: results = self.client.configchannel.lookupFileInfo(self.session, channel, [filename]) # grab the first item since we only do one file details = results[0] result = [] result.append('Path: %s' % details.get('path')) result.append('Type: %s' % details.get('type')) result.append('Revision: %i' % details.get('revision')) result.append('Created: %s' % details.get('creation')) result.append('Modified: %s' % details.get('modified')) if details.get('type') == 'symlink': result.append('') result.append('Target Path: %s' % details.get('target_path')) else: result.append('') result.append('Owner: %s' % details.get('owner')) result.append('Group: %s' % details.get('group')) result.append('Mode: %s' % details.get('permissions_mode')) result.append('SELinux Context: %s' % details.get('selinux_ctx')) if details.get('type') == 'file': result.append('SHA256: %s' % details.get('sha256')) result.append('Binary: %s' % details.get('binary')) if not details.get('binary'): result.append('') result.append('Contents') result.append('--------') result.append(details.get('contents')) return result #################### def help_configchannel_backup(self): print('configchannel_backup: backup a config channel') print('''usage: configchannel_backup CHANNEL [OUTDIR]) OUTDIR defaults to $HOME/spacecmd-backup/configchannel/YYYY-MM-DD/CHANNEL ''') def complete_configchannel_backup(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_backup(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 1: self.help_configchannel_backup() return channel = args[0] # use an output base from the user if it was passed if len(args) == 2: outputpath_base = datetime.now().strftime(os.path.expanduser(args[1])) else: outputpath_base = os.path.expanduser('~/spacecmd-backup/configchannel') # make the final output path be <base>/date/channel outputpath_base = os.path.join(outputpath_base, datetime.now().strftime("%Y-%m-%d"), channel) try: if not os.path.isdir(outputpath_base): os.makedirs(outputpath_base) except OSError: logging.error('Could not create output directory') return # the server return a null exception if an invalid file is passed valid_files = self.do_configchannel_listfiles(channel, True) results = self.client.configchannel.lookupFileInfo(self.session, channel, valid_files) try: fh = open(outputpath_base + "/.metainfo", 'w') except IOError: logging.error('Could not create metainfo file') return for details in results: dumpfile = outputpath_base + details.get('path') dumpdir = dumpfile print('Output Path: %s' % dumpfile) fh.write('[%s]\n' % details.get('path')) fh.write('type = %s\n' % details.get('type')) fh.write('revision = %s\n' % details.get('revision')) fh.write('creation = %s\n' % details.get('creation')) fh.write('modified = %s\n' % details.get('modified')) if details.get('type') == 'symlink': fh.write('target_path = %s\n' % details.get('target_path')) else: fh.write('owner = %s\n' % details.get('owner')) fh.write('group = %s\n' % details.get('group')) fh.write('permissions_mode = %s\n' % details.get('permissions_mode')) fh.write('selinux_ctx = %s\n' % details.get('selinux_ctx')) if details.get('type') == 'file': dumpdir = os.path.dirname(dumpfile) if not os.path.isdir(dumpdir): os.makedirs(dumpdir) if details.get('type') == 'file': fh.write('sha256 = %s\n' % details.get('sha256')) fh.write('binary = %s\n' % details.get('binary')) of = open(dumpfile, 'w') of.write(details.get('contents') or '') of.close() fh.write('\n') fh.close() #################### def help_configchannel_details(self): print('configchannel_details: Show the details of a config channel') print('usage: configchannel_details CHANNEL ...') def complete_configchannel_details(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_configchannel_details() return add_separator = False result = [] for channel in args: details = self.client.configchannel.getDetails(self.session, channel) files = self.client.configchannel.listFiles(self.session, channel) if add_separator: print(self.SEPARATOR) add_separator = True result.append('Label: %s' % details.get('label')) result.append('Name: %s' % details.get('name')) result.append('Description: %s' % details.get('description')) result.append('Type: %s' % details.get('configChannelType').get('label')) result.append('') result.append('Files') result.append('-----') for f in files: result.append(f.get('path')) return result #################### def help_configchannel_create(self): print('configchannel_create: Create a configuration channel of specific type') print('''usage: configchannel_create [options]) options: -n NAME -l LABEL -d DESCRIPTION -t TYPE('normal,'state')''') def do_configchannel_create(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-l', '--label') arg_parser.add_argument('-d', '--description') arg_parser.add_argument('-t', '--type') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.name = prompt_user('Name:', noblank=True) options.label = prompt_user('Label:') options.description = prompt_user('Description:') options.type = prompt_user('Type[normal, state]:') if options.label == '': options.label = options.name if options.description == '': options.description = options.name if options.type not in ('normal', 'state'): logging.error('Only [normal/state] values are acceptable for --type') return else: if not options.name: logging.error('A name is required') return if not options.label: options.label = options.name if not options.description: options.description = options.name if not options.type: options.type = 'normal' if options.type not in ('normal', 'state'): logging.error('Only [normal/state] values are acceptable for --type') return self.client.configchannel.create(self.session, options.label, options.name, options.description, options.type) #################### def help_configchannel_delete(self): print('configchannel_delete: Delete a configuration channel') print('usage: configchannel_delete CHANNEL ...') def complete_configchannel_delete(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_configchannel_delete() return # allow globbing of configchannel names channels = filter_results(self.do_configchannel_list('', True), args) logging.debug("configchannel_delete called with args %s, channels=%s" % (args, channels)) if not channels: logging.error("No channels matched argument %s" % args) return # Print the channels prior to the confirmation print('\n'.join(sorted(channels))) if self.user_confirm('Delete these channels [y/N]:'): self.client.configchannel.deleteChannels(self.session, channels) #################### def configfile_getinfo(self, args, options, file_info=None, interactive=False): # Common code which is used in both configchannel_addfile and # system_addconfigfile. Takes args/options from each call and # returns the file_info dict needed to create the file in either # the configchannel or sytem sandbox/local-override respectively # # file_info is the existing info from lookupFileInfo or None if # no file for this path exists already # initialize here instead of multiple times below contents = '' if interactive: # use existing values if available if file_info: for info in file_info: if info.get('path') == options.path: logging.debug('Found existing file in channel') options.owner = info.get('owner') options.group = info.get('group') options.mode = info.get('permissions_mode') options.target_path = info.get('target_path') options.selinux_ctx = info.get('selinux_ctx') contents = info.get('contents') if info.get('type') == 'symlink': options.symlink = True if not options.owner: options.owner = 'root' if not options.group: options.group = 'root' # if this is a new file, ask if it's a symlink if not options.symlink: userinput = prompt_user('Symlink [y/N]:') options.symlink = re.match('y', userinput, re.I) if options.symlink: target_input = prompt_user('Target Path:', noblank=True) selinux_input = prompt_user('SELinux Context [none]:') if target_input: options.target_path = target_input if selinux_input: options.selinux_ctx = selinux_input else: userinput = prompt_user('Directory [y/N]:') options.directory = re.match('y', userinput, re.I) if not options.mode: if options.directory: options.mode = '0755' else: options.mode = '0644' owner_input = prompt_user('Owner [%s]:' % options.owner) group_input = prompt_user('Group [%s]:' % options.group) mode_input = prompt_user('Mode [%s]:' % options.mode) selinux_input = \ prompt_user('SELinux Context [%s]:' % options.selinux_ctx) revision_input = prompt_user('Revision [next]:') if owner_input: options.owner = owner_input if group_input: options.group = group_input if mode_input: options.mode = mode_input if selinux_input: options.selinux_ctx = selinux_input if revision_input: try: options.revision = int(revision_input) except ValueError: logging.warning('The revision must be an integer') if not options.directory: if self.user_confirm('Read an existing file [y/N]:', nospacer=True, ignore_yes=True): options.file = prompt_user('File:') contents = read_file(options.file) if options.binary is None and self.file_is_binary(options.file): options.binary = True logging.debug("Binary detected") elif options.binary: logging.debug("Binary selected") else: if contents: template = contents else: template = '' (contents, _ignore) = editor(template=template, delete=True) else: if not options.path: logging.error('The path is required') return if not options.symlink and not options.directory: if options.file: contents = read_file(options.file) if options.binary is None: options.binary = self.file_is_binary(options.file) if options.binary: logging.debug("Binary detected") elif options.binary: logging.debug("Binary selected") else: logging.error('You must provide the file contents') return if options.symlink and not options.target_path: logging.error('You must provide the target path for a symlink') return # selinux_ctx can't be None if not options.selinux_ctx: options.selinux_ctx = '' # directory can't be None if not options.directory: options.directory = False if options.symlink: file_info = {'target_path': options.target_path, 'selinux_ctx': options.selinux_ctx} print('Path: %s' % options.path) print('Target Path: %s' % file_info['target_path']) print('SELinux Context: %s' % file_info['selinux_ctx']) else: if not options.owner: options.owner = 'root' if not options.group: options.group = 'root' if not options.mode: if options.directory: options.mode = '0755' else: options.mode = '0644' logging.debug("base64 encoding contents") contents = base64.b64encode(contents.encode('utf8')).decode() file_info = {'contents': ''.join(contents), 'owner': options.owner, 'group': options.group, 'selinux_ctx': options.selinux_ctx, 'permissions': options.mode, 'contents_enc64': True, 'binary': options.binary} # Binary set or detected if options.binary: file_info['binary'] = True print('Path: %s' % options.path) print('Directory: %s' % options.directory) print('Owner: %s' % file_info['owner']) print('Group: %s' % file_info['group']) print('Mode: %s' % file_info['permissions']) print('Binary: %s' % file_info['binary']) print('SELinux Context: %s' % file_info['selinux_ctx']) # only add the revision field if the user supplied it if options.revision: file_info['revision'] = int(options.revision) print('Revision: %i' % file_info['revision']) if not options.directory: print('') if options.binary: print('Contents not displayed (base64 encoded)') else: print('Contents') print('--------') if file_info['contents_enc64']: print(base64.b64decode(file_info['contents'])) else: print(file_info['contents']) return file_info def help_configchannel_addfile(self): print('configchannel_addfile/configchannel_updatefile: Create a configuration file') print('''usage: configchannel_addfile/configchannel_updatefile -c CHANNEL - p PATH -f LOCAL_FILE_PATH [OPTIONS]) options: -c CHANNEL -p PATH -r REVISION -o OWNER [default: root] -g GROUP [default: root] -m MODE [defualt: 0644] -x SELINUX_CONTEXT -d path is a directory -s path is a symlink -b path is a binary (or other file which needs base64 encoding) -t SYMLINK_TARGET -f local path to file contents -y automatically proceed with file contents Note re binary/base64: Some text files, notably those containing trailing newlines, those containing ASCII escape characters (or other charaters not allowed in XML) need to be sent as binary (-b). Some effort is made to auto- detect files which require this, but you may need to explicitly specify. ''') def complete_configchannel_addfile(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_addfile(self, args, update_path=''): arg_parser = get_argument_parser() arg_parser.add_argument('-c', '--channel') arg_parser.add_argument('-p', '--path') arg_parser.add_argument('-o', '--owner') arg_parser.add_argument('-g', '--group') arg_parser.add_argument('-m', '--mode') arg_parser.add_argument('-x', '--selinux-ctx') arg_parser.add_argument('-t', '--target-path') arg_parser.add_argument('-f', '--file') arg_parser.add_argument('-r', '--revision') arg_parser.add_argument('-s', '--symlink', action='store_true') arg_parser.add_argument('-b', '--binary', action='store_true') arg_parser.add_argument('-d', '--directory', action='store_true') arg_parser.add_argument('-y', '--yes', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) file_info = None interactive = is_interactive(options) if interactive: # the channel name can be passed in if args: options.channel = args[0] else: while True: print('Configuration Channels') print('----------------------') print('\n'.join(sorted(self.do_configchannel_list('', True)))) print('') options.channel = prompt_user('Select:', noblank=True) # ensure the user enters a valid configuration channel if options.channel in self.do_configchannel_list('', True): break else: print('') logging.warning('%s is not a valid channel' % options.channel) print('') if update_path: options.path = update_path else: options.path = prompt_user('Path:', noblank=True) # check if this file already exists try: file_info = \ self.client.configchannel.lookupFileInfo(self.session, options.channel, [options.path]) except xmlrpclib.Fault: logging.debug("No existing file information found for %s" % options.path) file_info = None file_info = self.configfile_getinfo(args, options, file_info, interactive) if not options.channel: logging.error("No config channel specified!") self.help_configchannel_addfile() return if not file_info: logging.error("Error obtaining file info") self.help_configchannel_addfile() return if options.yes or self.user_confirm(): if options.symlink: self.client.configchannel.createOrUpdateSymlink(self.session, options.channel, options.path, file_info) else: # compatibility for Satellite 5.3 if not self.check_api_version('10.11'): del file_info['selinux_ctx'] if 'revision' in file_info: del file_info['revision'] if options.directory: if 'contents' in file_info: del file_info['contents'] self.client.configchannel.createOrUpdatePath(self.session, options.channel, options.path, options.directory, file_info) #################### def help_configchannel_updateinitsls(self): print('configchannel_updateinitsls: Update init.sls file') print('''usage: configchannel_updateinitsls -c CHANNEL -f LOCAL_FILE_PATH [OPTIONS]) options: -c CHANNEL -f local path to file contents -y automatically proceed with file contents ''') def do_configchannel_updateinitsls(self, args, update_path=''): arg_parser = get_argument_parser() arg_parser.add_argument('-c', '--channel') arg_parser.add_argument('-f', '--file') arg_parser.add_argument('-y', '--yes', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) file_info = None path = "/init.sls" interactive = is_interactive(options) if interactive: # the channel name can be passed in if args: options.channel = args[0] else: while True: print('Configuration Channels') print('----------------------') print('\n'.join(sorted(self.do_configchannel_list('', True)))) print('') options.channel = prompt_user('Select:', noblank=True) # ensure the user enters a valid configuration channel if options.channel in self.do_configchannel_list('', True): break else: print('') logging.warning('%s is not a valid channel' % options.channel) print('') # check if this file already exists try: file_info = \ self.client.configchannel.lookupFileInfo(self.session, options.channel, [path]) except xmlrpclib.Fault: logging.error("No existing file information found for %s" % options.path) return contents = file_info[0].get('contents') if self.user_confirm('Read an existing file [y/N]:', nospacer=True, ignore_yes=True): options.file = prompt_user('File:') contents = read_file(options.file) if self.file_is_binary(options.file): logging.debug("Binary selected") else: if contents: template = contents else: template = '' (contents, _ignore) = editor(template=template, delete=True) else: if options.file: contents = read_file(options.file) else: logging.error('You must provide the file contents') return contents = base64.b64encode(contents.encode('utf8')).decode() file_info = {'contents': ''.join(contents), 'contents_enc64': True } if not options.channel: logging.error("No config channel specified!") self.help_configchannel_updateinitsls() return if not file_info: logging.error("Error obtaining file info") self.help_configchannel_updateinitsls() return print('contents_enc64: %s' % file_info['contents_enc64']) print('Contents') print('--------') print(base64.b64decode(file_info['contents'])) if options.yes or self.user_confirm(): self.client.configchannel.updateInitSls(self.session, options.channel, file_info) #################### def help_configchannel_updatefile(self): self.help_configchannel_addfile() def complete_configchannel_updatefile(self, text, line, beg, end): return self.complete_configchannel_addfile(text, line, beg, end) def do_configchannel_updatefile(self, args): return self.do_configchannel_addfile(args) #################### def help_configchannel_removefiles(self): print('configchannel_removefiles: Remove configuration files') print('usage: configchannel_removefiles CHANNEL <FILE ...>') def complete_configchannel_removefiles(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_configchannel_list('', True), text) elif len(parts) > 2: channel = parts[1] return tab_completer(self.do_configchannel_listfiles(channel, True), text) def do_configchannel_removefiles(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_configchannel_removefiles() return channel = args.pop(0) files = args if self.user_confirm('Remove these files [y/N]:'): self.client.configchannel.deleteFiles(self.session, channel, files) #################### def help_configchannel_verifyfile(self): print('configchannel_verifyfile: Verify a configuration file') print('usage: configchannel_verifyfile CHANNEL FILE <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_configchannel_verifyfile(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_configchannel_list('', True), text) elif len(parts) == 3: channel = parts[1] return tab_completer(self.do_configchannel_listfiles(channel, True), text) elif len(parts) > 3: return self.tab_complete_systems(text) def do_configchannel_verifyfile(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 3: self.help_configchannel_verifyfile() return channel = args[0] path = args[1] # use the systems listed in the SSM if re.match('ssm', args[2], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args[2:]) system_ids = [self.get_system_id(s) for s in systems] if not system_ids: logging.error('No valid system selected') return action_id = \ self.client.configchannel.scheduleFileComparisons(self.session, channel, path, system_ids) logging.info('Action ID: %i' % action_id) #################### def help_configchannel_export(self): print('configchannel_export: export config channel(s) to json format file') print('''usage: configchannel_export <CHANNEL>... [options]) options: -f outfile.json : specify an output filename, defaults to <CHANNEL>.json if exporting a single channel, ccs.json for multiple channels, or cc_all.json if no CHANNEL specified e.g (export ALL) Note : CHANNEL list is optional, default is to export ALL''') def complete_configchannel_export(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def export_configchannel_getdetails(self, channel): # Get the cc details logging.info("Getting config channel details for %s" % channel) details = self.client.configchannel.getDetails(self.session, channel) files = self.client.configchannel.listFiles(self.session, channel) details['files'] = [] paths = [f['path'] for f in files] fileinfo = [] # Some versions of the API blow up when lookupFileInfo is asked to # return details of files containing non-XML-valid characters. # later API versions simply return empty file contents, but to # ensure the least-bad operation with older (sat 5.3) API versions # we can iterate over each file, then we just error on individual files # instead of failing to export anything at all... for p in paths: logging.debug("Found file %s for %s" % (p, channel)) try: pinfo = self.client.configchannel.lookupFileInfo(self.session, channel, [p]) if pinfo: fileinfo.append(pinfo[0]) except xmlrpclib.Fault: logging.error("Failed to get details for file %s from %s" % (p, channel)) # Now we strip the datetime fields from the Info structs, as they # are not JSON serializable with the default encoder, and we don't # need them on import anyway # We also strip some other fields which are not useful on import # This is a bit complicated because the createOrUpdateFoo functions # take two different struct formats, which are both different to # the format returned by lookupFileInfo, doh! # We get: We need: # (file/dir) (symlink) # string "type" Y Y # string "path" Y Y # string "target_path" N Y # string "channel" N N # string "contents" Y N # int "revision" N (auto) N (auto) # dateTime.iso8601 "creation" N N # dateTime.iso8601 "modified" N N # string "owner" Y N # string "group" Y N # int "permissions" Y (as string!) N # string "permissions_mode" N N # string "selinux_ctx" Y Y # boolean "binary" Y N # string "sha256" N N # string "macro-start-delimiter" Y N # string "macro-end-delimiter" Y N for f in fileinfo: if f['type'] == 'symlink': for k in ['contents', 'owner', 'group', 'permissions', 'macro-start-delimiter', 'macro-end-delimiter']: if k in f: del f[k] else: if 'target_path' in f: del f['target_path'] f['permissions'] = str(f['permissions']) # If we're using a recent API version files exported with no contents # i.e binary or non-xml encodable ascii files can be exported as # base64 encoded if not 'contents' in f: if f['type'] != 'directory': if not self.check_api_version('11.1'): logging.warning("File %s could not be exported " % f['path'] + "with this API version(needs base64 encoding)") else: logging.info("File %s could not be exported as" % f['path'] + " text...getting base64 encoded version") b64f = self.client.configchannel.getEncodedFileRevision( self.session, channel, f['path'], f['revision']) f['contents'] = b64f['contents'] f['contents_enc64'] = b64f['contents_enc64'] for k in ['channel', 'revision', 'creation', 'modified', 'permissions_mode', 'sha256']: if k in f: del f[k] details['files'] = fileinfo return details def do_configchannel_export(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) filename = "" if options.file != None: logging.debug("Passed filename do_configchannel_export %s" % options.file) filename = options.file # Get the list of ccs to export and sort out the filename if required ccs = [] if not args: if not filename: filename = "cc_all.json" logging.info("Exporting ALL config channels to %s" % filename) ccs = self.do_configchannel_list('', True) else: # allow globbing of configchannel names ccs = filter_results(self.do_configchannel_list('', True), args) logging.debug("configchannel_export called with args %s, ccs=%s" % (args, ccs)) if not ccs: logging.error("Error, no valid config channel passed, " + "check name is correct with spacecmd configchannel_list") return if not filename: # No filename arg, so we try to do something sensible: # If we are exporting exactly one cc, we default to ccname.json # otherwise, generic ccs.json name if len(ccs) == 1: filename = "%s.json" % ccs[0] else: filename = "ccs.json" # Dump as a list of dict ccdetails_list = [] for c in ccs: logging.info("Exporting cc %s to %s" % (c, filename)) ccdetails_list.append(self.export_configchannel_getdetails(c)) logging.debug("About to dump %d ccs to %s" % (len(ccdetails_list), filename)) # Check if filepath exists, if it is an existing file # we prompt the user for confirmation if os.path.isfile(filename): if not self.user_confirm("File %s exists, " % filename + "confirm overwrite file? (y/n)"): return if json_dump_to_file(ccdetails_list, filename) != True: logging.error("Error saving exported config channels to file" % filename) return #################### def help_configchannel_import(self): print('configchannel_import: import config channel(s) from json file') print('''usage: configchannel_import <JSONFILES...>''') def do_configchannel_import(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: logging.error("Error, no filename passed") self.help_configchannel_import() return for filename in args: logging.debug("Passed filename do_configchannel_import %s" % filename) ccdetails_list = json_read_from_file(filename) if not ccdetails_list: logging.error("Error, could not read json data from %s" % filename) return for ccdetails in ccdetails_list: if self.import_configchannel_fromdetails(ccdetails) != True: logging.error("Error importing configchannel %s" % ccdetails['name']) # create a new cc based on the dict from export_configchannel_getdetails def import_configchannel_fromdetails(self, ccdetails): # First we check that an existing channel with the same name does not exist existing_ccs = self.do_configchannel_list('', True) if ccdetails['name'] in existing_ccs: logging.warning("Config channel %s already exists! Skipping!" % ccdetails['name']) return False else: # create the cc, we need to drop the org prefix from the cc name logging.info("Importing config channel %s" % ccdetails['name']) channeltype = 'normal' if 'configChannelType' in ccdetails: channeltype = ccdetails['configChannelType']['label'] # Create the channel self.client.configchannel.create(self.session, ccdetails['label'], ccdetails['name'], ccdetails['description'], channeltype) for filedetails in ccdetails['files']: path = filedetails['path'] del filedetails['path'] logging.info("Found %s %s for cc %s" % (filedetails['type'], path, ccdetails['name'])) ret = None if filedetails['type'] == 'symlink': del filedetails['type'] logging.debug("Adding symlink %s" % filedetails) ret = self.client.configchannel.createOrUpdateSymlink( self.session, ccdetails['label'], path, filedetails) elif filedetails['type'] == 'sls': # Filter out everything except the file contents: init_sls_details = {k:v for (k,v) in filedetails.items() if k in ['contents', 'contents_enc64']} ret = self.client.configchannel.updateInitSls( self.session, ccdetails['label'], init_sls_details) else: if filedetails['type'] == 'directory': isdir = True if 'contents' in filedetails: del filedetails['contents'] else: isdir = False # If binary files (or those containing characters which are # invalid in XML, e.g the ascii escape character) are # exported, on older API versions, you end up with a file # with no "contents" key ( # I guess the best thing to do here flag an error and # import everything else if not 'contents' in filedetails: logging.error( "Failed trying to import file %s (empty content)" % path) logging.error("Older APIs can't export encoded files") continue if not filedetails['contents_enc64']: logging.debug("base64 encoding file") filedetails['contents'] = \ base64.b64encode(filedetails['contents'].encode('utf8')) filedetails['contents_enc64'] = True logging.debug("Creating %s %s" % (filedetails['type'], filedetails)) if 'type' in filedetails: del filedetails['type'] ret = self.client.configchannel.createOrUpdatePath( self.session, ccdetails['label'], path, isdir, filedetails) if ret != None: logging.debug("Added file %s to %s" % (ret['path'], ccdetails['name'])) else: logging.error("Error adding file %s to %s" % (filedetails['path'], ccdetails['label'])) continue return True #################### def help_configchannel_clone(self): print('configchannel_clone: Clone config channel(s)') print('''usage examples:) configchannel_clone foo_label -c bar_label configchannel_clone foo_label1 foo_label2 -c prefix configchannel_clone foo_label -x "s/foo/bar" configchannel_clone foo_label1 foo_label2 -x "s/foo/bar" options: -c CLONE_LABEL : name/label of the resulting cc (note does not update description, see -x option), treated as a prefix if multiple keys are passed -x "s/foo/bar" : Optional regex replacement, replaces foo with bar in the clone name, label and description Note : If no -c or -x option is specified, interactive is assumed''') def complete_configchannel_clone(self, text, line, beg, end): return tab_completer(self.do_configchannel_list('', True), text) def do_configchannel_clone(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-c', '--clonelabel') arg_parser.add_argument('-x', '--regex') (args, options) = parse_command_arguments(args, arg_parser) allccs = self.do_configchannel_list('', True) if is_interactive(options): print('') print('Config Channels') print('------------------') print('\n'.join(sorted(allccs))) print('') if len(args) == 1: print("Channel to clone: %s" % args[0]) else: # Clear out any args as interactive doesn't handle multiple ccs args = [] args.append(prompt_user('Channel to clone:', noblank=True)) options.clonelabel = prompt_user('Clone label:', noblank=True) else: if not options.clonelabel and not options.regex: logging.error("Error - must specify either -c or -x options!") self.help_configchannel_clone() else: logging.debug("%s : %s" % (options.clonelabel, options.regex)) if not args: logging.error("Error no channel label passed!") self.help_configchannel_clone() return logging.debug("Got args=%s %d" % (args, len(args))) # allow globbing of configchannel names ccs = filter_results(self.do_configchannel_list('', True), args) logging.debug("Filtered ccs %s" % ccs) for cc in ccs: logging.debug("Cloning %s" % cc) ccdetails = self.export_configchannel_getdetails(cc) # If the -x/--regex option is passed, do a sed-style replacement over # the name, label and description. This makes it easier to clone when # content is based on a known naming convention if options.regex: # Expect option to be formatted like a sed-replacement, s/foo/bar findstr = options.regex.split("/")[1] replacestr = options.regex.split("/")[2] logging.debug("--regex selected with %s, replacing %s with %s" % (options.regex, findstr, replacestr)) newname = re.sub(findstr, replacestr, ccdetails['name']) ccdetails['name'] = newname newlabel = re.sub(findstr, replacestr, ccdetails['label']) ccdetails['label'] = newlabel newdesc = re.sub(findstr, replacestr, ccdetails['description']) ccdetails['description'] = newdesc logging.debug("regex mode : %s %s %s" % (ccdetails['name'], ccdetails['label'], ccdetails['description'])) elif options.clonelabel: if len(ccs) > 1: newlabel = options.clonelabel + ccdetails['label'] ccdetails['label'] = newlabel newname = options.clonelabel + ccdetails['name'] ccdetails['name'] = newname logging.debug("clonelabel mode with >1 channel : %s" % ccdetails['label']) else: newlabel = options.clonelabel ccdetails['label'] = newlabel newname = options.clonelabel ccdetails['name'] = newname logging.debug("clonelabel mode with 1 channel : %s" % ccdetails['label']) # Finally : import the cc from the modified ccdetails if self.import_configchannel_fromdetails(ccdetails) != True: logging.error("Failed to clone %s to %s" % (cc, ccdetails['label'])) #################### # configchannel helper def is_configchannel(self, name): if not name: return return name in self.do_configchannel_list(name, True) def check_configchannel(self, name): if not name: logging.error("no configchannel given") return False if not self.is_configchannel(name): logging.error("invalid configchannel label " + name) return False return True def dump_configchannel_filedetails(self, name, filename): content = self.do_configchannel_filedetails(name + " " + filename) return content def dump_configchannel(self, name, replacedict=None, excludes=None): if not excludes: excludes = ["Revision:", "Created:", "Modified:"] content = self.do_configchannel_details(name) for filename in self.do_configchannel_listfiles(name, True): content.extend(self.dump_configchannel_filedetails(name, filename)) content = get_normalized_text(content, replacedict=replacedict, excludes=excludes) return content #################### def help_configchannel_diff(self): print('configchannel_diff: diff between config channels') print('') print('usage: configchannel_diff SOURCE_CHANNEL TARGET_CHANNEL') def complete_configchannel_diff(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_configchannel_list('', True), text) if args == 3: return tab_completer(self.do_configchannel_list('', True), text) return [] def do_configchannel_diff(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1 and len(args) != 2: self.help_configchannel_diff() return source_channel = args[0] if not self.check_configchannel(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_configchannel_getcorresponding"): # can a corresponding channel name be found automatically? target_channel = self.do_configchannel_getcorresponding(source_channel) if not self.check_configchannel(target_channel): return source_replacedict, target_replacedict = get_string_diff_dicts(source_channel, target_channel) source_data = self.dump_configchannel(source_channel, source_replacedict) target_data = self.dump_configchannel(target_channel, target_replacedict) return diff(source_data, target_data, source_channel, target_channel) #################### def help_configchannel_sync(self): print('configchannel_sync:') print('sync config files between two config channels') print('') print('usage: configchannel_sync SOURCE_CHANNEL TARGET_CHANNEL') def complete_configchannel_sync(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_configchannel_list('', True), text) if args == 3: return tab_completer(self.do_configchannel_list('', True), text) return [] def do_configchannel_sync(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1 and len(args) != 2: self.help_configchannel_sync() return source_channel = args[0] if not self.check_configchannel(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_configchannel_getcorresponding"): # can a corresponding channel name be found automatically? target_channel = self.do_configchannel_getcorresponding(source_channel) if not self.check_configchannel(target_channel): return logging.info("syncing files from configchannel " + source_channel + " to " + target_channel) source_files = set(self.do_configchannel_listfiles(source_channel, doreturn=True)) target_files = set(self.do_configchannel_listfiles(target_channel, doreturn=True)) both = source_files & target_files if both: print("files common in both channels:") print("\n".join(both)) print('') source_only = source_files.difference(target_files) if source_only: print("files only in source " + source_channel) print("\n".join(source_only)) print('') target_only = target_files.difference(source_files) if target_only: print("files only in target " + target_channel) print("\n".join(target_only)) print('') if both: print("files that are in both channels will be overwritten in the target channel") if source_only: print("files only in the source channel will be added to the target channel") if target_only: print("files only in the target channel will be deleted") if not (both or source_only or target_only): logging.info("nothing to do") return if not self.user_confirm('perform synchronisation [y/N]:'): return source_data_list = self.client.configchannel.lookupFileInfo( self.session, source_channel, list(both) + list(source_only)) for source_data in source_data_list: if source_data.get('type') == 'file' or source_data.get('type') == 'directory': if source_data.get('contents') and not source_data.get('binary'): contents = source_data.get('contents').encode('base64') else: contents = source_data.get('contents') target_data = { 'contents': contents, 'contents_enc64': True, 'owner': source_data.get('owner'), 'group': source_data.get('group'), # get permissions from permissions_mode instead of permissions 'permissions': source_data.get('permissions_mode'), 'selinux_ctx': source_data.get('selinux_ctx'), 'macro-start-delimiter': source_data.get('macro-start-delimiter'), 'macro-end-delimiter': source_data.get('macro-end-delimiter'), } for k, v in target_data.items(): if not v: del target_data[k] if source_data.get('type') == 'directory': del target_data['contents_enc64'] logging.debug(source_data.get('path') + ": " + str(target_data)) self.client.configchannel.createOrUpdatePath(self.session, target_channel, source_data.get('path'), source_data.get('type') == 'directory', target_data) elif source_data.get('type') == 'symlink': target_data = { 'target_path': source_data.get('target_path'), 'selinux_ctx': source_data.get('selinux_ctx'), } logging.debug(source_data.get('path') + ": " + str(target_data)) self.client.configchannel.createOrUpdateSymlink(self.session, target_channel, source_data.get('path'), target_data) else: logging.warning("unknown file type " + source_data.type) # removing all files from target channel that did not exist on source channel if target_only: #self.do_configchannel_removefiles( target_channel + " " + "/.metainfo" + " ".join(target_only) ) self.do_configchannel_removefiles(target_channel + " " + " ".join(target_only)) 07070100000018000081B40000000000000000000000015D65A5B20000178C000000000000000000000000000000000000002300000000spacecmd/src/spacecmd/cryptokey.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_cryptokey_create(self): print('cryptokey_create: Create a cryptographic key') print('''usage: cryptokey_create [options]) options: -t GPG or SSL -d DESCRIPTION -f KEY_FILE''') def do_cryptokey_create(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-t', '--type') arg_parser.add_argument('-d', '--description') arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) options.contents = None if is_interactive(options): options.type = prompt_user('GPG or SSL [G/S]:') options.description = '' while options.description == '': options.description = prompt_user('Description:') if self.user_confirm('Read an existing file [y/N]:', nospacer=True, ignore_yes=True): options.file = prompt_user('File:') else: options.contents = editor(delete=True) else: if not options.type: logging.error('The key type is required') return if not options.description: logging.error('A description is required') return if not options.file: logging.error('A file containing the key is required') return # read the file the user specified if options.file: options.contents = read_file(options.file) if not options.contents: logging.error('No contents of the file') return # translate the key type to what the server expects if re.match('G', options.type, re.I): options.type = 'GPG' elif re.match('S', options.type, re.I): options.type = 'SSL' else: logging.error('Invalid key type') return self.client.kickstart.keys.create(self.session, options.description, options.type, options.contents) #################### def help_cryptokey_delete(self): print('cryptokey_delete: Delete a cryptographic key') print('usage: cryptokey_delete NAME') def complete_cryptokey_delete(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_cryptokey_list('', True), text) def do_cryptokey_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_cryptokey_delete() return # allow globbing of cryptokey names keys = filter_results(self.do_cryptokey_list('', True), args) logging.debug("cryptokey_delete called with args %s, keys=%s" % (args, keys)) if not keys: logging.error("No keys matched argument %s" % args) return # Print the keys prior to the confirmation print('\n'.join(sorted(keys))) if self.user_confirm('Delete key(s) [y/N]:'): for key in keys: self.client.kickstart.keys.delete(self.session, key) #################### def help_cryptokey_list(self): print('cryptokey_list: List all cryptographic keys (SSL, GPG)') print('usage: cryptokey_list') def do_cryptokey_list(self, args, doreturn=False): keys = self.client.kickstart.keys.listAllKeys(self.session) keys = [k.get('description') for k in keys] if doreturn: return keys else: if keys: print('\n'.join(sorted(keys))) #################### def help_cryptokey_details(self): print('cryptokey_details: Show the contents of a cryptographic key') print('usage: cryptokey_details KEY ...') def complete_cryptokey_details(self, text, line, beg, end): return tab_completer(self.do_cryptokey_list('', True), text) def do_cryptokey_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_cryptokey_details() return # allow globbing of cryptokey names keys = filter_results(self.do_cryptokey_list('', True), args) logging.debug("cryptokey_details called with args %s, keys=%s" % (args, keys)) if not keys: logging.error("No keys matched argument %s" % args) return add_separator = False for key in keys: try: details = self.client.kickstart.keys.getDetails(self.session, key) except xmlrpclib.Fault: logging.warning('%s is not a valid crypto key' % key) return if add_separator: print(self.SEPARATOR) add_separator = True print('Description: %s' % details.get('description')) print('Type: %s' % details.get('type')) print('') print(details.get('content')) 07070100000019000081B40000000000000000000000015D65A5B200001619000000000000000000000000000000000000002400000000spacecmd/src/spacecmd/custominfo.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 from spacecmd.utils import * def help_custominfo_createkey(self): print('custominfo_createkey: Create a custom key') print('usage: custominfo_createkey [NAME] [DESCRIPTION]') def do_custominfo_createkey(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if args: key = args[0] else: key = '' while key == '': key = prompt_user('Name:') if len(args) > 1: description = ' '.join(args[1:]) else: description = prompt_user('Description:') if description == '': description = key self.client.system.custominfo.createKey(self.session, key, description) #################### def help_custominfo_deletekey(self): print('custominfo_deletekey: Delete a custom key') print('usage: custominfo_deletekey KEY ...') def complete_custominfo_deletekey(self, text, line, beg, end): return tab_completer(self.do_custominfo_listkeys('', True), text) def do_custominfo_deletekey(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_custominfo_deletekey() return # allow globbing of custominfo key names keys = filter_results(self.do_custominfo_listkeys('', True), args) logging.debug("customkey_deletekey called with args %s, keys=%s" % (args, keys)) if not keys: logging.error("No keys matched argument %s" % args) return # Print the keys prior to the confirmation print('\n'.join(sorted(keys))) if not self.user_confirm('Delete these keys [y/N]:'): return for key in keys: self.client.system.custominfo.deleteKey(self.session, key) #################### def help_custominfo_listkeys(self): print('custominfo_listkeys: List all custom keys') print('usage: custominfo_listkeys') def do_custominfo_listkeys(self, args, doreturn=False): keys = self.client.system.custominfo.listAllKeys(self.session) keys = [k.get('label') for k in keys] if doreturn: return keys else: if keys: print('\n'.join(sorted(keys))) #################### def help_custominfo_details(self): print('custominfo_details: Show the details of a custom key') print('usage: custominfo_details KEY ...') def complete_custominfo_details(self, text, line, beg, end): return tab_completer(self.do_custominfo_listkeys('', True), text) def do_custominfo_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_custominfo_details() return # allow globbing of custominfo key names keys = filter_results(self.do_custominfo_listkeys('', True), args) logging.debug("customkey_details called with args: '{}', keys: '{}'.".format( ", ".join(args), ", ".join(keys))) if not keys: logging.error("No keys matched argument '{}'.".format(", ".join(args))) return add_separator = False all_keys = self.client.system.custominfo.listAllKeys(self.session) for key in keys: details = {} for key_details in all_keys: if key_details.get('label') == key: details = key_details if details: if add_separator: print(self.SEPARATOR) add_separator = True print('Label: %s' % (details.get('label') or "N/A")) print('Description: %s' % (details.get('description') or "N/A")) print('Modified: %s' % (details.get('last_modified') or "N/A")) print('System Count: %i' % (details.get('system_count') or 0)) #################### def help_custominfo_updatekey(self): print('custominfo_updatekey: Update a custom key') print('usage: custominfo_updatekey [NAME] [DESCRIPTION]') def do_custominfo_updatekey(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if args: key = args[0] else: key = '' while key == '': key = prompt_user('Name:') if len(args) > 1: description = ' '.join(args[1:]) else: description = prompt_user('Description:') if description == '': description = key self.client.system.custominfo.updateKey(self.session, key, description) 0707010000001A000081B40000000000000000000000015D65A5B20000234B000000000000000000000000000000000000002600000000spacecmd/src/spacecmd/distribution.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from spacecmd.utils import * def help_distribution_create(self): print('distribution_create: Create a Kickstart tree') print('''usage: distribution_create [options] options: -n NAME -p path to tree -b base channel to associate with -t install type [fedora|rhel_4/5/6|generic_rpm]''') def do_distribution_create(self, args, update=False): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-p', '--path') arg_parser.add_argument('-b', '--base-channel') arg_parser.add_argument('-t', '--install-type') (args, options) = parse_command_arguments(args, arg_parser) # fill in the name of the distribution when updating if update: if args: options.name = args[0] elif not options.name: logging.error('The name of the distribution is required') return if is_interactive(options): if not update: options.name = prompt_user('Name:', noblank=True) options.path = prompt_user('Path to Kickstart Tree:', noblank=True) options.base_channel = '' while options.base_channel == '': print('') print('Base Channels') print('-------------') print('\n'.join(sorted(self.list_base_channels()))) print('') options.base_channel = prompt_user('Base Channel:') if options.base_channel not in self.list_base_channels(): logging.warning('Invalid channel label') options.base_channel = '' install_types = \ self.client.kickstart.tree.listInstallTypes(self.session) install_types = [t.get('label') for t in install_types] options.install_type = '' while options.install_type == '': print('') print('Install Types') print('-------------') print('\n'.join(sorted(install_types))) print('') options.install_type = prompt_user('Install Type:') if options.install_type not in install_types: logging.warning('Invalid install type') options.install_type = '' else: if not options.name: logging.error('A name is required') return if not options.path: logging.error('A path is required') return if not options.base_channel: logging.error('A base channel is required') return if not options.install_type: logging.error('An install type is required') return if update: self.client.kickstart.tree.update(self.session, options.name, options.path, options.base_channel, options.install_type) else: self.client.kickstart.tree.create(self.session, options.name, options.path, options.base_channel, options.install_type) #################### def help_distribution_list(self): print('distribution_list: List the available autoinstall trees') print('usage: distribution_list') def do_distribution_list(self, args, doreturn=False): channels = self.client.kickstart.listAutoinstallableChannels(self.session) avail_trees = [] for c in channels: trees = self.client.kickstart.tree.list(self.session, c.get('label')) for t in trees: label = t.get('label') if label not in avail_trees: avail_trees.append(label) if doreturn: return avail_trees else: if avail_trees: print('\n'.join(sorted(avail_trees))) #################### def help_distribution_delete(self): print('distribution_delete: Delete a Kickstart tree') print('usage: distribution_delete LABEL') def complete_distribution_delete(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_distribution_list('', True), text) def do_distribution_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_distribution_delete() return # allow globbing of distribution names dists = filter_results(self.do_distribution_list('', True), args) logging.debug("distribution_delete called with args %s, dists=%s" % (args, dists)) if not dists: logging.error("No distributions matched argument %s" % args) return # Print the distributions prior to the confirmation print('\n'.join(sorted(dists))) if self.user_confirm('Delete distribution tree(s) [y/N]:'): for d in dists: self.client.kickstart.tree.delete(self.session, d) #################### def help_distribution_details(self): print('distribution_details: Show the details of a Kickstart tree') print('usage: distribution_details LABEL') def complete_distribution_details(self, text, line, beg, end): return tab_completer(self.do_distribution_list('', True), text) def do_distribution_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_distribution_details() return # allow globbing of distribution names dists = filter_results(self.do_distribution_list('', True), args) logging.debug("distribution_details called with args %s, dists=%s" % (args, dists)) if not dists: logging.error("No distributions matched argument %s" % args) return add_separator = False for label in dists: details = self.client.kickstart.tree.getDetails(self.session, label) channel = \ self.client.channel.software.getDetails(self.session, details.get('channel_id')) if add_separator: print(self.SEPARATOR) add_separator = True print('Name: %s' % details.get('label')) print('Path: %s' % details.get('abs_path')) print('Channel: %s' % channel.get('label')) #################### def help_distribution_rename(self): print('distribution_rename: Rename a Kickstart tree') print('usage: distribution_rename OLDNAME NEWNAME') def complete_distribution_rename(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_distribution_list('', True), text) def do_distribution_rename(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_distribution_rename() return oldname = args[0] newname = args[1] self.client.kickstart.tree.rename(self.session, oldname, newname) #################### def help_distribution_update(self): print('distribution_update: Update the path of a Kickstart tree') print('''usage: distribution_update NAME [options] options: -p path to tree -b base channel to associate with -t install type [fedora|rhel_4/5/6|generic_rpm]''') def complete_distribution_update(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_distribution_list('', True), text) def do_distribution_update(self, args): arguments, _ = parse_command_arguments(args, get_argument_parser()) if not arguments: self.help_distribution_update() else: return self.do_distribution_create(args, update=True) 0707010000001B000081B40000000000000000000000015D65A5B2000045BB000000000000000000000000000000000000002000000000spacecmd/src/spacecmd/errata.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2013--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from operator import itemgetter try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_errata_list(self): print('errata_list: List all patches') print('usage: errata_list') def do_errata_list(self, args, doreturn=False): self.generate_errata_cache() if doreturn: return self.all_errata.keys() else: if self.all_errata.keys(): print('\n'.join(sorted(self.all_errata.keys()))) #################### def help_errata_summary(self): print('errata_summary: Print a summary of all errata') print('usage: errata_summary') def do_errata_summary(self, args): self.generate_errata_cache() map(print_errata_summary, sorted(self.all_errata.values(), key=itemgetter('advisory_name'))) #################### def help_errata_apply(self): print('errata_apply: Apply an erratum to all affected systems') print('''usage: errata_apply [options] ERRATA|search:XXX ...) options: -s START_TIME''') print('') print(self.HELP_TIME_OPTS) def complete_errata_apply(self, text, line, beg, end): return self.tab_complete_errata(text) def do_errata_apply(self, args, only_systems=None): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) only_systems = only_systems or [] if not args: self.help_errata_apply() return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # allow globbing and searching via arguments errata_list = self.expand_errata(args) systems = [] summary = [] to_apply_by_name = {} for erratum in errata_list: try: # get the systems affected by each errata affected_systems = \ self.client.errata.listAffectedSystems(self.session, erratum) # build a list of systems that we will schedule errata for, # indexed by errata name for system in affected_systems: # add this system to the list of systems affected by # this erratum if we were not passed a list of systems # (and therefore all systems are to be touched) or we were # passed a list of systems and this one is part of that list if not only_systems or system.get('name') in only_systems: if erratum not in to_apply_by_name: to_apply_by_name[erratum] = [] if system.get('name') not in to_apply_by_name[erratum]: to_apply_by_name[erratum].append(system.get('name')) except xmlrpclib.Fault: logging.debug('%s does not affect any systems' % erratum) continue # make a summary list to show the user if erratum in to_apply_by_name: summary.append('%s %s' % (erratum.ljust(15), str(len(to_apply_by_name[erratum])).rjust(3))) else: logging.debug('%s does not affect any systems' % erratum) # get a unique list of all systems we need to touch for systemlist in to_apply_by_name.values(): systems += systemlist systems = list(set(systems)) if not systems: logging.warning('No patches to apply') return # a summary of which errata we're going to apply print('Errata Systems') print('-------------- -------') print('\n'.join(sorted(summary))) print('') print('Start Time: %s' % options.start_time) if not self.user_confirm('Apply these patches [y/N]:'): return # if the API supports it, try to schedule multiple systems for one erratum # in order to reduce the number of actions scheduled if self.check_api_version('10.11'): to_apply = {} for system in systems: system_id = self.get_system_id(system) # only attempt to schedule unscheduled errata system_errata = self.client.system.getUnscheduledErrata(self.session, system_id) # make a list of systems for each erratum for erratum in system_errata: erratum_id = erratum.get('id') if erratum.get('advisory_name') in errata_list: if erratum_id not in to_apply: to_apply[erratum_id] = [] to_apply[erratum_id].append(system_id) # apply the errata for erratum in to_apply: self.client.system.scheduleApplyErrata(self.session, to_apply[erratum], [erratum], options.start_time) logging.info('Scheduled %i system(s) for %s' % (len(to_apply[erratum]), self.get_erratum_name(erratum))) else: for system in systems: system_id = self.get_system_id(system) # only schedule unscheduled errata system_errata = self.client.system.getUnscheduledErrata(self.session, system_id) # if an errata specified for installation is unscheduled for # this system, add it to the list to schedule errata_to_apply = [] for erratum in errata_list: for e in system_errata: if erratum == e.get('advisory_name'): errata_to_apply.append(e.get('id')) break if not errata_to_apply: logging.warning('No patches to schedule for %s' % system) continue # this results in one action per erratum for each server self.client.system.scheduleApplyErrata(self.session, system_id, errata_to_apply, options.start_time) logging.info('Scheduled %i patches for %s' % (len(errata_to_apply), system)) #################### def help_errata_listaffectedsystems(self): print('errata_listaffectedsystems: List of systems affected by an patch') print('usage: errata_listaffectedsystems ERRATA|search:XXX ...') def complete_errata_listaffectedsystems(self, text, line, beg, end): return self.tab_complete_errata(text) def do_errata_listaffectedsystems(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_errata_listaffectedsystems() return # allow globbing and searching via arguments errata_list = self.expand_errata(args) add_separator = False for erratum in errata_list: systems = self.client.errata.listAffectedSystems(self.session, erratum) if systems: if add_separator: print(self.SEPARATOR) add_separator = True print('%s:' % erratum) print('\n'.join(sorted([s.get('name') for s in systems]))) #################### def help_errata_listcves(self): print('errata_listcves: List of CVEs addressed by an patch') print('usage: errata_listcves ERRATA|search:XXX ...') def complete_errata_listcves(self, text, line, beg, end): return self.tab_complete_errata(text) def do_errata_listcves(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_errata_listcves() return # allow globbing and searching via arguments errata_list = self.expand_errata(args) add_separator = False for erratum in errata_list: cves = self.client.errata.listCves(self.session, erratum) if cves: if len(errata_list) > 1: if add_separator: print(self.SEPARATOR) add_separator = True print('%s:' % erratum) print('\n'.join(sorted(cves))) #################### def help_errata_findbycve(self): print('errata_findbycve: List errata addressing a CVE') print('usage: errata_findbycve CVE-YYYY-NNNN ...') def complete_errata_findbycve(self, text, line, beg, end): return self.tab_complete_errata(text) def do_errata_findbycve(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_errata_findbycve() return # More than one CVE may be specified cve_list = args logging.debug("Got CVE list %s" % cve_list) add_separator = False # Then iterate over the requested CVEs and dump the errata which match for c in cve_list: if add_separator: print(self.SEPARATOR) add_separator = True print("%s:" % c) errata = self.client.errata.findByCve(self.session, c) if errata: for e in errata: print("%s" % e.get('advisory_name')) #################### def help_errata_details(self): print('errata_details: Show the details of an patch') print('usage: errata_details ERRATA|search:XXX ...') def complete_errata_details(self, text, line, beg, end): return self.tab_complete_errata(text) def do_errata_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_errata_details() return # allow globbing and searching via arguments errata_list = self.expand_errata(args) add_separator = False for erratum in errata_list: try: details = self.client.errata.getDetails(self.session, erratum) packages = self.client.errata.listPackages(self.session, erratum) systems = self.client.errata.listAffectedSystems(self.session, erratum) cves = self.client.errata.listCves(self.session, erratum) channels = \ self.client.errata.applicableToChannels(self.session, erratum) except xmlrpclib.Fault: logging.warning('%s is not a valid erratum' % erratum) continue if add_separator: print(self.SEPARATOR) add_separator = True print('Name: %s' % erratum) print('Product: %s' % details.get('product')) print('Type: %s' % details.get('type')) print('Issue Date: %s' % details.get('issue_date')) print('') print('Topic') print('-----') print('\n'.join(wrap(details.get('topic')))) print('') print('Description') print('-----------') print('\n'.join(wrap(details.get('description')))) if details.get('notes'): print('') print('Notes') print('-----') print('\n'.join(wrap(details.get('notes')))) print('') print('CVEs') print('----') print('\n'.join(sorted(cves))) print('') print('Solution') print('--------') print('\n'.join(wrap(details.get('solution')))) print('') print('References') print('----------') print('\n'.join(wrap(details.get('references')))) print('') print('Affected Channels') print('-----------------') print('\n'.join(sorted([c.get('label') for c in channels]))) print('') print('Affected Systems') print('----------------') print(str(len(systems))) print('') print('Affected Packages') print('-----------------') print('\n'.join(sorted(build_package_names(packages)))) #################### def help_errata_delete(self): print('errata_delete: Delete an patch') print('usage: errata_delete ERRATA|search:XXX ...') def complete_errata_delete(self, text, line, beg, end): return self.tab_complete_errata(text) def do_errata_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_errata_delete() return # allow globbing and searching via arguments errata = self.expand_errata(args) if not errata: logging.warning('No patches to delete') return print('Erratum Channels') print('------- --------') # tell the user how many channels each erratum affects for erratum in sorted(errata): channels = self.client.errata.applicableToChannels(self.session, erratum) print('%s %s' % (erratum.ljust(20), str(len(channels)).rjust(3))) if not self.user_confirm('Delete these patches [y/N]:'): return for erratum in errata: self.client.errata.delete(self.session, erratum) logging.info('Deleted %i patches' % len(errata)) self.generate_errata_cache(True) #################### def help_errata_publish(self): print('errata_publish: Publish an patch to a channel') print('usage: errata_publish ERRATA|search:XXX <CHANNEL ...>') def complete_errata_publish(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_errata(text) elif len(parts) > 2: return tab_completer(self.do_softwarechannel_list('', True), text) def do_errata_publish(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_errata_publish() return # allow globbing and searching via arguments errata = self.expand_errata(args[0]) channels = args[1:] if not errata: logging.warning('No patches to publish') return print('\n'.join(sorted(errata))) if not self.user_confirm('Publish these patches [y/N]:'): return for erratum in errata: self.client.errata.publish(self.session, erratum, channels) #################### def help_errata_search(self): print('errata_search: List patches that meet the given criteria') print('usage: errata_search CVE|RHSA|RHBA|RHEA|CLA ...') print('') print('Example:') print('> errata_search CVE-2009:1674') print('> errata_search RHSA-2009:1674') def complete_errata_search(self, text, line, beg, end): return tab_completer(self.do_errata_list('', True), text) def do_errata_search(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_errata_search() return add_separator = False for query in args: errata = [] if re.match('CVE', query, re.I): errata = self.client.errata.findByCve(self.session, query.upper()) else: self.generate_errata_cache() for name in self.all_errata.keys(): if re.search(query, name, re.I) or \ re.search(query, self.all_errata[name]['advisory_synopsis'], re.I): match = self.all_errata[name] # build a structure to pass to print_errata_summary() errata.append({'advisory_name': name, 'advisory_type': match['advisory_type'], 'advisory_synopsis': match['advisory_synopsis'], 'date': match['date']}) if add_separator: print(self.SEPARATOR) add_separator = True if errata: if doreturn: return [erratum['advisory_name'] for erratum in errata] else: map(print_errata_summary, sorted(errata, reverse=True)) else: return [] 0707010000001C000081B40000000000000000000000015D65A5B200001070000000000000000000000000000000000000002A00000000spacecmd/src/spacecmd/filepreservation.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from spacecmd.utils import * def help_filepreservation_list(self): print('filepreservation_list: List all file preservations') print('usage: filepreservation_list') def do_filepreservation_list(self, args, doreturn=False): lists = [l.get('name') for l in self.client.kickstart.filepreservation.listAllFilePreservations(self.session)] if not doreturn: print('\n'.join(sorted(lists))) else: return lists #################### def help_filepreservation_create(self): print('filepreservation_create: Create a file preservation list') print('usage: filepreservation_create [NAME] [FILE ...]') def do_filepreservation_create(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if args: name = args[0] else: name = prompt_user('Name:', noblank=True) if len(args) > 1: files = args[1:] else: files = [] while True: print('File List') print('---------') print('\n'.join(sorted(files))) print('') userinput = prompt_user('File [blank to finish]:') if userinput == '': break else: if userinput not in files: files.append(userinput) print('') print('File List') print('---------') print('\n'.join(sorted(files))) if not self.user_confirm(): return self.client.kickstart.filepreservation.create(self.session, name, files) #################### def help_filepreservation_delete(self): print('filepreservation_delete: Delete a file preservation list') print('usage: filepreservation_delete NAME') def complete_filepreservation_delete(self, text, line, beg, end): return tab_completer(self.do_filepreservation_list('', True), text) def do_filepreservation_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_filepreservation_delete() return name = args[0] if not self.user_confirm('Delete this list [y/N]:'): return self.client.kickstart.filepreservation.delete(self.session, name) #################### def help_filepreservation_details(self): print('''filepreservation_details: Show the details of a file 'preservation list''') print('usage: filepreservation_details NAME') def complete_filepreservation_details(self, text, line, beg, end): return tab_completer(self.do_filepreservation_list('', True), text) def do_filepreservation_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_filepreservation_details() return name = args[0] details = \ self.client.kickstart.filepreservation.getDetails(self.session, name) print('\n'.join(sorted(details.get('file_names')))) 0707010000001D000081B40000000000000000000000015D65A5B200003290000000000000000000000000000000000000001F00000000spacecmd/src/spacecmd/group.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import os import re import shlex try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_group_addsystems(self): print('group_addsystems: Add systems to a group') print('usage: group_addsystems GROUP <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_group_addsystems(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_group_list('', True), text) elif len(parts) > 2: return self.tab_complete_systems(parts[-1]) def do_group_addsystems(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_group_addsystems() return group_name = args.pop(0) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) system_ids = [] for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue system_ids.append(system_id) self.client.systemgroup.addOrRemoveSystems(self.session, group_name, system_ids, True) #################### def help_group_removesystems(self): print('group_removesystems: Remove systems from a group') print('usage: group_removesystems GROUP <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_group_removesystems(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_group_list('', True), text) elif len(parts) > 2: return self.tab_complete_systems(parts[-1]) def do_group_removesystems(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_group_removesystems() return group_name = args.pop(0) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) system_ids = [] for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue system_ids.append(system_id) print('Systems') print('-------') print('\n'.join(sorted(systems))) if not self.user_confirm('Remove these systems [y/N]:'): return self.client.systemgroup.addOrRemoveSystems(self.session, group_name, system_ids, False) #################### def help_group_create(self): print('group_create: Create a system group') print('usage: group_create [NAME] [DESCRIPTION]') def do_group_create(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if args: name = args[0] else: name = prompt_user('Name:') if len(args) > 1: description = ' '.join(args[1:]) else: description = prompt_user('Description:') self.client.systemgroup.create(self.session, name, description) #################### def help_group_delete(self): print('group_delete: Delete a system group') print('usage: group_delete NAME ...') def complete_group_delete(self, text, line, beg, end): return tab_completer(self.do_group_list('', True), text) def do_group_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_group_delete() return groups = args self.do_group_details('', True) if not self.user_confirm('Delete these groups [y/N]:'): return for group in groups: self.client.systemgroup.delete(self.session, group) #################### def help_group_backup(self): print('group_backup: backup a system group') print('''usage: group_backup NAME [OUTDIR]) OUTDIR defaults to $HOME/spacecmd-backup/group/YYYY-MM-DD/NAME ''') def complete_group_backup(self, text, line, beg, end): List = self.do_group_list('', True) List.append('ALL') return tab_completer(List, text) def do_group_backup(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_group_backup() return groups = args if len(args) == 1 and args[0] == 'ALL': groups = self.do_group_list('', True) outputpath_base = None # use an output base from the user if it was passed if len(args) == 2: outputpath_base = datetime.now().strftime(os.path.expanduser(args[1])) else: outputpath_base = os.path.expanduser('~/spacecmd-backup/group') # make the final output path be <base>/date outputpath_base = os.path.join(outputpath_base, datetime.now().strftime("%Y-%m-%d")) try: if not os.path.isdir(outputpath_base): os.makedirs(outputpath_base) except OSError: logging.error('Could not create output directory') return for group in groups: print("Backup Group: %s" % group) details = self.client.systemgroup.getDetails(self.session, group) outputpath = outputpath_base + "/" + group print("Output File: %s" % outputpath) fh = open(outputpath, 'w') fh.write(details['description']) fh.close() #################### def help_group_restore(self): print('group_restore: restore a system group') print('usage: group_restore INPUTDIR [NAME] ...') def complete_group_restore(self, text, line, beg, end): parts = shlex.split(line) if len(parts) > 1: groups = self.do_group_list('', True) groups.append('ALL') return tab_completer(groups, text) def do_group_restore(self, args): arg_parser = get_argument_parser() (args, options) = parse_command_arguments(args, arg_parser) inputdir = os.getcwd() groups = [] files = {} current = {} if args: inputdir = args[0] groups = args[1:] else: self.help_group_restore() return inputdir = os.path.abspath(inputdir) logging.debug("Input Directory: %s" % (inputdir)) # make a list of file items in the input dir if os.path.isdir(inputdir): d_content = os.listdir(inputdir) for d_item in d_content: if os.path.isfile(inputdir + "/" + d_item): logging.debug("Found file %s" % inputdir + "/" + d_item) files[d_item] = inputdir + "/" + d_item else: logging.error("Restore dir %s does not exits or is not a directory" % inputdir) return if not files: logging.error("Restore dir %s has no restore items" % inputdir) return if (len(groups) == 1 and groups[0] == 'ALL') or not groups: groups = files.keys() elif groups: for group in groups: if group in files: groups.append(group) else: logging.error("Group %s was not found in backup" % (group)) for groupname in self.do_group_list('', True): details = self.client.systemgroup.getDetails(self.session, groupname) current[groupname] = details['description'] current[groupname] = current[groupname].rstrip('\n') for groupname in files: fh = open(files[groupname], 'r') details = fh.read() fh.close() details = details.rstrip('\n') if groupname in current and current[groupname] == details: logging.debug("Already have %s" % groupname) continue elif groupname in current: logging.debug("Already have %s but the description has changed" % groupname) if is_interactive(options): print("Changing description from:") print("\n\"%s\"\nto\n\"%s\"\n" % (current[groupname], details)) userinput = prompt_user('Continue [y/N]:') if re.match('y', userinput, re.I): logging.info("Updating description for group: %s" % groupname) self.client.systemgroup.update(self.session, groupname, details) else: logging.info("Updating description for group: %s" % groupname) self.client.systemgroup.update(self.session, groupname, details) else: logging.info("Creating new group %s" % groupname) group = self.client.systemgroup.create(self.session, groupname, details) #################### def help_group_list(self): print('group_list: List available system groups') print('usage: group_list') def do_group_list(self, args, doreturn=False): groups = self.client.systemgroup.listAllGroups(self.session) groups = [g.get('name') for g in groups] if doreturn: return groups else: if groups: print('\n'.join(sorted(groups))) #################### def help_group_listsystems(self): print('group_listsystems: List the members of a group') print('usage: group_listsystems GROUP') def complete_group_listsystems(self, text, line, beg, end): return tab_completer(self.do_group_list('', True), text) def do_group_listsystems(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_group_listsystems() return group = args[0] try: systems = self.client.systemgroup.listSystems(self.session, group) systems = [s.get('profile_name') for s in systems] except xmlrpclib.Fault: logging.warning('%s is not a valid group' % group) return [] if doreturn: return systems else: if systems: print('\n'.join(sorted(systems))) #################### def help_group_details(self): print('group_details: Show the details of a system group') print('usage: group_details GROUP ...') def complete_group_details(self, text, line, beg, end): return tab_completer(self.do_group_list('', True), text) def do_group_details(self, args, short=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_group_details() return add_separator = False for group in args: try: details = self.client.systemgroup.getDetails(self.session, group) systems = self.client.systemgroup.listSystems(self.session, group) systems = [s.get('profile_name') for s in systems] except xmlrpclib.Fault: logging.warning('%s is not a valid group' % group) return if add_separator: print(self.SEPARATOR) add_separator = True print('ID: %i' % details.get('id')) print('Name: %s' % details.get('name')) print('Description: %s' % details.get('description')) print('Number of Systems: %i' % details.get('system_count')) if not short: print('') print('Members') print('-------') print('\n'.join(sorted(systems))) 0707010000001E000081B40000000000000000000000015D65A5B20001577B000000000000000000000000000000000000002300000000spacecmd/src/spacecmd/kickstart.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2013--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from getpass import getpass from operator import itemgetter try: from urllib2 import urlopen, HTTPError except ImportError: from urllib.request import urlopen from urllib.error import HTTPError import re try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * KICKSTART_OPTIONS = ['autostep', 'interactive', 'install', 'upgrade', 'text', 'network', 'cdrom', 'harddrive', 'nfs', 'url', 'lang', 'langsupport keyboard', 'mouse', 'device', 'deviceprobe', 'zerombr', 'clearpart', 'bootloader', 'timezone', 'auth', 'rootpw', 'selinux', 'reboot', 'firewall', 'xconfig', 'skipx', 'key', 'ignoredisk', 'autopart', 'cmdline', 'firstboot', 'graphical', 'iscsi', 'iscsiname', 'logging', 'monitor', 'multipath', 'poweroff', 'halt', 'service', 'shutdown', 'user', 'vnc', 'zfcp'] VIRT_TYPES = ['none', 'para_host', 'qemu', 'xenfv', 'xenpv'] UPDATE_TYPES = ['red_hat', 'all', 'none'] def help_kickstart_list(self): print('kickstart_list: List the available Kickstart profiles') print('usage: kickstart_list') def do_kickstart_list(self, args, doreturn=False): kickstarts = self.client.kickstart.listKickstarts(self.session) kickstarts = [k.get('name') for k in kickstarts] if doreturn: return kickstarts else: if kickstarts: print('\n'.join(sorted(kickstarts))) #################### def help_kickstart_create(self): print('kickstart_create: Create a Kickstart profile') print('''usage: kickstart_create [options]) options: -n NAME -d DISTRIBUTION -p ROOT_PASSWORD -v VIRT_TYPE ['none', 'para_host', 'qemu', 'xenfv', 'xenpv']''') def do_kickstart_create(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-d', '--distribution') arg_parser.add_argument('-v', '--virt-type') arg_parser.add_argument('-p', '--root-password') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.name = prompt_user('Name:', noblank=True) print('Virtualization Types') print('--------------------') print('\n'.join(sorted(self.VIRT_TYPES))) print('') options.virt_type = prompt_user('Virtualization Type [none]:') if options.virt_type == '' or options.virt_type not in self.VIRT_TYPES: options.virt_type = 'none' options.distribution = '' while options.distribution == '': trees = self.do_distribution_list('', True) print('') print('Distributions') print('-------------') print('\n'.join(sorted(trees))) print('') options.distribution = prompt_user('Select:') options.root_password = '' while options.root_password == '': print('') password1 = getpass('Root Password: ') password2 = getpass('Repeat Password: ') if password1 == password2: options.root_password = password1 elif password1 == '': logging.warning('Password must be at least 5 characters') else: logging.warning("Passwords don't match") else: if not options.name: logging.error('The Kickstart name is required') return if not options.distribution: logging.error('The distribution is required') return if not options.virt_type: options.virt_type = 'none' if not options.root_password: logging.error('A root password is required') return # leave this blank to use the default server host = '' self.client.kickstart.createProfile(self.session, options.name, options.virt_type, options.distribution, host, options.root_password) #################### def help_kickstart_delete(self): print('kickstart_delete: Delete kickstart profile(s)') print('usage: kickstart_delete PROFILE') print('usage: kickstart_delete PROFILE1 PROFILE2') print('usage: kickstart_delete \"PROF*\"') def complete_kickstart_delete(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 1: self.help_kickstart_delete() return # allow globbing of kickstart labels all_labels = self.do_kickstart_list('', True) labels = filter_results(all_labels, args) logging.debug("Got labels to delete of %s" % labels) if not labels: logging.error("No valid kickstart labels passed as arguments!") self.help_kickstart_delete() return for label in labels: if not label in all_labels: logging.error("kickstart label %s doesn't exist!" % label) continue if self.user_confirm("Delete profile %s [y/N]:" % label): self.client.kickstart.deleteProfile(self.session, label) #################### def help_kickstart_import(self): print('kickstart_import: Import a Kickstart profile from a file') print('''usage: kickstart_import [options]) options: -f FILE -n NAME -d DISTRIBUTION -v VIRT_TYPE ['none', 'para_host', 'qemu', 'xenfv', 'xenpv']''') def do_kickstart_import(self, args): self.kickstart_import_file(raw=False, args=args) def kickstart_import_file(self, raw, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-d', '--distribution') arg_parser.add_argument('-v', '--virt-type') arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.name = prompt_user('Name:', noblank=True) options.file = prompt_user('File:', noblank=True) print('Virtualization Types') print('--------------------') print('\n'.join(sorted(self.VIRT_TYPES))) print('') options.virt_type = prompt_user('Virtualization Type [none]:') if options.virt_type == '' or options.virt_type not in self.VIRT_TYPES: options.virt_type = 'none' options.distribution = '' while options.distribution == '': trees = self.do_distribution_list('', True) print('') print('Distributions') print('-------------') print('\n'.join(sorted(trees))) print('') options.distribution = prompt_user('Select:') else: if not options.name: logging.error('The Kickstart name is required') return if not options.distribution: logging.error('The distribution is required') return if not options.file: logging.error('A filename is required') return if not options.virt_type: options.virt_type = 'none' # read the contents of the Kickstart file options.contents = read_file(options.file) if raw: self.client.kickstart.importRawFile(self.session, options.name, options.virt_type, options.distribution, options.contents) else: # use the default server host = '' self.client.kickstart.importFile(self.session, options.name, options.virt_type, options.distribution, host, options.contents) #################### def help_kickstart_import_raw(self): print('kickstart_import_raw: Import a raw Kickstart or autoyast profile from a file') print('''usage: kickstart_import_raw [options]) options: -f FILE -n NAME -d DISTRIBUTION -v VIRT_TYPE ['none', 'para_host', 'qemu', 'xenfv', 'xenpv']''') def do_kickstart_import_raw(self, args): self.kickstart_import_file(raw=True, args=args) #################### def help_kickstart_details(self): print('kickstart_details: Show the details of a Kickstart profile') print('usage: kickstart_details PROFILE') def complete_kickstart_details(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_kickstart_details() return label = args[0] kickstart = None profiles = self.client.kickstart.listKickstarts(self.session) for p in profiles: if p.get('label') == label: kickstart = p break if not kickstart: logging.warning('Invalid Kickstart profile') return act_keys = \ self.client.kickstart.profile.keys.getActivationKeys(self.session, label) try: variables = self.client.kickstart.profile.getVariables(self.session, label) except xmlrpclib.Fault: variables = {} tree = \ self.client.kickstart.tree.getDetails(self.session, kickstart.get('tree_label')) base_channel = \ self.client.channel.software.getDetails(self.session, tree.get('channel_id')) child_channels = \ self.client.kickstart.profile.getChildChannels(self.session, label) custom_options = \ self.client.kickstart.profile.getCustomOptions(self.session, label) advanced_options = \ self.client.kickstart.profile.getAdvancedOptions(self.session, label) config_manage = \ self.client.kickstart.profile.system.checkConfigManagement( self.session, label) remote_commands = \ self.client.kickstart.profile.system.checkRemoteCommands( self.session, label) partitions = \ self.client.kickstart.profile.system.getPartitioningScheme( self.session, label) crypto_keys = \ self.client.kickstart.profile.system.listKeys(self.session, label) file_preservations = \ self.client.kickstart.profile.system.listFilePreservations( self.session, label) software = self.client.kickstart.profile.software.getSoftwareList( self.session, label) scripts = self.client.kickstart.profile.listScripts(self.session, label) result = [] result.append('Name: %s' % kickstart.get('name')) result.append('Label: %s' % kickstart.get('label')) result.append('Tree: %s' % kickstart.get('tree_label')) result.append('Active: %s' % kickstart.get('active')) result.append('Advanced: %s' % kickstart.get('advanced_mode')) result.append('Org Default: %s' % kickstart.get('org_default')) result.append('') result.append('Configuration Management: %s' % config_manage) result.append('Remote Commands: %s' % remote_commands) result.append('') result.append('Software Channels') result.append('-----------------') result.append(base_channel.get('label')) for channel in sorted(child_channels): result.append(' |-- %s' % channel) if advanced_options: result.append('') result.append('Advanced Options') result.append('----------------') for o in sorted(advanced_options, key=itemgetter('name')): if o.get('arguments'): result.append('%s %s' % (o.get('name'), o.get('arguments'))) if custom_options: result.append('') result.append('Custom Options') result.append('--------------') for o in sorted(custom_options, key=itemgetter('arguments')): result.append(re.sub('\n', '', o.get('arguments'))) if partitions: result.append('') result.append('Partitioning') result.append('------------') result.append('\n'.join(partitions)) result.append('') result.append('Software') result.append('--------') result.append('\n'.join(software)) if act_keys: result.append('') result.append('Activation Keys') result.append('---------------') for k in sorted(act_keys, key=itemgetter('key')): result.append(k.get('key')) if crypto_keys: result.append('') result.append('Crypto Keys') result.append('-----------') for k in sorted(crypto_keys, key=itemgetter('description')): result.append(k.get('description')) if file_preservations: result.append('') result.append('File Preservations') result.append('------------------') for fp in sorted(file_preservations, key=itemgetter('name')): result.append(fp.get('name')) for profile_name in sorted(fp.get('file_names')): result.append(' |-- %s' % profile_name) if variables: result.append('') result.append('Variables') result.append('---------') for k in sorted(variables.keys()): result.append('%s = %s' % (k, str(variables[k]))) if scripts: result.append('') result.append('Scripts') result.append('-------') add_separator = False for s in scripts: if add_separator: result.append(self.SEPARATOR) add_separator = True result.append('Type: %s' % s.get('script_type')) result.append('Chroot: %s' % s.get('chroot')) if s.get('interpreter'): result.append('Interpreter: %s' % s.get('interpreter')) result.append('') result.append(s.get('contents')) return result #################### def kickstart_getcontents(self, profile): kickstart = None if self.check_api_version('10.11'): kickstart = self.client.kickstart.profile.downloadRenderedKickstart( self.session, profile) else: # old versions of th API don't return a rendered Kickstart, # so grab it in a hacky way url = 'http://%s/ks/cfg/label/%s' % (self.server, profile) try: logging.debug('Retrieving %s' % url) response = urlopen(url) kickstart = response.read() except HTTPError: logging.error('Could not retrieve the Kickstart file') return kickstart def help_kickstart_getcontents(self): print('kickstart_getcontents: Show the contents of a Kickstart profile') print(' as they would be presented to a client') print('usage: kickstart_getcontents LABEL') def complete_kickstart_getcontents(self, text, line, beg, end): return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_getcontents(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_getcontents() return profile = args[0] kickstart = self.kickstart_getcontents(profile) if kickstart: # We try to encode the output as UTF8, which is what we expect from # the API. This avoids "'ascii' codec can't encode character" errors try: print(kickstart.encode('UTF8')) except UnicodeDecodeError: print(kickstart) #################### def help_kickstart_rename(self): print('kickstart_rename: Rename a Kickstart profile') print('usage: kickstart_rename OLDNAME NEWNAME') def complete_kickstart_rename(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_rename(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_kickstart_rename() return oldname = args[0] newname = args[1] self.client.kickstart.renameProfile(self.session, oldname, newname) #################### def help_kickstart_listcryptokeys(self): print('kickstart_listcryptokeys: List the crypto keys associated ' + 'with a Kickstart profile') print('usage: kickstart_listcryptokeys PROFILE') def complete_kickstart_listcryptokeys(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listcryptokeys(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listcryptokeys() return profile = args[0] keys = self.client.kickstart.profile.system.listKeys(self.session, profile) keys = [k.get('description') for k in keys] if doreturn: return keys else: if keys: print('\n'.join(sorted(keys))) #################### def help_kickstart_addcryptokeys(self): print('kickstart_addcryptokeys: Add crypto keys to a Kickstart profile') print('usage: kickstart_addcryptokeys PROFILE <KEY ...>') def complete_kickstart_addcryptokeys(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_cryptokey_list('', True), text) def do_kickstart_addcryptokeys(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_addcryptokeys() return profile = args[0] keys = args[1:] self.client.kickstart.profile.system.addKeys(self.session, profile, keys) #################### def help_kickstart_removecryptokeys(self): print('kickstart_removecryptokeys: Remove crypto keys from a ' + 'Kickstart profile') print('usage: kickstart_removecryptokeys PROFILE <KEY ...>') def complete_kickstart_removecryptokeys(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: # only tab complete keys currently assigned to the profile try: keys = self.do_kickstart_listcryptokeys(parts[1], True) except xmlrpclib.Fault: keys = [] return tab_completer(keys, text) def do_kickstart_removecryptokeys(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_removecryptokeys() return profile = args[0] keys = args[1:] self.client.kickstart.profile.system.removeKeys(self.session, profile, keys) #################### def help_kickstart_listactivationkeys(self): print('kickstart_listactivationkeys: List the activation keys ' + 'associated with a Kickstart profile') print('usage: kickstart_listactivationkeys PROFILE') def complete_kickstart_listactivationkeys(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listactivationkeys(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listactivationkeys() return profile = args[0] keys = \ self.client.kickstart.profile.keys.getActivationKeys(self.session, profile) keys = [k.get('key') for k in keys] if doreturn: return keys else: if keys: print('\n'.join(sorted(keys))) #################### def help_kickstart_addactivationkeys(self): print('kickstart_addactivationkeys: Add activation keys to a ' + 'Kickstart profile') print('usage: kickstart_addactivationkeys PROFILE <KEY ...>') def complete_kickstart_addactivationkeys(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_activationkey_list('', True), text) def do_kickstart_addactivationkeys(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_addactivationkeys() return profile = args[0] keys = args[1:] for key in keys: self.client.kickstart.profile.keys.addActivationKey(self.session, profile, key) #################### def help_kickstart_removeactivationkeys(self): print('kickstart_removeactivationkeys: Remove activation keys from ' + 'a Kickstart profile') print('usage: kickstart_removeactivationkeys PROFILE <KEY ...>') def complete_kickstart_removeactivationkeys(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: # only tab complete keys currently assigned to the profile try: keys = self.do_kickstart_listactivationkeys(parts[1], True) except xmlrpclib.Fault: keys = [] return tab_completer(keys, text) def do_kickstart_removeactivationkeys(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_removeactivationkeys() return profile = args[0] keys = args[1:] if not self.user_confirm('Remove these keys [y/N]:'): return for key in keys: self.client.kickstart.profile.keys.removeActivationKey(self.session, profile, key) #################### def help_kickstart_enableconfigmanagement(self): print('kickstart_enableconfigmanagement: Enable configuration ' + 'management on a Kickstart profile') print('usage: kickstart_enableconfigmanagement PROFILE') def complete_kickstart_enableconfigmanagement(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_enableconfigmanagement(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_enableconfigmanagement() return profile = args[0] self.client.kickstart.profile.system.enableConfigManagement( self.session, profile) #################### def help_kickstart_disableconfigmanagement(self): print('kickstart_disableconfigmanagement: Disable configuration ' + 'management on a Kickstart profile') print('usage: kickstart_disableconfigmanagement PROFILE') def complete_kickstart_disableconfigmanagement(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_disableconfigmanagement(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_disableconfigmanagement() return profile = args[0] self.client.kickstart.profile.system.disableConfigManagement( self.session, profile) #################### def help_kickstart_enableremotecommands(self): print('kickstart_enableremotecommands: Enable remote commands ' + 'on a Kickstart profile') print('usage: kickstart_enableremotecommands PROFILE') def complete_kickstart_enableremotecommands(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_enableremotecommands(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_enableremotecommands() return profile = args[0] self.client.kickstart.profile.system.enableRemoteCommands(self.session, profile) #################### def help_kickstart_disableremotecommands(self): print('kickstart_disableremotecommands: Disable remote commands ' + 'on a Kickstart profile') print('usage: kickstart_disableremotecommands PROFILE') def complete_kickstart_disableremotecommands(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_disableremotecommands(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_disableremotecommands() return profile = args[0] self.client.kickstart.profile.system.disableRemoteCommands(self.session, profile) #################### def help_kickstart_setlocale(self): print('kickstart_setlocale: Set the locale for a Kickstart profile') print('usage: kickstart_setlocale PROFILE LOCALE') def complete_kickstart_setlocale(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) == 3: return tab_completer(list_locales(), text) def do_kickstart_setlocale(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_kickstart_setlocale() return profile = args[0] locale = args[1] # always use UTC utc = True self.client.kickstart.profile.system.setLocale(self.session, profile, locale, utc) #################### def help_kickstart_setselinux(self): print('kickstart_setselinux: Set the SELinux mode for a Kickstart ' + 'profile') print('usage: kickstart_setselinux PROFILE MODE') def complete_kickstart_setselinux(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) == 3: modes = ['enforcing', 'permissive', 'disabled'] return tab_completer(modes, text) def do_kickstart_setselinux(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_kickstart_setselinux() return profile = args[0] mode = args[1] self.client.kickstart.profile.system.setSELinux(self.session, profile, mode) #################### def help_kickstart_setpartitions(self): print('kickstart_setpartitions: Set the partitioning scheme for a ' + 'Kickstart profile') print('usage: kickstart_setpartitions PROFILE') def complete_kickstart_setpartitions(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_setpartitions(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_setpartitions() return profile = args[0] try: # get the current scheme so the user can edit it current = \ self.client.kickstart.profile.system.getPartitioningScheme( self.session, profile) template = '\n'.join(current) except xmlrpclib.Fault: template = '' (partitions, _ignore) = editor(template=template, delete=True) print(partitions) if not self.user_confirm(): return lines = partitions.split('\n') self.client.kickstart.profile.system.setPartitioningScheme(self.session, profile, lines) #################### def help_kickstart_setdistribution(self): print('kickstart_setdistribution: Set the distribution for a ' + 'Kickstart profile') print('usage: kickstart_setdistribution PROFILE DISTRIBUTION') def complete_kickstart_setdistribution(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) == 3: return tab_completer(self.do_distribution_list('', True), text) def do_kickstart_setdistribution(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_kickstart_setdistribution() return profile = args[0] distribution = args[1] self.client.kickstart.profile.setKickstartTree(self.session, profile, distribution) #################### def help_kickstart_enablelogging(self): print('kickstart_enablelogging: Enable logging for a Kickstart profile') print('usage: kickstart_enablelogging PROFILE') def complete_kickstart_enablelogging(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_enablelogging(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_enablelogging() return profile = args[0] self.client.kickstart.profile.setLogging(self.session, profile, True, True) #################### def help_kickstart_addvariable(self): print('kickstart_addvariable: Add a variable to a Kickstart profile') print('usage: kickstart_addvariable PROFILE KEY VALUE') def complete_kickstart_addvariable(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_addvariable(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 3: self.help_kickstart_addvariable() return profile = args[0] key = args[1] value = ' '.join(args[2:]) variables = self.client.kickstart.profile.getVariables(self.session, profile) variables[key] = value self.client.kickstart.profile.setVariables(self.session, profile, variables) #################### def help_kickstart_updatevariable(self): print('kickstart_updatevariable: Update a variable in a Kickstart ' + 'profile') print('usage: kickstart_updatevariable PROFILE KEY VALUE') def complete_kickstart_updatevariable(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: variables = {} try: variables = \ self.client.kickstart.profile.getVariables(self.session, parts[1]) except xmlrpclib.Fault: pass return tab_completer(variables.keys(), text) def do_kickstart_updatevariable(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 3: self.help_kickstart_updatevariable() return return self.do_kickstart_addvariable(' '.join(args)) #################### def help_kickstart_removevariables(self): print('kickstart_removevariables: Remove variables from a ' + 'Kickstart profile') print('usage: kickstart_removevariables PROFILE <KEY ...>') def complete_kickstart_removevariables(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: variables = {} try: variables = \ self.client.kickstart.profile.getVariables(self.session, parts[1]) except xmlrpclib.Fault: pass return tab_completer(variables.keys(), text) def do_kickstart_removevariables(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_removevariables() return profile = args[0] keys = args[1:] variables = self.client.kickstart.profile.getVariables(self.session, profile) for key in keys: if key in variables: del variables[key] self.client.kickstart.profile.setVariables(self.session, profile, variables) #################### def help_kickstart_listvariables(self): print('kickstart_listvariables: List the variables of a Kickstart ' + 'profile') print('usage: kickstart_listvariables PROFILE') def complete_kickstart_listvariables(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listvariables(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listvariables() return profile = args[0] variables = self.client.kickstart.profile.getVariables(self.session, profile) for v in variables: print('%s = %s' % (v, variables[v])) #################### def help_kickstart_addoption(self): print('kickstart_addoption: Set an option for a Kickstart profile') print('usage: kickstart_addoption PROFILE KEY [VALUE]') def complete_kickstart_addoption(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) == 3: return tab_completer(sorted(self.KICKSTART_OPTIONS), text) def do_kickstart_addoption(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_addoption() return profile = args[0] key = args[1] if len(args) > 2: value = ' '.join(args[2:]) else: value = '' # only pre-defined options can be set as 'advanced options' if key in self.KICKSTART_OPTIONS: advanced = \ self.client.kickstart.profile.getAdvancedOptions(self.session, profile) # remove any instances of this key from the current list for item in advanced: if item.get('name') == key: advanced.remove(item) break advanced.append({'name': key, 'arguments': value}) self.client.kickstart.profile.setAdvancedOptions(self.session, profile, advanced) else: logging.warning('%s needs to be set as a custom option' % key) return #################### def help_kickstart_removeoptions(self): print('kickstart_removeoptions: Remove options from a Kickstart profile') print('usage: kickstart_removeoptions PROFILE <OPTION ...>') def complete_kickstart_removeoptions(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: try: options = self.client.kickstart.profile.getAdvancedOptions( self.session, parts[1]) options = [o.get('name') for o in options] except xmlrpclib.Fault: options = self.KICKSTART_OPTIONS return tab_completer(sorted(options), text) def do_kickstart_removeoptions(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_removeoptions() return profile = args[0] keys = args[1:] advanced = \ self.client.kickstart.profile.getAdvancedOptions(self.session, profile) # remove any instances of this key from the current list for key in keys: for item in advanced: if item.get('name') == key: advanced.remove(item) self.client.kickstart.profile.setAdvancedOptions(self.session, profile, advanced) #################### def help_kickstart_listoptions(self): print('kickstart_listoptions: List the options of a Kickstart ' + 'profile') print('usage: kickstart_listoptions PROFILE') def complete_kickstart_listoptions(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listoptions(self, args): arg_parser = get_argument_parser() (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listoptions() return profile = args[0] options = self.client.kickstart.profile.getAdvancedOptions(self.session, profile) for o in sorted(options, key=itemgetter('name')): if o.get('arguments'): print('%s %s' % (o.get('name'), o.get('arguments'))) #################### def help_kickstart_listcustomoptions(self): print('kickstart_listcustomoptions: List the custom options of a ' + 'Kickstart profile') print('usage: kickstart_listcustomoptions PROFILE') def complete_kickstart_listcustomoptions(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listcustomoptions(self, args): arg_parser = get_argument_parser() (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listcustomoptions() return profile = args[0] options = self.client.kickstart.profile.getCustomOptions(self.session, profile) for o in options: if 'arguments' in o: print(o.get('arguments')) #################### def help_kickstart_setcustomoptions(self): print('kickstart_setcustomoptions: Set custom options for a ' + 'Kickstart profile') print('usage: kickstart_setcustomoptions PROFILE') def complete_kickstart_setcustomoptions(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_setcustomoptions(self, args): arg_parser = get_argument_parser() (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_setcustomoptions() return profile = args[0] options = self.client.kickstart.profile.getCustomOptions(self.session, profile) # the first item in the list is missing the 'arguments' key old_options = [] for o in options: if 'arguments' in o: old_options.append(o.get('arguments')) old_options = '\n'.join(old_options) # let the user edit the custom options (new_options, _ignore) = editor(template=old_options, delete=True) new_options = new_options.split('\n') self.client.kickstart.profile.setCustomOptions(self.session, profile, new_options) #################### def help_kickstart_addchildchannels(self): print('kickstart_addchildchannels: Add a child channels to a ' + 'Kickstart profile') print('usage: kickstart_addchildchannels PROFILE <CHANNEL ...>') def complete_kickstart_addchildchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: profile = parts[1] try: tree = \ self.client.kickstart.profile.getKickstartTree(self.session, profile) tree_details = self.client.kickstart.tree.getDetails( self.session, tree) base_channel = \ self.client.channel.software.getDetails(self.session, tree_details.get('channel_id')) parent_channel = base_channel.get('label') except xmlrpclib.Fault: return [] return tab_completer(self.list_child_channels( parent=parent_channel), text) def do_kickstart_addchildchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_addchildchannels() return profile = args[0] new_channels = args[1:] channels = self.client.kickstart.profile.getChildChannels(self.session, profile) channels.extend(new_channels) self.client.kickstart.profile.setChildChannels(self.session, profile, channels) #################### def help_kickstart_removechildchannels(self): print('kickstart_removechildchannels: Remove child channels from ' + 'a Kickstart profile') print('usage: kickstart_removechildchannels PROFILE <CHANNEL ...>') def complete_kickstart_removechildchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_kickstart_listchildchannels( parts[1], True), text) def do_kickstart_removechildchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_removechildchannels() return profile = args[0] to_remove = args[1:] channels = self.client.kickstart.profile.getChildChannels(self.session, profile) for channel in to_remove: if channel in channels: channels.remove(channel) self.client.kickstart.profile.setChildChannels(self.session, profile, channels) #################### def help_kickstart_listchildchannels(self): print('kickstart_listchildchannels: List the child channels of a ' + 'Kickstart profile') print('usage: kickstart_listchildchannels PROFILE') def complete_kickstart_listchildchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listchildchannels(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listchildchannels() return profile = args[0] channels = self.client.kickstart.profile.getChildChannels(self.session, profile) if doreturn: return channels else: if channels: print('\n'.join(sorted(channels))) #################### def help_kickstart_addfilepreservations(self): print('kickstart_addfilepreservations: Add file preservations to a ' + 'Kickstart profile') print('usage: kickstart_addfilepreservations PROFILE <FILELIST ...>') def complete_kickstart_addfilepreservations(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) == 3: return tab_completer(self.do_filepreservation_list('', True), text) def do_kickstart_addfilepreservations(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_addfilepreservations() return profile = args[0] files = args[1:] self.client.kickstart.profile.system.addFilePreservations(self.session, profile, files) #################### def help_kickstart_removefilepreservations(self): print('kickstart_removefilepreservations: Remove file ' + 'preservations from a Kickstart profile') print('usage: kickstart_removefilepreservations PROFILE <FILE ...>') def complete_kickstart_removefilepreservations(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: files = [] try: # only tab complete files currently assigned to the profile files = \ self.client.kickstart.profile.system.listFilePreservations( self.session, parts[1]) files = [f.get('name') for f in files] except xmlrpclib.Fault: return [] return tab_completer(files, text) def do_kickstart_removefilepreservations(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_removefilepreservations() return profile = args[0] files = args[1:] self.client.kickstart.profile.system.removeFilePreservations( self.session, profile, files) #################### def help_kickstart_listpackages(self): print('kickstart_listpackages: List the packages for a Kickstart ' + 'profile') print('usage: kickstart_listpackages PROFILE') def complete_kickstart_listpackages(self, text, line, beg, end): return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listpackages(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listpackages() return profile = args[0] packages = \ self.client.kickstart.profile.software.getSoftwareList(self.session, profile) if doreturn: return packages else: if packages: print('\n'.join(packages)) #################### def help_kickstart_addpackages(self): print('kickstart_addpackages: Add packages to a Kickstart profile') print('usage: kickstart_addpackages PROFILE <PACKAGE ...>') def complete_kickstart_addpackages(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: return tab_completer(self.get_package_names(), text) def do_kickstart_addpackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) >= 2: self.help_kickstart_addpackages() return profile = args[0] packages = args[1:] self.client.kickstart.profile.software.appendToSoftwareList( self.session, profile, packages) #################### def help_kickstart_removepackages(self): print('kickstart_removepackages: Remove packages from a Kickstart ' + 'profile') print('usage: kickstart_removepackages PROFILE <PACKAGE ...>') def complete_kickstart_removepackages(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_kickstart_listpackages( parts[1], True), text) def do_kickstart_removepackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_kickstart_removepackages() return profile = args[0] to_remove = args[1:] # setSoftwareList requires a new list of packages, so grab # the old list and remove the list of packages from the user packages = self.do_kickstart_listpackages(profile, True) for package in to_remove: if package in packages: packages.remove(package) self.client.kickstart.profile.software.setSoftwareList(self.session, profile, packages) #################### def help_kickstart_listscripts(self): print('kickstart_listscripts: List the scripts for a Kickstart ' + 'profile') print('usage: kickstart_listscripts PROFILE') def complete_kickstart_listscripts(self, text, line, beg, end): return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_listscripts(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_listscripts() return profile = args[0] scripts = \ self.client.kickstart.profile.listScripts(self.session, profile) add_separator = False for script in scripts: if add_separator: print(self.SEPARATOR) add_separator = True print('ID: %i' % script.get('id')) print('Type: %s' % script.get('script_type')) print('Chroot: %s' % script.get('chroot')) print('Interpreter: %s' % script.get('interpreter')) print('') print('Contents') print('--------') print(script.get('contents')) #################### def help_kickstart_addscript(self): print('kickstart_addscript: Add a script to a Kickstart profile') print('''usage: kickstart_addscript PROFILE [options]) options: -p PROFILE -e EXECUTION_TIME ['pre', 'post'] -i INTERPRETER -f FILE -c execute in a chroot environment -t ENABLING_TEMPLATING''') def complete_kickstart_addscript(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_addscript(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-p', '--profile') arg_parser.add_argument('-e', '--execution-time') arg_parser.add_argument('-c', '--chroot', action='store_true') arg_parser.add_argument('-t', '--template', action='store_true') arg_parser.add_argument('-i', '--interpreter') arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): if args: options.profile = args[0] else: options.profile = prompt_user('Profile Name:', noblank=True) options.execution_time = prompt_user('Pre/Post Script [post]:') options.chroot = prompt_user('Chrooted [Y/n]:') options.interpreter = prompt_user('Interpreter [/bin/bash]:') # get the contents of the script if self.user_confirm('Read an existing file [y/N]:', nospacer=True, ignore_yes=True): options.file = prompt_user('File:') else: (options.contents, _ignore) = editor(delete=True) # check user input if options.interpreter == '': options.interpreter = '/bin/bash' if re.match('n', options.chroot, re.I): options.chroot = False else: options.chroot = True if re.match('n', options.template, re.I): options.template = False else: options.template = True if re.match('pre', options.execution_time, re.I): options.execution_time = 'pre' else: options.execution_time = 'post' else: if not options.profile: logging.error('The Kickstart name is required') return if not options.file: logging.error('A filename is required') return if not options.execution_time: logging.error('The execution time is required') return if not options.chroot: options.chroot = False if not options.interpreter: options.interpreter = '/bin/bash' if not options.template: options.template = False if options.file: options.contents = read_file(options.file) print('') print('Profile Name: %s' % options.profile) print('Execution Time: %s' % options.execution_time) print('Chroot: %s' % options.chroot) print('Template: %s' % options.template) print('Interpreter: %s' % options.interpreter) print('Contents:') print(options.contents) if not self.user_confirm(): return self.client.kickstart.profile.addScript(self.session, options.profile, options.contents, options.interpreter, options.execution_time, options.chroot, options.template) #################### def help_kickstart_removescript(self): print('kickstart_removescript: Remove a script from a Kickstart profile') print('usage: kickstart_removescript PROFILE [ID]') def complete_kickstart_removescript(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_removescript(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_kickstart_removescript() return profile = args[0] script_id = 0 # allow a script ID to be passed in if len(args) == 2: try: script_id = int(args[1]) except ValueError: logging.error('Invalid script ID') # print(the scripts for the user to review) self.do_kickstart_listscripts(profile) if not script_id: while script_id == 0: print('') userinput = prompt_user('Script ID:', noblank=True) try: script_id = int(userinput) except ValueError: logging.error('Invalid script ID') if not self.user_confirm('Remove this script [y/N]:'): return self.client.kickstart.profile.removeScript(self.session, profile, script_id) #################### def help_kickstart_clone(self): print('kickstart_clone: Clone a Kickstart profile') print('''usage: kickstart_clone [options]) options: -n NAME -c CLONE_NAME''') def complete_kickstart_clone(self, text, line, beg, end): return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_clone(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-c', '--clonename') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): profiles = self.do_kickstart_list('', True) print('') print('Kickstart Profiles') print('------------------') print('\n'.join(sorted(profiles))) print('') options.name = prompt_user('Original Profile:', noblank=True) options.clonename = prompt_user('Cloned Profile:', noblank=True) else: if not options.name: logging.error('The Kickstart name is required') return if not options.clonename: logging.error('The Kickstart clone name is required') return self.client.kickstart.cloneProfile(self.session, options.name, options.clonename) #################### def help_kickstart_export(self): print('kickstart_export: export kickstart profile(s) to json format file') print('''usage: kickstart_export <KSPROFILE>... [options]) options: -f outfile.json : specify an output filename, defaults to <KSPROFILE>.json if exporting a single kickstart, profiles.json for multiple kickstarts, or ks_all.json if no KSPROFILE specified e.g (export ALL) Note : KSPROFILE list is optional, default is to export ALL''') def complete_kickstart_export(self, text, line, beg, end): return tab_completer(self.do_kickstart_list('', True), text) def export_kickstart_getdetails(self, profile, kickstarts): # Get the initial ks details struct from the kickstarts list-of-struct, # which is returned from kickstart.listKickstarts() logging.debug("Getting kickstart profile details for %s" % profile) details = None for k in kickstarts: if k.get('label') == profile: details = k break logging.debug("Got basic details for %s : %s" % (profile, details)) # Now use the various other API functions to build up a more complete # details struct for export. Note there are a some ommisions from the API # e.g the "template" option which enables cobbler templating on scripts details['child_channels'] = \ self.client.kickstart.profile.getChildChannels(self.session, profile) details['advanced_opts'] = \ self.client.kickstart.profile.getAdvancedOptions(self.session, profile) details['software_list'] = \ self.client.kickstart.profile.software.getSoftwareList(self.session, profile) details['custom_opts'] = \ self.client.kickstart.profile.getCustomOptions(self.session, profile) details['script_list'] = \ self.client.kickstart.profile.listScripts(self.session, profile) details['ip_ranges'] = \ self.client.kickstart.profile.listIpRanges(self.session, profile) logging.debug("About to get variable_list for %s" % profile) details['variable_list'] = \ self.client.kickstart.profile.getVariables(self.session, profile) logging.debug("done variable_list for %s = %s" % (profile, details['variable_list'])) # just export the key names, then look for one with the same name on import details['activation_keys'] = [k['key'] for k in self.client.kickstart.profile.keys.getActivationKeys(self.session, profile)] details['partitioning_scheme'] = \ self.client.kickstart.profile.system.getPartitioningScheme( self.session, profile) if self.check_api_version('10.11'): details['reg_type'] = \ self.client.kickstart.profile.system.getRegistrationType(self.session, profile) else: details['reg_type'] = "none" details['config_mgmt'] = \ self.client.kickstart.profile.system.checkConfigManagement( self.session, profile) details['remote_cmds'] = \ self.client.kickstart.profile.system.checkRemoteCommands( self.session, profile) # Just export the file preservation list names, then look for one with the # same name on import details['file_preservations'] = [ f['name'] for f in self.client.kickstart.profile.system.listFilePreservations( self.session, profile)] # just export the key description/names , then look for one with the same # name on import details['gpg_ssl_keys'] = [k['description'] for k in self.client.kickstart.profile.system.listKeys(self.session, profile)] # There's a setLogging() but no getLogging(), so we look in the rendered # kickstart to figure out if pre/post logging is enabled kscontents = self.kickstart_getcontents(profile) if re.search("pre --logfile", kscontents): logging.debug("Detected pre script logging") details['pre_logging'] = True else: details['pre_logging'] = False if re.search("post --logfile", kscontents): logging.debug("Detected post script logging") details['post_logging'] = True else: details['post_logging'] = False # There's also no way to get the "Kernel Options" and "Post Kernel Options" # The Post options can be derived from the grubby --default-kernel` --args # line in the kickstart, ugly but at least we can then show some of what's # missing in the warnings that get printed on import if re.search("`/sbin/grubby --default-kernel` --args=", kscontents): post_kopts = \ kscontents.split("`/sbin/grubby --default-kernel` --args=")[1].\ split("\"")[1] logging.debug("Post kernel options %s detected" % post_kopts) details['post_kopts'] = post_kopts # and now sort all the lists for i in details.keys(): if isinstance(details[i], list): details[i].sort() return details def do_kickstart_export(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) filename = "" if options.file != None: logging.debug("Passed filename do_kickstart_export %s" % options.file) filename = options.file # Get the list of profiles to export and sort out the filename if required profiles = [] if not args: if not filename: filename = "ks_all.json" logging.info("Exporting ALL kickstart profiles to %s" % filename) profiles = self.do_kickstart_list('', True) else: # allow globbing of kickstart kickstart names profiles = filter_results(self.do_kickstart_list('', True), args) logging.debug("kickstart_export called with args %s, profiles=%s" % (args, profiles)) if not profiles: logging.error("Error, no valid kickstart profile passed, " + "check name is correct with spacecmd kickstart_list") return if not filename: # No filename arg, so we try to do something sensible: # If we are exporting exactly one ks, we default to ksname.json # otherwise, generic ks_profiles.json name if len(profiles) == 1: filename = "%s.json" % profiles[0] else: filename = "ks_profiles.json" # First grab the list of basic details about all kickstarts because you # can't get details-per-label, call here to avoid potential duplicate calls # in export_kickstart_getdetails for multi-profile exports kickstarts = self.client.kickstart.listKickstarts(self.session) # Dump as a list of dict ksdetails_list = [] for p in profiles: logging.info("Exporting ks %s to %s" % (p, filename)) ksdetails_list.append(self.export_kickstart_getdetails(p, kickstarts)) logging.debug("About to dump %d ks profiles to %s" % (len(ksdetails_list), filename)) # Check if filepath exists, if an existing file we prompt for confirmation if os.path.isfile(filename): if not self.user_confirm("File %s exists, " % filename + "confirm overwrite file? (y/n)"): return if json_dump_to_file(ksdetails_list, filename) != True: logging.error("Error saving exported kickstart profiles to file" % filename) return #################### def help_kickstart_importjson(self): print('kickstart_import: import kickstart profile(s) from json file') print('''usage: kickstart_import <JSONFILES...>''') def do_kickstart_importjson(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: logging.error("Error, no filename passed") self.help_kickstart_import() return for filename in args: logging.debug("Passed filename do_kickstart_import %s" % filename) ksdetails_list = json_read_from_file(filename) if not ksdetails_list: logging.error("Error, could not read json data from %s" % filename) return for ksdetails in ksdetails_list: if self.import_kickstart_fromdetails(ksdetails) != True: logging.error("Error importing kickstart %s" % ksdetails['name']) # create a new ks based on the dict from export_kickstart_getdetails def import_kickstart_fromdetails(self, ksdetails): # First we check that an existing kickstart with the same name does not exist existing_profiles = self.do_kickstart_list('', True) if ksdetails['name'] in existing_profiles: logging.error("ERROR : kickstart profile %s already exists! Skipping!" % ksdetails['name']) return False # create the ks, we need to drop the org prefix from the ks name logging.info("Found ks %s" % ksdetails['name']) # Create the kickstart # for adding new profiles, we require a root password. # This is overridden when we set the 'advanced options' tmppw = 'foobar' virt_type = 'none' # assume none as there's no API call to read this info ks_host = '' self.client.kickstart.createProfile(self.session, ksdetails['label'], virt_type, ksdetails['tree_label'], ks_host, tmppw) # Now set other options self.client.kickstart.profile.setChildChannels( self.session, ksdetails['label'], ksdetails['child_channels']) self.client.kickstart.profile.setAdvancedOptions( self.session, ksdetails['label'], ksdetails['advanced_opts']) self.client.kickstart.profile.system.setPartitioningScheme( self.session, ksdetails['label'], ksdetails['partitioning_scheme']) self.client.kickstart.profile.software.setSoftwareList( self.session, ksdetails['label'], ksdetails['software_list']) self.client.kickstart.profile.setCustomOptions( self.session, ksdetails['label'], [o and o.get('arguments') or "\n" for o in ksdetails['custom_opts']]) self.client.kickstart.profile.setVariables( self.session, ksdetails['label'], ksdetails['variable_list']) self.client.kickstart.profile.system.setRegistrationType( self.session, ksdetails['label'], ksdetails['reg_type']) if ksdetails['config_mgmt']: self.client.kickstart.profile.system.enableConfigManagement( self.session, ksdetails['label']) if ksdetails['remote_cmds']: self.client.kickstart.profile.system.enableRemoteCommands( self.session, ksdetails['label']) # Add the scripts for script in ksdetails['script_list']: # Somewhere between spacewalk-java-1.2.39-85 and 1.2.39-108, # two new versions of listScripts and addScripts were added, which # allows us to correctly set the "template" checkbox on import # However, we can't detect this capability via API version since # the API version number is the same (10.11) # So, we look for the template key in the script dict and use the "new" # API call if we find it. This will obviously break if migrating # kickstarts from a server with the new API call to one without it, # so ensure the target satellite is at least as up-to-date as the # satellite where the export was performed. if 'template' in script: ret = self.client.kickstart.profile.addScript(self.session, ksdetails['label'], script['name'], script['contents'], script['interpreter'], script[ 'script_type'], script['chroot'], script['template']) else: ret = self.client.kickstart.profile.addScript( self.session, ksdetails['label'], script['name'], script['contents'], script['interpreter'], script['script_type'], script['chroot']) if ret: logging.debug("Added %s script to profile" % script['script_type']) else: logging.error("Error adding %s script" % script['script_type']) # Specify ip ranges for iprange in ksdetails['ip_ranges']: if self.client.kickstart.profile.addIpRange(self.session, ksdetails['label'], iprange['min'], iprange['max']): logging.debug("added ip range %s-%s" % iprange['min'], iprange['max']) else: logging.warning("failed to add ip range %s-%s, continuing" % iprange['min'], iprange['max']) continue # File preservations, only if the list exists existing_file_preservations = [ x['name'] for x in self.client.kickstart.filepreservation.listAllFilePreservations( self.session)] if ksdetails['file_preservations']: for fp in ksdetails['file_preservations']: if fp in existing_file_preservations: if self.client.kickstart.profile.system.addFilePreservations( self.session, ksdetails['label'], [fp]): logging.debug("added file preservation '%s'" % fp) else: logging.warning("failed to add file preservation %s, skipping" % fp) else: logging.warning("file preservation list %s doesn't exist, skipping" % fp) # Now add activationkeys, only if they exist existing_act_keys = [k['key'] for k in self.client.activationkey.listActivationKeys(self.session)] for akey in ksdetails['activation_keys']: if akey in existing_act_keys: logging.debug("Adding activation key %s to profile" % akey) self.client.kickstart.profile.keys.addActivationKey(self.session, ksdetails['label'], akey) else: logging.warning("Actvationkey %s does not exist on the " % akey + "satellite, skipping") # The GPG/SSL keys, only if they exist existing_gpg_ssl_keys = [x['description'] for x in self.client.kickstart.keys.listAllKeys(self.session)] for key in ksdetails['gpg_ssl_keys']: if key in existing_gpg_ssl_keys: logging.debug("Adding GPG/SSL key %s to profile" % key) self.client.kickstart.profile.system.addKeys(self.session, ksdetails['label'], [key]) else: logging.warning("GPG/SSL key %s does not exist on the " % key + "satellite, skipping") # The pre/post logging settings self.client.kickstart.profile.setLogging(self.session, ksdetails['label'], ksdetails['pre_logging'], ksdetails['post_logging']) # There are some frustrating ommisions from the API which means we can't # export/import some settings, so we post a warning that some manual # fixup may be required logging.warning("Due to API ommissions, there are some settings which" + " cannot be imported, please check and fixup manually if necessary") logging.warning(" * Details->Preserve ks.cfg") logging.warning(" * Details->Comment") # Org default gets exported but no way to set it, so we can just show this # warning if they are trying to import an org_default profile if ksdetails['org_default']: logging.warning(" * Details->Organization Default Profile") # No way to set the kernel options logging.warning(" * Details->Kernel Options") # We can export Post kernel options (sort of, see above) # if they exist on import, flag a warning if 'post_kopts' in ksdetails: logging.warning(" * Details->Post Kernel Options : %s" % ksdetails['post_kopts']) return True #################### # kickstart helper def is_kickstart(self, name): if not name: return return name in self.do_kickstart_list(name, True) def check_kickstart(self, name): if not name: logging.error("no kickstart label given") return False if not self.is_kickstart(name): logging.error("invalid kickstart label " + name) return False return True def dump_kickstart(self, name, replacedict=None, excludes=None): excludes = excludes or ["Org Default:"] content = self.do_kickstart_details(name) content = get_normalized_text(content, replacedict=replacedict, excludes=excludes) return content #################### def help_kickstart_diff(self): print('kickstart_diff: diff kickstart files') print('') print('usage: kickstart_diff SOURCE_CHANNEL TARGET_CHANNEL') def complete_kickstart_diff(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_kickstart_list('', True), text) if args == 3: return tab_completer(self.do_kickstart_list('', True), text) return [] def do_kickstart_diff(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1 and len(args) != 2: self.help_kickstart_diff() return source_channel = args[0] if not self.check_kickstart(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_kickstart_getcorresponding"): # can a corresponding channel name be found automatically? target_channel = self.do_kickstart_getcorresponding(source_channel) if not self.check_kickstart(target_channel): return source_replacedict, target_replacedict = get_string_diff_dicts(source_channel, target_channel) source_data = self.dump_kickstart(source_channel, source_replacedict) target_data = self.dump_kickstart(target_channel, target_replacedict) return diff(source_data, target_data, source_channel, target_channel) #################### def help_kickstart_getupdatetype(self): print('kickstart_getupdatetype: Get the update type for a kickstart profile(s)') print('usage: kickstart_getupdatetype PROFILE') print('usage: kickstart_getupdatetype PROFILE1 PROFILE2') print('usage: kickstart_getupdatetype \"PROF*\"') def complete_kickstart_getupdatetype(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_getupdatetype(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 1: self.help_kickstart_getupdatetype() return # allow globbing of kickstart labels all_labels = self.do_kickstart_list('', True) labels = filter_results(all_labels, args) logging.debug("Got labels to list the update type %s" % labels) if not labels: logging.error("No valid kickstart labels passed as arguments!") self.help_kickstart_getupdatetype() return for label in labels: if not label in all_labels: logging.error("kickstart label %s doesn't exist!" % label) continue updatetype = self.client.kickstart.profile.getUpdateType(self.session, label) if len(labels) == 1: print(updatetype) elif len(labels) > 1: print(label, ":", updatetype) #################### def help_kickstart_setupdatetype(self): print('kickstart_setupdatetype: Set the update type for a kickstart profile(s)') print('''usage: kickstart_setupdatetype [options] KS_LABEL) options: -u UPDATE_TYPE ['red_hat', 'all', 'none']''') def do_kickstart_setupdatetype(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-u', '--update-type') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): print('Update Types') print('--------------------') print('\n'.join(sorted(self.UPDATE_TYPES))) print('') options.update_type = prompt_user('Update Type [none]:') if options.update_type == '' or options.update_type not in self.UPDATE_TYPES: options.update_type = 'none' else: if not options.update_type: options.update_type = 'none' # allow globbing of kickstart labels all_labels = self.do_kickstart_list('', True) labels = filter_results(all_labels, args) logging.debug("Got labels to set the update type %s" % labels) if not labels: logging.error("No valid kickstart labels passed as arguments!") self.help_kickstart_setupdatetype() return for label in labels: if not label in all_labels: logging.error("kickstart label %s doesn't exist!" % label) continue self.client.kickstart.profile.setUpdateType(self.session, label, options.update_type) #################### def help_kickstart_getsoftwaredetails(self): print('kickstart_getsoftwaredetails: Gets kickstart profile software details') print('usage: kickstart_getsoftwaredetails KS_LABEL') print('usage: kickstart_getsoftwaredetails KS_LABEL KS_LABEL2 ...') def complete_kickstart_getsoftwaredetails(self, text, line, beg, end): if len(line.split(' ')) >= 2: return tab_completer(self.do_kickstart_list('', True), text) def do_kickstart_getsoftwaredetails(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 1: self.help_kickstart_getsoftwaredetails() return # allow globbing of kickstart labels all_labels = self.do_kickstart_list('', True) labels = filter_results(all_labels, args) logging.debug("Got labels to set the update type %s" % labels) if not labels: logging.error("No valid kickstart labels passed as arguments!") self.help_kickstart_getsoftwaredetails() return for label in labels: if not label in all_labels: logging.error("kickstart label %s doesn't exist!" % label) continue software_details = self.client.kickstart.profile.software.getSoftwareDetails(self.session, label) if len(labels) == 1: print("noBase: %s" % software_details.get("noBase")) print("ignoreMissing: %s" % software_details.get("ignoreMissing")) elif len(labels) > 1: print("Kickstart Label: %s" % label) print("noBase: %s" % software_details.get("noBase")) print("ignoreMissing: %s" % software_details.get("ignoreMissing")) print('') #################### def help_kickstart_setsoftwaredetails(self): print('kickstart_setsoftwaredetails: Sets kickstart profile software details.') print('usage: kickstart_setsoftwaredetails PROFILE KICKSTART_PACKAGES_INFO VALUE') print('usage: kickstart_setsoftwaredetails PROFILE KICKSTART_PACKAGES_INFO VALUE KICKSTART_PACKAGES_INFO VALUE') def complete_kickstart_setsoftwaredetails(self, text, line, beg, end): parts = line.split(' ') length = len(parts) if length == 2: return tab_completer(self.do_kickstart_list('', True), text) if length in [3, 5]: if 'noBase' in parts: return tab_completer(['ignoreMissing'], text) if 'ignoreMissing' in parts: return tab_completer(['noBase'], text) kspkginfo = ['noBase', 'ignoreMissing'] return tab_completer(kspkginfo, text) if length in [4, 6]: mode= ['True', 'False'] return tab_completer(mode, text) def do_kickstart_setsoftwaredetails(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) length = len(args) kspkginfo = ['noBase', 'ignoreMissing'] mode = ['True', 'False'] if length < 1 or length not in [3, 5]: self.help_kickstart_setsoftwaredetails() return if args[0] not in self.do_kickstart_list('', True): print("Selected profile does not exist") return if args[1] not in kspkginfo or args[2] not in mode: print("Enter valid input") self.help_kickstart_setsoftwaredetails() return if length==5: if (args[3] not in kspkginfo or args[4] not in mode) or args[1] == args[3]: print("Enter valid input") self.help_kickstart_setsoftwaredetails() return args[2] = string_to_bool(args[2]) if length == 5: args[4] = string_to_bool(args[4]) profile = args[0] if length == 3: software_details = self.client.kickstart.profile.software.getSoftwareDetails(self.session, profile) if args[1] == 'noBase': kspkginfo= {'noBase': args[2], 'ignoreMissing': string_to_bool(software_details.get("ignoreMissing"))} elif args[1] == 'ignoreMissing': kspkginfo= {'noBase': string_to_bool(software_details.get("noBase")), 'ignoreMissing':args[2]} else: if args[3] == 'noBase': kspkginfo= {'noBase': args[4], 'ignoreMissing': args[2]} elif args[3] == 'ignoreMissing': kspkginfo= {'noBase': args[2], 'ignoreMissing':args[4]} self.client.kickstart.profile.software.setSoftwareDetails(self.session, profile, kspkginfo) 0707010000001F000081B40000000000000000000000015D65A5B2000070C0000000000000000000000000000000000000001E00000000spacecmd/src/spacecmd/misc.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2011--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import logging import readline import shlex from getpass import getpass try: # python 3 from configparser import NoOptionError except ImportError: # python 2 from ConfigParser import NoOptionError from time import sleep try: # python 3 from xmlrpc import client as xmlrpclib except ImportError: # python2 import xmlrpclib from spacecmd.utils import * # list of system selection options for the help output HELP_SYSTEM_OPTS = '''<SYSTEMS> can be any of the following: name ssm (see 'help ssm') search:QUERY (see 'help system_search') group:GROUP channel:CHANNEL ''' HELP_TIME_OPTS = '''Dates can be any of the following: Explicit Dates: Dates can be expressed as explicit date strings in the YYYYMMDD[HHMMSS] format. The year, month and day are required, while the hours and minutes are not; the hours and minutes will default to 0000 if no values are provided. Deltas: Dates can be expressed as delta values. For example, '2h' would mean 2 hours in the future. You can also use negative values to express times in the past (e.g., -7d would be one week ago). Units: s -> seconds m -> minutes h -> hours d -> days ''' #################### # life of caches in seconds SYSTEM_CACHE_TTL = 3600 PACKAGE_CACHE_TTL = 86400 ERRATA_CACHE_TTL = 86400 MINIMUM_API_VERSION = 10.8 SEPARATOR = '\n' + '#' * 30 + '\n' #################### ENTITLEMENTS = ['enterprise_entitled', 'virtualization_host' ] SYSTEM_SEARCH_FIELDS = ['id', 'name', 'ip', 'hostname', 'device', 'vendor', 'driver', 'uuid'] CONTACT_METHODS = ['default', 'ssh-push', 'ssh-push-tunnel'] #################### def help_systems(self): print(HELP_SYSTEM_OPTS) def help_time(self): print(HELP_TIME_OPTS) #################### def help_clear(self): print('clear: clear the screen') print('usage: clear') def do_clear(self, args): os.system('clear') #################### def help_clear_caches(self): print('clear_caches: Clear the internal caches kept for systems and packages') print('usage: clear_caches') def do_clear_caches(self, args): self.clear_system_cache() self.clear_package_cache() self.clear_errata_cache() #################### def help_get_apiversion(self): print('get_apiversion: Display the API version of the server') print('usage: get_apiversion') def do_get_apiversion(self, args): print(self.client.api.getVersion()) #################### def help_get_serverversion(self): print('get_serverversion: Display the version of the server') print('usage: get_serverversion') def do_get_serverversion(self, args): print(self.client.api.systemVersion()) #################### def help_list_proxies(self): print('list_proxies: List the proxies within the user\'s organization ') print('usage: list_proxies') def do_list_proxies(self, args): proxies = self.client.satellite.listProxies(self.session) print(proxies) #################### def help_get_session(self): print('get_session: Show the current session string') print('usage: get_session') def do_get_session(self, args): if self.session: print(self.session) else: logging.error('No session found') #################### def help_help(self): print('help: Show help for the given command') print('usage: help COMMAND') #################### def help_history(self): print('history: List your command history') print('usage: history') def do_history(self, args): for i in range(1, readline.get_current_history_length()): print('%s %s' % (str(i).rjust(4), readline.get_history_item(i))) #################### def help_toggle_confirmations(self): print('toggle_confirmations: Toggle confirmation messages on/off') print('usage: toggle_confirmations') def do_toggle_confirmations(self, args): if self.options.yes: self.options.yes = False print('Confirmation messages are enabled') else: self.options.yes = True logging.warning('Confirmation messages are DISABLED!') #################### def help_login(self): print('login: Connect to a Spacewalk server') print('usage: login [USERNAME] [SERVER]') def do_login(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) # logout before logging in again if self.session: logging.warning('You are already logged in') return True # an argument passed to the function get precedence if len(args) == 2: server = args[1] else: # use the server we were already using server = self.config['server'] # bail out if not server was given if not server: logging.warning('No server specified') return False # load the server-specific configuration self.load_config_section(server) # an argument passed to the function get precedence if args: username = args[0] elif 'username' in self.config: # use the username from before username = self.config['username'] elif self.options.username: # use the username from before username = self.options.username else: username = '' # set the protocol if 'nossl' in self.config and self.config['nossl']: proto = 'http' else: proto = 'https' server_url = '%s://%s/rpc/api' % (proto, server) # this will enable spewing out all client/server traffic verbose_xmlrpc = False if self.options.debug > 1: verbose_xmlrpc = True # connect to the server logging.debug('Connecting to %s', server_url) self.client = xmlrpclib.Server(server_url, verbose=verbose_xmlrpc) # check the API to verify connectivity # pylint: disable=W0702 try: self.api_version = self.client.api.getVersion() logging.debug('Server API Version = %s', self.api_version) except: if self.options.debug > 0: e = sys.exc_info()[0] logging.exception(e) logging.error('Failed to connect to %s', server_url) self.client = None return False # ensure the server is recent enough if float(self.api_version) < self.MINIMUM_API_VERSION: logging.error('API (%s) is too old (>= %s required)', self.api_version, self.MINIMUM_API_VERSION) self.client = None return False # store the session file in the server's own directory session_file = os.path.join(self.conf_dir, server, 'session') # retrieve a cached session if os.path.isfile(session_file) and not self.options.password: try: sessionfile = open(session_file, 'r') # read the session (format = username:session) for line in sessionfile: parts = line.split(':') # if a username was passed, make sure it matches if username: if parts[0] == username: self.session = parts[1] else: # get the username from the cache if one # wasn't passed by the user username = parts[0] self.session = parts[1] sessionfile.close() except IOError: logging.error('Could not read %s', session_file) # check the cached credentials by doing an API call if self.session: try: logging.debug('Using cached credentials from %s', session_file) self.client.user.listAssignableRoles(self.session) except xmlrpclib.Fault: logging.warning('Cached credentials are invalid') self.current_user = '' self.session = '' # attempt to login if we don't have a valid session yet if not self.session: if username: logging.info('Spacewalk Username: %s', username) else: username = prompt_user('Spacewalk Username:', noblank=True) if self.options.password: password = self.options.password # remove this from the options so that if 'login' is called # again, the user is prompted for the information self.options.password = None elif 'password' in self.config: password = self.config['password'] else: password = getpass('Spacewalk Password: ') # login to the server try: self.session = self.client.auth.login(username, password) # don't keep the password around password = None except xmlrpclib.Fault: logging.error('Invalid credentials') return False try: # make sure ~/.spacecmd/<server> exists conf_dir = os.path.join(self.conf_dir, server) if not os.path.isdir(conf_dir): os.mkdir(conf_dir, int('0700', 8)) # add the new cache to the file line = '%s:%s\n' % (username, self.session) # write the new cache file out sessionfile = open(session_file, 'w') sessionfile.write(line) sessionfile.close() except IOError: logging.error('Could not write session file') # load the system/package/errata caches self.load_caches(server, username) # keep track of who we are and who we're connected to self.current_user = username self.server = server logging.info('Connected to %s as %s', server_url, username) return True #################### def help_logout(self): print('logout: Disconnect from the server') print('usage: logout') def do_logout(self, args): if self.session: self.client.auth.logout(self.session) self.session = '' self.current_user = '' self.server = '' self.do_clear_caches('') #################### def help_whoami(self): print('whoami: Print the name of the currently logged in user') print('usage: whoami') def do_whoami(self, args): if self.current_user: print(self.current_user) else: logging.warning("You are not logged in") #################### def help_whoamitalkingto(self): print('whoamitalkingto: Print the name of the server') print('usage: whoamitalkingto') def do_whoamitalkingto(self, args): if self.server: print(self.server) else: logging.warning('Yourself') #################### def tab_complete_errata(self, text): options = self.do_errata_list('', True) options.append('search:') return tab_completer(options, text) def tab_complete_systems(self, text): if re.match('group:', text): # prepend 'group' to each item for tab completion groups = ['group:%s' % g for g in self.do_group_list('', True)] return tab_completer(groups, text) if re.match('channel:', text): # prepend 'channel' to each item for tab completion channels = ['channel:%s' % s for s in self.do_softwarechannel_list('', True)] return tab_completer(channels, text) if re.match('search:', text): # prepend 'search' to each item for tab completion fields = ['search:%s:' % f for f in self.SYSTEM_SEARCH_FIELDS] return tab_completer(fields, text) options = self.get_system_names() # add our special search options options.extend(['group:', 'channel:', 'search:']) return tab_completer(options, text) def remove_last_history_item(self): last = readline.get_current_history_length() - 1 if last >= 0: readline.remove_history_item(last) def clear_errata_cache(self): self.all_errata = {} self.errata_cache_expire = datetime.now() self.save_errata_cache() def get_errata_names(self): return sorted([e.get('advisory_name') for e in self.all_errata]) def get_erratum_id(self, name): if name in self.all_errata: return self.all_errata[name]['id'] def get_erratum_name(self, erratum_id): for erratum in self.all_errata: if self.all_errata[erratum]['id'] == erratum_id: return erratum def generate_errata_cache(self, force=False): if not force and datetime.now() < self.errata_cache_expire: return if not self.options.quiet: # tell the user what's going on self.replace_line_buffer('** Generating errata cache **') channels = self.client.channel.listSoftwareChannels(self.session) channels = [c.get('label') for c in channels] for c in channels: try: errata = \ self.client.channel.software.listErrata(self.session, c) except xmlrpclib.Fault: logging.debug('No access to %s', c) continue for erratum in errata: if erratum.get('advisory_name') not in self.all_errata: self.all_errata[erratum.get('advisory_name')] = \ {'id': erratum.get('id'), 'advisory_name': erratum.get('advisory_name'), 'advisory_type': erratum.get('advisory_type'), 'date': erratum.get('date'), 'advisory_synopsis': erratum.get('advisory_synopsis')} self.errata_cache_expire = \ datetime.now() + timedelta(self.ERRATA_CACHE_TTL) self.save_errata_cache() if not self.options.quiet: # restore the original line buffer self.replace_line_buffer() def save_errata_cache(self): save_cache(self.errata_cache_file, self.all_errata, self.errata_cache_expire) def clear_package_cache(self): self.all_packages_short = {} self.all_packages = {} self.all_packages_by_id = {} self.package_cache_expire = datetime.now() self.save_package_caches() def generate_package_cache(self, force=False): if not force and datetime.now() < self.package_cache_expire: return if not self.options.quiet: # tell the user what's going on self.replace_line_buffer('** Generating package cache **') channels = self.client.channel.listSoftwareChannels(self.session) channels = [c.get('label') for c in channels] for c in channels: try: packages = \ self.client.channel.software.listAllPackages(self.session, c) except xmlrpclib.Fault: logging.debug('No access to %s', c) continue for p in packages: if not p.get('name') in self.all_packages_short: self.all_packages_short[p.get('name')] = '' longname = build_package_names(p) if not longname in self.all_packages: self.all_packages[longname] = [p.get('id')] else: self.all_packages[longname].append(p.get('id')) # keep a reverse dictionary so we can lookup package names by ID # We assume that package IDs are unique, so one ID is only # refering one package. self.all_packages_by_id = {} for k, v in self.all_packages.items(): for i in v: # Alert in case of non-unique ID is detected. if i in self.all_packages_by_id: logging.debug( 'Non-unique package id "%s" is detected. Taking "%s" ' 'instead of "%s"' % (i, k, self.all_packages_by_id[i])) self.all_packages_by_id[i] = k self.package_cache_expire = \ datetime.now() + timedelta(seconds=self.PACKAGE_CACHE_TTL) self.save_package_caches() if not self.options.quiet: # restore the original line buffer self.replace_line_buffer() def save_package_caches(self): # store the cache to disk to speed things up save_cache(self.packages_short_cache_file, self.all_packages_short, self.package_cache_expire) save_cache(self.packages_long_cache_file, self.all_packages, self.package_cache_expire) save_cache(self.packages_by_id_cache_file, self.all_packages_by_id, self.package_cache_expire) # create a global list of all available package names def get_package_names(self, longnames=False): self.generate_package_cache() if longnames: return self.all_packages.keys() return self.all_packages_short def get_package_id(self, name): self.generate_package_cache() try: return set(self.all_packages[name]) except TypeError: # FIX: If we're using an old style cache (package_name -> integer_id) # then we insert the integer id into a set. return set([self.all_packages[name]]) except KeyError: return def get_package_name(self, package_id): self.generate_package_cache() try: return self.all_packages_by_id[package_id] except KeyError: return def clear_system_cache(self): self.all_systems = {} self.system_cache_expire = datetime.now() self.save_system_cache() def generate_system_cache(self, force=False, delay=0): if not force and datetime.now() < self.system_cache_expire: return if not self.options.quiet: # tell the user what's going on self.replace_line_buffer('** Generating system cache **') # we might need to wait for some systems to delete if delay: sleep(delay) systems = self.client.system.listSystems(self.session) self.all_systems = {} for s in systems: self.all_systems[s.get('id')] = s.get('name') self.system_cache_expire = \ datetime.now() + timedelta(seconds=self.SYSTEM_CACHE_TTL) self.save_system_cache() if not self.options.quiet: # restore the original line buffer self.replace_line_buffer() def save_system_cache(self): save_cache(self.system_cache_file, self.all_systems, self.system_cache_expire) def load_caches(self, server, username): conf_dir = os.path.join(self.conf_dir, server, username) try: if not os.path.isdir(conf_dir): os.mkdir(conf_dir, int('0700', 8)) except OSError: logging.error('Could not create directory %s', conf_dir) return self.ssm_cache_file = os.path.join(conf_dir, 'ssm') self.system_cache_file = os.path.join(conf_dir, 'systems') self.errata_cache_file = os.path.join(conf_dir, 'errata') self.packages_long_cache_file = os.path.join(conf_dir, 'packages_long') self.packages_by_id_cache_file = \ os.path.join(conf_dir, 'packages_by_id') self.packages_short_cache_file = \ os.path.join(conf_dir, 'packages_short') # load self.ssm from disk (self.ssm, _ignore) = load_cache(self.ssm_cache_file) # update the prompt now that we loaded the SSM self.postcmd(False, '') # load self.all_systems from disk (self.all_systems, self.system_cache_expire) = \ load_cache(self.system_cache_file) # load self.all_errata from disk (self.all_errata, self.errata_cache_expire) = \ load_cache(self.errata_cache_file) # load self.all_packages_short from disk (self.all_packages_short, self.package_cache_expire) = \ load_cache(self.packages_short_cache_file) # load self.all_packages from disk (self.all_packages, self.package_cache_expire) = \ load_cache(self.packages_long_cache_file) # load self.all_packages_by_id from disk (self.all_packages_by_id, self.package_cache_expire) = \ load_cache(self.packages_by_id_cache_file) def get_system_names(self): self.generate_system_cache() return self.all_systems.values() def get_system_names_ids(self): self.generate_system_cache() return self.all_systems # check for duplicate system names and return the system ID def get_system_id(self, name): name = "%s" % name self.generate_system_cache() systems = [] try: # check if we were passed a system instead of a name system_id = int(name) if system_id in self.all_systems: systems.append(system_id) except ValueError: pass # get a set of matching systems to check for duplicate names if not systems: for system_id in self.all_systems: if name == self.all_systems[system_id]: systems.append(system_id) if len(systems) == 1: return systems[0] elif not systems: logging.warning("Can't find system ID for %s", name) return 0 else: if len(systems) == 2 and systems[0] == systems[1]: return systems[0] logging.warning('Duplicate system profile names found!') logging.warning("Please reference systems by ID or resolve the") logging.warning("underlying issue with 'system_delete' or 'system_rename'") id_list = '%s = ' % name for system_id in systems: id_list = id_list + '%i, ' % system_id logging.warning('') logging.warning(id_list[:-2]) return 0 def get_system_name(self, system_id): self.generate_system_cache() try: return self.all_systems[system_id] except KeyError: return def get_org_id(self, name): details = self.client.org.getDetails(self.session, name) return details.get('id') def expand_errata(self, args): if not isinstance(args, list): args = args.split() self.generate_errata_cache() if not args: return self.all_errata errata = [] for item in args: if re.match('search:', item): item = re.sub('search:', '', item) errata.extend(self.do_errata_search(item, True)) else: errata.append(item) matches = filter_results(self.all_errata, errata) return matches def expand_systems(self, args): if not isinstance(args, list): args = shlex.split(args) systems = [] system_ids = [] for item in args: if re.match('ssm', item, re.I): systems.extend(self.ssm) elif re.match('group:', item): item = re.sub('group:', '', item) members = self.do_group_listsystems("'%s'" % item, True) if members: systems.extend([re.escape(m) for m in members]) else: logging.warning('No systems in group %s', item) elif re.match('search:', item): query = item.split(':', 1)[1] results = self.do_system_search(query, True) if results: system_ids.extend(results) elif re.match('channel:', item): item = re.sub('channel:', '', item) members = self.do_softwarechannel_listsystems(item, True) if members: systems.extend([re.escape(m) for m in members]) else: logging.warning('No systems subscribed to %s', item) else: # translate system IDs that the user passes try: sys_id = int(item) system_ids.append(sys_id) except ValueError: # just a system name systems.append(item) matches = filter_results(self.get_system_names(), systems) return ["%s" % x for x in list(set(matches + system_ids))] def list_base_channels(self): all_channels = self.client.channel.listSoftwareChannels(self.session) base_channels = [] for c in all_channels: if not c.get('parent_label'): base_channels.append(c.get('label')) return base_channels def list_child_channels(self, system=None, parent=None, subscribed=False): channels = [] if system: system_id = self.get_system_id(system) if not system_id: return if subscribed: channels = \ self.client.system.listSubscribedChildChannels(self.session, system_id) else: channels = self.client.system.listSubscribableChildChannels( self.session, system_id) elif parent: all_channels = \ self.client.channel.listSoftwareChannels(self.session) for c in all_channels: if parent == c.get('parent_label'): channels.append(c) else: # get all channels that have a parent all_channels = \ self.client.channel.listSoftwareChannels(self.session) for c in all_channels: if c.get('parent_label'): channels.append(c) return [c.get('label') for c in channels] def user_confirm(self, prompt='Is this ok [y/N]:', nospacer=False, integer=False, ignore_yes=False): if self.options.yes and not ignore_yes: return True if nospacer: answer = prompt_user('%s' % prompt) else: answer = prompt_user('\n%s' % prompt) if re.match('y', answer, re.I): if integer: return 1 return True if integer: return 0 return False # check if the available API is recent enough def check_api_version(self, want): want_parts = [int(i) for i in want.split('.')] have_parts = [int(i) for i in self.api_version.split('.')] if len(have_parts) == 2 and len(want_parts) == 2: if have_parts[0] == want_parts[0]: # compare minor versions if majors are the same return have_parts[1] >= want_parts[1] # only compare major versions if they differ return have_parts[0] >= want_parts[0] # compare the whole value return float(self.api_version) >= float(want) # replace the current line buffer def replace_line_buffer(self, msg=None): # restore the old buffer if we weren't given a new line if not msg: msg = readline.get_line_buffer() # don't print(a prompt if there wasn't one to begin with) if readline.get_line_buffer(): new_line = '%s%s' % (self.prompt, msg) else: new_line = '%s' % msg # clear the current line self.stdout.write('\r'.ljust(len(self.current_line) + 1)) self.stdout.flush() # write the new line self.stdout.write('\r%s' % new_line) self.stdout.flush() # keep track of what is displayed so we can clear it later self.current_line = new_line def load_config_section(self, section): config_opts = ['server', 'username', 'password', 'nossl'] if not self.config_parser.has_section(section): logging.debug('Configuration section [%s] does not exist', section) return logging.debug('Loading configuration section [%s]', section) for key in config_opts: # don't override command-line options if self.options.__dict__[key]: # set the config value to the command-line argument self.config[key] = self.options.__dict__[key] else: try: self.config[key] = self.config_parser.get(section, key) except NoOptionError: pass try: if ('username' in self.config and self.config['username'] != self.config_parser.get(section, 'username')): del self.config['password'] except NoOptionError: pass # handle the nossl boolean if 'nossl' in self.config and isinstance(self.config['nossl'], str): self.config['nossl'] = re.match('^1|y|true$', self.config['nossl'], re.I) # Obfuscate the password with asterisks config_debug = self.config.copy() if 'password' in config_debug: config_debug['password'] = "*" * len(config_debug['password']) logging.debug('Current Configuration: %s', config_debug) 07070100000020000081B40000000000000000000000015D65A5B200002F41000000000000000000000000000000000000001D00000000spacecmd/src/spacecmd/org.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import logging from getpass import getpass from operator import itemgetter from spacecmd.utils import * _PREFIXES = ['Dr.', 'Mr.', 'Miss', 'Mrs.', 'Ms.'] def help_org_create(self): print('org_create: Create an organization') print('''usage: org_create [options]) options: -n ORG_NAME -u USERNAME -P PREFIX (%s) -f FIRST_NAME -l LAST_NAME -e EMAIL -p PASSWORD --pam enable PAM authentication''' % ', '.join(_PREFIXES)) def do_org_create(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--org-name') arg_parser.add_argument('-u', '--username') arg_parser.add_argument('-P', '--prefix') arg_parser.add_argument('-f', '--first-name') arg_parser.add_argument('-l', '--last-name') arg_parser.add_argument('-e', '--email') arg_parser.add_argument('-p', '--password') arg_parser.add_argument('--pam', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.org_name = prompt_user('Organization Name:', noblank=True) options.username = prompt_user('Username:', noblank=True) options.prefix = prompt_user('Prefix (%s):' % ', '.join(_PREFIXES), noblank=True) options.first_name = prompt_user('First Name:', noblank=True) options.last_name = prompt_user('Last Name:', noblank=True) options.email = prompt_user('Email:', noblank=True) options.pam = self.user_confirm('PAM Authentication [y/N]:', nospacer=True, integer=False, ignore_yes=True) options.password = '' while options.password == '': password1 = getpass('Password: ') password2 = getpass('Repeat Password: ') if password1 == password2: options.password = password1 elif password1 == '': logging.warning('Password must be at least 5 characters') else: logging.warning("Passwords don't match") else: if not options.org_name: logging.error('An organization name is required') return if not options.username: logging.error('A username is required') return if not options.first_name: logging.error('A first name is required') return if not options.last_name: logging.error('A last name is required') return if not options.email: logging.error('An email address is required') return if not options.password: logging.error('A password is required') return if not options.pam: options.pam = False if not options.prefix: options.prefix = 'Dr.' if options.prefix[-1] != '.' and options.prefix != 'Miss': options.prefix = options.prefix + '.' self.client.org.create(self.session, options.org_name, options.username, options.password, options.prefix.capitalize(), options.first_name, options.last_name, options.email, options.pam) #################### def help_org_delete(self): print('org_delete: Delete an organization') print('usage: org_delete NAME') def complete_org_delete(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_org_delete() return name = args[0] org_id = self.get_org_id(name) if self.user_confirm('Delete this organization [y/N]:'): self.client.org.delete(self.session, org_id) #################### def help_org_rename(self): print('org_rename: Rename an organization') print('usage: org_rename OLDNAME NEWNAME') def complete_org_rename(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_rename(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_org_rename() return org_id = self.get_org_id(args[0]) new_name = args[1] self.client.org.updateName(self.session, org_id, new_name) #################### def help_org_addtrust(self): print('org_addtrust: Add a trust between two organizations') print('usage: org_addtrust YOUR_ORG ORG_TO_TRUST') def complete_org_addtrust(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_addtrust(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_org_addtrust() return your_org_id = self.get_org_id(args[0]) org_to_trust_id = self.get_org_id(args[1]) self.client.org.trusts.addTrust(self.session, your_org_id, org_to_trust_id) #################### def help_org_removetrust(self): print('org_removetrust: Remove a trust between two organizations') print('usage: org_removetrust YOUR_ORG TRUSTED_ORG') def complete_org_removetrust(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_removetrust(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_org_removetrust() return your_org_id = self.get_org_id(args[0]) trusted_org_id = self.get_org_id(args[1]) systems = self.client.org.trusts.listSystemsAffected(self.session, your_org_id, trusted_org_id) print('Affected Systems') print('----------------') if systems: print('\n'.join(sorted([s.get('systemName') for s in systems]))) else: print('None') if not self.user_confirm('Remove this trust [y/N]:'): return self.client.org.trusts.removeTrust(self.session, your_org_id, trusted_org_id) #################### def help_org_trustdetails(self): print('org_trustdetails: Show the details of an organizational trust') print('usage: org_trustdetails TRUSTED_ORG') def complete_org_trustdetails(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_trustdetails(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_org_trustdetails() return trusted_org = args[0] org_id = self.get_org_id(trusted_org) details = self.client.org.trusts.getDetails(self.session, org_id) consumed = self.client.org.trusts.listChannelsConsumed(self.session, org_id) provided = self.client.org.trusts.listChannelsProvided(self.session, org_id) print('Trusted Organization: %s' % trusted_org) print('Trusted Since: %s' % details.get('trusted_since')) print('Systems Migrated From: %i' % details.get('systems_migrated_from')) print('Systems Migrated To: %i' % details.get('systems_migrated_to')) print('') print('Channels Consumed') print('-----------------') if consumed: print('\n'.join(sorted([c.get('name') for c in consumed]))) print('') print('Channels Provided') print('-----------------') if provided: print('\n'.join(sorted([c.get('name') for c in provided]))) #################### def help_org_list(self): print('org_list: List all organizations') print('usage: org_list') def do_org_list(self, args, doreturn=False): orgs = self.client.org.listOrgs(self.session) orgs = [o.get('name') for o in orgs] if doreturn: return orgs else: if orgs: print('\n'.join(sorted(orgs))) #################### def help_org_listtrusts(self): print("org_listtrusts: List an organization's trusts") print('usage: org_listtrusts NAME') def complete_org_listtrusts(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_listtrusts(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_org_listtrusts() return org_id = self.get_org_id(args[0]) trusts = self.client.org.trusts.listTrusts(self.session, org_id) for trust in sorted(trusts, key=itemgetter('orgName')): if trust.get('trustEnabled'): print(trust.get('orgName')) #################### def help_org_listusers(self): print("org_listusers: List an organization's users") print('usage: org_listusers NAME') def complete_org_listusers(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_listusers(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_org_listusers() return org_id = self.get_org_id(args[0]) users = self.client.org.listUsers(self.session, org_id) print('\n'.join(sorted([u.get('login') for u in users]))) #################### def help_org_details(self): print('org_details: Show the details of an organization') print('usage: org_details NAME') def complete_org_details(self, text, line, beg, end): return tab_completer(self.do_org_list('', True), text) def do_org_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_org_details() return name = args[0] details = self.client.org.getDetails(self.session, name) print('Name: %s' % details.get('name')) print('Active Users: %i' % details.get('active_users')) print('Systems: %i' % details.get('systems')) # trusts is optional, which is annoying... if 'trusts' in details: print('Trusts: %i' % details.get('trusts')) else: print('Trusts: %i' % 0) print('System Groups: %i' % details.get('system_groups')) print('Activation Keys: %i' % details.get('activation_keys')) print('Kickstart Profiles: %i' % details.get('kickstart_profiles')) print('Configuration Channels: %i' % details.get('configuration_channels')) (args, _options) = parse_command_arguments(args, arg_parser) 07070100000021000081B40000000000000000000000015D65A5B200002C73000000000000000000000000000000000000002100000000spacecmd/src/spacecmd/package.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_package_details(self): print('package_details: Show the details of a software package') print('usage: package_details PACKAGE ...') def complete_package_details(self, text, line, beg, end): return tab_completer(self.get_package_names(True), text) def do_package_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_package_details() return packages = [] for package in args: packages.extend(self.do_package_search(' '.join(args), True)) if not packages: logging.warning('No packages found') return add_separator = False for package in packages: if add_separator: print(self.SEPARATOR) add_separator = True package_ids = self.get_package_id(package) if not package_ids: logging.warning('%s is not a valid package' % package) continue for package_id in package_ids: details = self.client.packages.getDetails(self.session, package_id) channels = \ self.client.packages.listProvidingChannels(self.session, package_id) installed_systems = \ self.client.system.listSystemsWithPackage(self.session, package_id) print('Name: %s' % details.get('name')) print('Version: %s' % details.get('version')) print('Release: %s' % details.get('release')) print('Epoch: %s' % details.get('epoch')) print('Arch: %s' % details.get('arch_label')) print('') print('File: %s' % details.get('file')) print('Path: %s' % details.get('path')) print('Size: %s' % details.get('size')) print('%s: %s' % (details.get('checksum_type').upper(), details.get('checksum'))) print('') print('Installed Systems: %i' % len(installed_systems)) print('') print('Description') print('-----------') print('\n'.join(wrap(details.get('description')))) print('') print('Available From Channels') print('-----------------------') print('\n'.join(sorted([c.get('label') for c in channels]))) print('') #################### def help_package_search(self): print('package_search: Find packages that meet the given criteria') print('usage: package_search NAME|QUERY') print('') print('Example: package_search kernel') print('') print('Advanced Search:') print('Available Fields: name, epoch, version, release, arch, description, summary') print('Example: name:kernel AND version:2.6.18 AND -description:devel') def do_package_search(self, args, doreturn=False): if not args: self.help_package_search() return fields = ('name:', 'epoch:', 'version:', 'release:', 'arch:', 'description:', 'summary:') packages = [] advanced = False for f in fields: if args.find(f) != -1: logging.debug('Using advanced search') advanced = True break if advanced: packages = self.client.packages.search.advanced(self.session, args) packages = build_package_names(packages) else: # for non-advanced searches, use local regex instead of # the APIs for searching; this is done because the fuzzy # search on the server gives a lot of garbage back packages = filter_results(self.get_package_names(True), [args], search=True) if doreturn: return packages else: if packages: print('\n'.join(sorted(packages))) #################### def help_package_remove(self): print('package_remove: Remove a package from Satellite') print('usage: package_remove PACKAGE ...') def complete_package_remove(self, text, line, beg, end): return tab_completer(self.get_package_names(True), text) def do_package_remove(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_package_remove() return packages = args to_remove = filter_results(self.get_package_names(True), packages) if not to_remove: return print('Packages') print('--------') print('\n'.join(sorted(to_remove))) if not self.user_confirm('Remove these packages [y/N]:'): return for package in to_remove: for package_id in self.get_package_id(package): try: self.client.packages.removePackage(self.session, package_id) except xmlrpclib.Fault: logging.error('Failed to remove package ID %i' % package_id) # regenerate the package cache after removing these packages self.generate_package_cache(True) #################### def help_package_listorphans(self): print('package_listorphans: List packages that are not in a channel') print('usage: package_listorphans') def do_package_listorphans(self, args, doreturn=False): packages = self.client.channel.software.listPackagesWithoutChannel( self.session) packages = build_package_names(packages) if doreturn: return packages else: if packages: print('\n'.join(sorted(packages))) #################### def help_package_removeorphans(self): print('package_removeorphans: Remove packages that are not in a channel') print('usage: package_removeorphans') def do_package_removeorphans(self, args): packages = \ self.client.channel.software.listPackagesWithoutChannel(self.session) if not packages: logging.warning('No orphaned packages') return print('Packages') print('--------') print('\n'.join(sorted(build_package_names(packages)))) if not self.user_confirm('Remove these packages [y/N]:'): return for package in packages: try: self.client.packages.removePackage(self.session, package.get('id')) except xmlrpclib.Fault: logging.error('Failed to remove package ID %i' % package.get('id')) #################### def help_package_listinstalledsystems(self): print('package_listinstalledsystems: List the systems with a package installed') print('usage: package_listinstalledsystems PACKAGE ...') def complete_package_listinstalledsystems(self, text, line, beg, end): return tab_completer(self.get_package_names(True), text) def do_package_listinstalledsystems(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_package_listinstalledsystems() return packages = [] for package in args: packages.extend(self.do_package_search(package, True)) if not packages: logging.warning('No packages found') return add_separator = False for package in packages: if add_separator: print(self.SEPARATOR) add_separator = True systems = [] for package_id in self.get_package_id(package): systems += self.client.system.listSystemsWithPackage(self.session, package_id) print(package) print('-' * len(package)) if systems: print('\n'.join(sorted(['%s : %s' % (s.get('name'), s.get('id')) for s in systems]))) #################### def help_package_listerrata(self): print('package_listerrata: List the errata that provide this package') print('usage: package_listerrata PACKAGE ...') def complete_package_listerrata(self, text, line, beg, end): return tab_completer(self.get_package_names(True), text) def do_package_listerrata(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_package_listerrata() return packages = [] for package in args: packages.extend(self.do_package_search(' '.join(args), True)) if not packages: logging.warning('No packages found') return add_separator = False for package in packages: if add_separator: print(self.SEPARATOR) add_separator = True for package_id in self.get_package_id(package): errata = self.client.packages.listProvidingErrata(self.session, package_id) print(package) print('-' * len(package)) if errata: print('\n'.join(sorted([e.get('advisory') for e in errata]))) #################### def help_package_listdependencies(self): print('package_listdependencies: List the dependencies for a package') print('usage: package_listdependencies PACKAGE') def do_package_listdependencies(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_package_listdependencies() return packages = [] for package in args: packages.extend(self.do_package_search(' '.join(args), True)) if not packages: logging.warning('No packages found') return add_separator = False for package in packages: if add_separator: print(self.SEPARATOR) add_separator = True for package_id in self.get_package_id(package): if not package_id: logging.warning('%s is not a valid package' % package) continue package_id = int(package_id) pkgdeps = self.client.packages.list_dependencies(self.session, package_id) print('Package Name: %s' % package) for dep in pkgdeps: print('Dependency: %s Type: %s Modifier: %s' % \ (dep['dependency'], dep['dependency_type'], dep['dependency_modifier'])) print(self.SEPARATOR) 07070100000022000081B40000000000000000000000015D65A5B200003216000000000000000000000000000000000000001E00000000spacecmd/src/spacecmd/repo.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2011 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import shlex try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_repo_list(self): print('repo_list: List all available user repos') print('usage: repo_list') def do_repo_list(self, args, doreturn=False): repos = self.client.channel.software.listUserRepos(self.session) repos = [c.get('label') for c in repos] if doreturn: return repos else: if repos: print('\n'.join(sorted(repos))) #################### def help_repo_details(self): print('repo_details: Show the details of a user repo') print('usage: repo_details <repo ...>') def complete_repo_details(self, text, line, beg, end): return tab_completer(self.do_repo_list('', True), text) def do_repo_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_repo_details() return # allow globbing of repo names repos = filter_results(self.do_repo_list('', True), args) add_separator = False for repo in repos: details = self.client.channel.software.getRepoDetails( self.session, repo) if add_separator: print(self.SEPARATOR) add_separator = True print('Repository Label: %s' % details.get('label')) print('Repository URL: %s' % details.get('sourceUrl')) print('Repository Type: %s' % details.get('type')) print('Repository SSL Ca Certificate: %s' % (details.get('sslCaDesc') or "None")) print('Repository SSL Client Certificate: %s' % (details.get('sslCertDesc') or "None")) print('Repository SSL Client Key: %s' % (details.get('sslKeyDesc') or "None")) #################### def help_repo_listfilters(self): print('repo_listfilters: Show the filters for a user repo') print('usage: repo_listfilters repo') def complete_repo_listfilters(self, text, line, beg, end): return tab_completer(self.do_repo_list('', True), text) def do_repo_listfilters(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_repo_listfilters() return filters = \ self.client.channel.software.listRepoFilters(self.session, args[0]) for f in filters: print("%s%s" % (f.get('flag'), f.get('filter'))) #################### def help_repo_addfilters(self): print('repo_addfilters: Add filters for a user repo') print('usage: repo_addfilters repo <filter ...>') def complete_repo_addfilters(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_repo_list('', True), text) def do_repo_addfilters(self, args): # arguments can start with -, so don't parse arguments in the normal way args = shlex.split(args) if not args: self.help_repo_addfilters() return repo = args[0] for arg in args[1:]: flag = arg[0] repofilter = arg[1:] if not (flag == '+' or flag == '-'): logging.error('Each filter must start with + or -') return self.client.channel.software.addRepoFilter(self.session, repo, {'filter': repofilter, 'flag': flag}) #################### def help_repo_removefilters(self): print('repo_removefilters: Remove filters from a user repo') print('usage: repo_removefilters repo <filter ...>') def complete_repo_removefilters(self, text, line, beg, end): return tab_completer(self.do_repo_remove('', True), text) def do_repo_removefilters(self, args): # arguments can start with -, so don't parse arguments in the normal way args = shlex.split(args) if not args: self.help_repo_removefilters() return repo = args[0] for arg in args[1:]: flag = arg[0] repofilter = arg[1:] if not (flag == '+' or flag == '-'): logging.error('Each filter must start with + or -') return self.client.channel.software.removeRepoFilter(self.session, repo, {'filter': repofilter, 'flag': flag}) #################### def help_repo_setfilters(self): print('repo_setfilters: Set the filters for a user repo') print('usage: repo_setfilters repo <filter ...>') def complete_repo_setfilters(self, text, line, beg, end): return tab_completer(self.do_repo_set('', True), text) def do_repo_setfilters(self, args): # arguments can start with -, so don't parse arguments in the normal way args = shlex.split(args) if not args: self.help_repo_setfilters() return repo = args[0] filters = [] for arg in args[1:]: flag = arg[0] repofilter = arg[1:] if not (flag == '+' or flag == '-'): logging.error('Each filter must start with + or -') return filters.append({'filter': repofilter, 'flag': flag}) self.client.channel.software.setRepoFilters(self.session, repo, filters) #################### def help_repo_clearfilters(self): print('repo_clearfilters: Clears the filters for a user repo') print('usage: repo_clearfilters repo') def complete_repo_clearfilters(self, text, line, beg, end): return tab_completer(self.do_repo_clear('', True), text) def do_repo_clearfilters(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_repo_clearfilters() return if self.user_confirm('Remove these filters [y/N]:'): self.client.channel.software.clearRepoFilters(self.session, args[0]) #################### def help_repo_delete(self): print('repo_delete: Delete a user repo') print('usage: repo_delete <repo ...>') def complete_repo_delete(self, text, line, beg, end): return tab_completer(self.do_repo_list('', True), text) def do_repo_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_repo_delete() return # allow globbing of repo names repos = filter_results(self.do_repo_list('', True), args) print('Repos') print('-----') print('\n'.join(sorted(repos))) if self.user_confirm('Delete these repos [y/N]:'): for repo in repos: try: self.client.channel.software.removeRepo(self.session, repo) except xmlrpclib.Fault: logging.error('Failed to remove repo %s' % repo) #################### def help_repo_create(self): print('repo_create: Create a user repository') print('''usage: repo_create <options>) options: -n, --name name of repository -u, --url url of repository -t, --type type of repository (defaults to yum) --ca SSL CA certificate (not required) --cert SSL Client certificate (not required) --key SSL Client key (not required)''') def do_repo_create(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-u', '--url') arg_parser.add_argument('-t', '--type') arg_parser.add_argument('--ca', default='') arg_parser.add_argument('--cert', default='') arg_parser.add_argument('--key', default='') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.name = prompt_user('Name:', noblank=True) options.url = prompt_user('URL:', noblank=True) options.type = prompt_user('Type:', noblank=True) options.ca = prompt_user('SSL CA cert:') options.cert = prompt_user('SSL Client cert:') options.key = prompt_user('SSL Client key:') else: if not options.name: logging.error('A name is required') return if not options.url: logging.error('A URL is required') return if not options.type: options.type = 'yum' self.client.channel.software.createRepo(self.session, options.name, options.type, options.url, options.ca, options.cert, options.key) #################### def help_repo_rename(self): print('repo_rename: Rename a user repository') print('usage: repo_rename OLDNAME NEWNAME') def complete_repo_rename(self, text, line, beg, end): if len(line.split(' ')) <= 2: return tab_completer(self.do_repo_list('', True), text) def do_repo_rename(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_repo_rename() return try: details = self.client.channel.software.getRepoDetails(self.session, args[0]) oldname = details.get('id') except xmlrpclib.Fault: logging.error('Could not find repo %s' % args[0]) return False newname = args[1] self.client.channel.software.updateRepoLabel(self.session, oldname, newname) #################### def help_repo_updateurl(self): print('repo_updateurl: Change the URL of a user repository') print('usage: repo_updateurl <repo> <url>') def complete_repo_updateurl(self, text, line, beg, end): if len(line.split(' ')) == 2: return tab_completer(self.do_repo_list('', True), text) def do_repo_updateurl(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_repo_updateurl() return name = args[0] url = args[1] self.client.channel.software.updateRepoUrl(self.session, name, url) def help_repo_updatessl(self): print('repo_updatessl: Change the SSL certificates of a user repository') print('''usage: repo_updatessl <options>) options: --ca SSL CA certificate (not required) --cert SSL Client certificate (not required) --key SSL Client key (not required)''') def do_repo_updatessl(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('--ca', default='') arg_parser.add_argument('--cert', default='') arg_parser.add_argument('--key', default='') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.name = prompt_user('Name:', noblank=True) options.ca = prompt_user('SSL CA cert:') options.cert = prompt_user('SSL Client cert:') options.key = prompt_user('SSL Client key:') else: if not options.name: logging.error('A name is required') return self.client.channel.software.updateRepoSsl(self.session, options.name, options.ca, options.cert, options.key) 07070100000023000081B40000000000000000000000015D65A5B200002A21000000000000000000000000000000000000002000000000spacecmd/src/spacecmd/report.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from operator import itemgetter from spacecmd.utils import * def help_report_inactivesystems(self): print('report_inactivesystems: List all inactive systems') print('usage: report_inactivesystems [DAYS]') def do_report_inactivesystems(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) # allow the user to set a limit on the number of days if len(args) == 1: try: days = int(args[0]) except ValueError: # default to a week when passed a bad argument days = 7 systems = self.client.system.listInactiveSystems(self.session, days) else: # use the server's default period if no argument was passed systems = self.client.system.listInactiveSystems(self.session) if systems: max_size = max_length([s.get('name') for s in systems]) print('%s %s %s' % ('System ID ', 'System'.ljust(max_size), 'Last Checkin')) print(('---------- '+'-' * max_size) + ' ------------') for s in sorted(systems, key=itemgetter('name')): print('%s %s %s' % (s.get('id'),s.get('name').ljust(max_size), s.get('last_checkin'))) #################### def help_report_outofdatesystems(self): print('report_outofdatesystems: List all out-of-date systems') print('usage: report_outofdatesystems') def do_report_outofdatesystems(self, args): systems = self.client.system.listOutOfDateSystems(self.session) max_size = max_length([s.get('name') for s in systems]) report = {} for system in systems: report[system.get('name')] = system.get('outdated_pkg_count') if report: print('%s %s' % ('System'.ljust(max_size), 'Packages')) print(('-' * max_size) + ' --------') for system in sorted(report): print('%s %s' % (system.ljust(max_size), str(report[system]).rjust(3))) #################### def help_report_ungroupedsystems(self): print('report_ungroupedsystems: List all ungrouped systems') print('usage: report_ungroupedsystems') def do_report_ungroupedsystems(self, args): systems = self.client.system.listUngroupedSystems(self.session) systems = [s.get('name') for s in systems] if systems: print('\n'.join(sorted(systems))) #################### def help_report_errata(self): print('report_errata: List all errata and how many systems they affect') print('usage: report_errata [ERRATA|search:XXX ...]') # XXX: performance is terrible due to all the API calls def do_report_errata(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: print('All errata requested - this may take a few minutes, please be patient!') errata_list = self.expand_errata(args) report = {} for erratum in errata_list: logging.debug('Getting affected systems for %s' % erratum) affected = self.client.errata.listAffectedSystems(self.session, erratum) num_affected = len(affected) if num_affected: report[erratum] = num_affected # XXX: max(list, key=len) in >2.5 max_size = 0 for e in report: size = len(e) if size > max_size: max_size = size if report: print('%s # Systems' % ('Errata'.ljust(max_size))) print('%s ---------' % ('------'.ljust(max_size))) for erratum in sorted(report): print('%s %s' % (erratum.ljust(max_size), str(report[erratum]).rjust(3))) #################### def help_report_ipaddresses(self): print('report_ipaddresses: List the hostname and IP of each system') print('usage: report_ipaddresses [<SYSTEMS>]') print('') print(self.HELP_SYSTEM_OPTS) def do_report_ipaddresses(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if args: # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) else: systems = self.get_system_names() report = {} for system in systems: system_id = self.get_system_id(system) network = self.client.system.getNetwork(self.session, system_id) report[system] = {'hostname': network.get('hostname'), 'ip': network.get('ip')} # XXX: max(list, key=len) in >2.5 system_max_size = 0 for s in report: size = len(s) if size > system_max_size: system_max_size = size hostname_max_size = 0 for h in [report[h]['hostname'] for h in report]: size = len(h) if size > hostname_max_size: hostname_max_size = size if report: print('%s %s IP' % ('System'.ljust(system_max_size), 'Hostname'.ljust(hostname_max_size))) print('%s %s --' % ('------'.ljust(system_max_size), '--------'.ljust(hostname_max_size))) for system in sorted(report): print('%s %s %s' % (system.ljust(system_max_size), report[system]['hostname'].ljust(hostname_max_size), report[system]['ip'].ljust(15))) #################### def help_report_kernels(self): print('report_kernels: List the running kernel of each system') print('usage: report_kernels [<SYSTEMS>]') print('') print(self.HELP_SYSTEM_OPTS) def do_report_kernels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if args: # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) else: systems = self.get_system_names() report = {} for system in systems: system_id = self.get_system_id(system) kernel = self.client.system.getRunningKernel(self.session, system_id) report[system] = kernel # XXX: max(list, key=len) in >2.5 system_max_size = 0 for s in report: size = len(s) if size > system_max_size: system_max_size = size if report: print('%s Kernel' % ('System'.ljust(system_max_size))) print('%s ------' % ('------'.ljust(system_max_size))) for system in sorted(report): print('%s %s' % (system.ljust(system_max_size), report[system])) #################### def help_report_duplicates(self): print('report_duplicates: List duplicate system profiles') print('usage: report_duplicates') def do_report_duplicates(self, args): add_separator = False dupes_by_profile = [] for system in self.get_system_names(): if self.get_system_names().count(system) > 1: if system not in dupes_by_profile: dupes_by_profile.append(system) if dupes_by_profile: add_separator = True for item in dupes_by_profile: print('%s:' % item) # get some details for each duplicate systems = self.client.system.searchByName(self.session, '^%s$' % item) print('System ID Last Checkin') print('---------- -----------------') for dupe in systems: print('%i %s' % (dupe.get('id'), dupe.get('last_checkin'))) if len(dupes_by_profile) > 1: print('') if self.check_api_version('10.11'): dupes_by_ip = self.client.system.listDuplicatesByIp(self.session) dupes_by_mac = self.client.system.listDuplicatesByMac(self.session) dupes_by_hostname = \ self.client.system.listDuplicatesByHostname(self.session) if dupes_by_ip: if add_separator: print(self.SEPARATOR) add_separator = True for item in dupes_by_ip: print('%s:' % item.get('ip')) print('System ID Last Checkin') print('---------- -----------------') for dupe in item.get('systems'): print('%i %s' % (dupe.get('systemId'), dupe.get('last_checkin'))) if len(dupes_by_ip) > 1: print('') if dupes_by_mac: if add_separator: print(self.SEPARATOR) add_separator = True for item in dupes_by_mac: print('%s:' % item.get('mac').upper()) print('System ID Last Checkin') print('---------- -----------------') for dupe in item.get('systems'): print('%i %s' % (dupe.get('systemId'), dupe.get('last_checkin'))) if len(dupes_by_mac) > 1: print('') if dupes_by_hostname: if add_separator: print(self.SEPARATOR) add_separator = True for item in dupes_by_hostname: print('%s:' % item.get('hostname')) print('System ID Last Checkin') print('---------- -----------------') for dupe in item.get('systems'): print('%i %s' % (dupe.get('systemId'), dupe.get('last_checkin'))) if len(dupes_by_hostname) > 1: print('') 07070100000024000081B40000000000000000000000015D65A5B2000017B9000000000000000000000000000000000000001E00000000spacecmd/src/spacecmd/scap.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2013--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from spacecmd.utils import * def help_scap_listxccdfscans(self): print('scap_listxccdfscans: Return a list of finished OpenSCAP scans for given systems') print('usage: scap_listxccdfscans <SYSTEMS>') def complete_system_scap_listxccdfscans(self, text, line, beg, end): return self.tab_complete_systems(text) def do_scap_listxccdfscans(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_scap_listxccdfscans() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) add_separator = False for system in sorted(systems): if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) print('') system_id = self.get_system_id(system) if not system_id: continue scan_list = self.client.system.scap.listXccdfScans(self.session, system_id) for s in scan_list: print('XID: %d Profile: %s Path: (%s) Completed: %s' % (s['xid'], s['profile'], s['path'], s['completed'])) #################### def help_scap_getxccdfscanruleresults(self): print('scap_getxccdfscanruleresults: Return a full list of RuleResults for given OpenSCAP XCCDF scan') print('usage: scap_getxccdfscanruleresults <XID>') def do_scap_getxccdfscanruleresults(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_scap_getxccdfscanruleresults() return add_separator = False for xid in args: if add_separator: print(self.SEPARATOR) add_separator = True if len(args) > 1: print('XID: %s' % xid) print('') xid = int(xid) scan_results = self.client.system.scap.getXccdfScanRuleResults(self.session, xid) for s in scan_results: print('IDref: %s Result: %s Idents: (%s)' % (s['idref'], s['result'], s['idents'])) #################### def help_scap_getxccdfscandetails(self): print('scap_getxccdfscandetails: Get details of given OpenSCAP XCCDF scan') print('usage: scap_getxccdfscandetails <XID>') def do_scap_getxccdfscandetails(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_scap_getxccdfscandetails() return add_separator = False for xid in args: if add_separator: print(self.SEPARATOR) add_separator = True if len(args) > 1: print('XID: %s' % xid) print('') xid = int(xid) scan_details = self.client.system.scap.getXccdfScanDetails(self.session, xid) print("XID:", scan_details['xid'], "SID:", scan_details['sid'], "Action_ID:", scan_details['action_id'], "Path:", scan_details['path'], \ "OSCAP_Parameters:", scan_details['oscap_parameters'], \ "Test_Result:", scan_details['test_result'], "Benchmark:", \ scan_details['benchmark'], "Benchmark_Version:", \ scan_details['benchmark_version'], "Profile:", scan_details['profile'], \ "Profile_Title:", scan_details['profile_title'], "Start_Time:", \ scan_details['start_time'], "End_Time:", scan_details['end_time'], \ "Errors:", scan_details['errors']) #################### def help_scap_schedulexccdfscan(self): print('scap_schedulexccdfscan: Schedule Scap XCCDF scan') print('usage: scap_schedulexccdfscan PATH_TO_XCCDF_FILE XCCDF_OPTIONS SYSTEMS') print(' scap_schedulexccdfscan ssm PATH_TO_XCCDF_FILE XCCDF_OPTIONS') print('') print('Example:') print('> scap_schedulexccdfscan \'/usr/share/openscap/scap-security-xccdf.xml\'' + ' \'profile Web-Default\' system-scap.example.com') print('\nTo use systems in the ssm, pass the "ssm" keyword in front. Example:') print("> scap_schedulexccdfscan ssm '/usr/share/openscap/scap-security-xccdf.xml'" + " 'profile Web-Default'") def do_scap_schedulexccdfscan(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 3: self.help_scap_schedulexccdfscan() return path = args[0] param = "--" param += args[1] # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args[2:]) for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.scap.scheduleXccdfScan(self.session, system_id, path, param) 07070100000025000081B40000000000000000000000015D65A5B2000034B3000000000000000000000000000000000000002200000000spacecmd/src/spacecmd/schedule.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import base64 from operator import itemgetter try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def print_schedule_summary(self, action_type, args): args = args.split() or [] if args: begin_date = parse_time_input(args[0]) logging.debug('Begin Date: %s' % begin_date) else: begin_date = None if len(args) > 1: end_date = parse_time_input(args[1]) logging.debug('End Date: %s' % end_date) else: end_date = None if action_type == 'pending': actions = self.client.schedule.listInProgressActions(self.session) elif action_type == 'completed': actions = self.client.schedule.listCompletedActions(self.session) elif action_type == 'failed': actions = self.client.schedule.listFailedActions(self.session) elif action_type == 'archived': actions = self.client.schedule.listArchivedActions(self.session) elif action_type == 'all': # get actions in all states except archived in_progress = self.client.schedule.listInProgressActions(self.session) completed = self.client.schedule.listCompletedActions(self.session) failed = self.client.schedule.listFailedActions(self.session) actions = [] added = [] for action in in_progress + completed + failed: if action.get('id') not in added: actions.append(action) added.append(action.get('id')) else: return if not actions: return print('ID Date C F P Action') print('-- ---- --- --- --- ------') for action in sorted(actions, key=itemgetter('id'), reverse=True): if begin_date: if action.get('earliest') < begin_date: continue if end_date: if action.get('earliest') > end_date: continue if self.check_api_version('10.11'): print('%s %s %s %s %s %s' % (str(action.get('id')).ljust(6), action.get('earliest'), str(action.get('completedSystems')).rjust(3), str(action.get('failedSystems')).rjust(3), str(action.get('inProgressSystems')).rjust(3), action.get('name'))) else: # Satellite 5.3 compatibility in_progress = \ self.client.schedule.listInProgressSystems(self.session, action.get('id')) completed = \ self.client.schedule.listCompletedSystems(self.session, action.get('id')) failed = \ self.client.schedule.listFailedSystems(self.session, action.get('id')) print('%s %s %s %s %s %s' % (str(action.get('id')).ljust(6), action.get('earliest'), str(len(completed)).rjust(3), str(len(failed)).rjust(3), str(len(in_progress)).rjust(3), action.get('name'))) #################### def help_schedule_cancel(self): print('schedule_cancel: Cancel scheduled actions') print('usage: schedule_cancel ID|* ...') def complete_schedule_cancel(self, text, line, beg, end): try: actions = self.client.schedule.listInProgressActions(self.session) return tab_completer([str(a.get('id')) for a in actions], text) except xmlrpclib.Fault: return [] def do_schedule_cancel(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_schedule_cancel() return # cancel all actions if '.*' in args: if not self.user_confirm('Cancel all pending actions [y/N]:'): return actions = self.client.schedule.listInProgressActions(self.session) strings = [a.get('id') for a in actions] else: strings = args # convert strings to integers actions = [] for a in strings: try: actions.append(int(a)) except ValueError: logging.warning('%s is not a valid ID' % str(a)) continue self.client.schedule.cancelActions(self.session, actions) for a in actions: logging.info('Canceled action %i' % a) print('Canceled %i action(s)' % len(actions)) #################### def help_schedule_reschedule(self): print('schedule_reschedule: Reschedule failed actions') print('usage: schedule_reschedule ID|* ...') def complete_schedule_reschedule(self, text, line, beg, end): try: actions = self.client.schedule.listFailedActions(self.session) return tab_completer([str(a.get('id')) for a in actions], text) except xmlrpclib.Fault: return [] def do_schedule_reschedule(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_schedule_reschedule() return failed_actions = self.client.schedule.listFailedActions(self.session) failed_actions = [a.get('id') for a in failed_actions] to_reschedule = [] # reschedule all failed actions if '.*' in args: if not self.user_confirm('Reschedule all failed actions [y/N]:'): return to_reschedule = failed_actions else: # use the list of action IDs passed in for a in args: try: action_id = int(a) if action_id in failed_actions: to_reschedule.append(action_id) else: logging.warning('%i is not a failed action' % action_id) except ValueError: logging.warning('%s is not a valid ID' % str(a)) continue if not to_reschedule: logging.warning('No failed actions to reschedule') return self.client.schedule.rescheduleActions(self.session, to_reschedule, True) print('Rescheduled %i action(s)' % len(to_reschedule)) #################### def help_schedule_details(self): print('schedule_details: Show the details of a scheduled action') print('usage: schedule_details ID') def do_schedule_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_schedule_details() return try: action_id = int(args[0]) except ValueError: logging.warning('%s is not a valid ID' % str(action_id)) return completed = self.client.schedule.listCompletedSystems(self.session, action_id) failed = self.client.schedule.listFailedSystems(self.session, action_id) pending = self.client.schedule.listInProgressSystems(self.session, action_id) # put all the system arrays together for the summary all_systems = [] all_systems.extend(completed) all_systems.extend(failed) all_systems.extend(pending) # schedule.getAction() API call would make this easier all_actions = self.client.schedule.listAllActions(self.session) action = None for a in all_actions: if a.get('id') == action_id: action = a del all_actions break print('ID: %i' % action.get('id')) print('Action: %s' % action.get('name')) print('User: %s' % action.get('scheduler')) print('Date: %s' % action.get('earliest')) print('') print('Completed: %s' % str(len(completed)).rjust(3)) print('Failed: %s' % str(len(failed)).rjust(3)) print('Pending: %s' % str(len(pending)).rjust(3)) if completed: print('') print('Completed Systems') print('-----------------') for s in completed: print(s.get('server_name')) if failed: print('') print('Failed Systems') print('--------------') for s in failed: print(s.get('server_name')) if pending: print('') print('Pending Systems') print('---------------') for s in pending: print(s.get('server_name')) #################### def help_schedule_getoutput(self): print('schedule_getoutput: Show the output from an action') print('usage: schedule_getoutput ID') def do_schedule_getoutput(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_schedule_getoutput() return try: action_id = int(args[0]) except ValueError: logging.error('%s is not a valid action ID' % str(args[0])) return script_results = None try: script_results = \ self.client.system.getScriptResults(self.session, action_id) except xmlrpclib.Fault: pass # scripts have a different data structure than other actions if script_results: add_separator = False for r in script_results: if add_separator: print(self.SEPARATOR) add_separator = True if r.get('serverId'): system = self.get_system_name(r.get('serverId')) else: system = 'UNKNOWN' print('System: %s' % system) print('Start Time: %s' % r.get('startDate')) print('Stop Time: %s' % r.get('stopDate')) print('Return Code: %i' % r.get('returnCode')) print('') print('Output') print('------') if r.get('output_enc64'): print(base64.b64decode(r.get('output'))) else: print(r.get('output').encode('UTF8')) else: completed = self.client.schedule.listCompletedSystems(self.session, action_id) failed = self.client.schedule.listFailedSystems(self.session, action_id) add_separator = False for action in completed + failed: if add_separator: print(self.SEPARATOR) add_separator = True print('System: %s' % action.get('server_name')) print('Completed: %s' % action.get('timestamp')) print('') print('Output') print('------') print(action.get('message')) #################### def help_schedule_listpending(self): print('schedule_listpending: List pending actions') print('usage: schedule_listpending [BEGINDATE] [ENDDATE]') print('') print(self.HELP_TIME_OPTS) def do_schedule_listpending(self, args): return self.print_schedule_summary('pending', args) #################### def help_schedule_listcompleted(self): print('schedule_listcompleted: List completed actions') print('usage: schedule_listcompleted [BEGINDATE] [ENDDATE]') print('') print(self.HELP_TIME_OPTS) def do_schedule_listcompleted(self, args): return self.print_schedule_summary('completed', args) #################### def help_schedule_listfailed(self): print('schedule_listfailed: List failed actions') print('usage: schedule_listfailed [BEGINDATE] [ENDDATE]') print('') print(self.HELP_TIME_OPTS) def do_schedule_listfailed(self, args): return self.print_schedule_summary('failed', args) #################### def help_schedule_listarchived(self): print('schedule_listarchived: List archived actions') print('usage: schedule_listarchived [BEGINDATE] [ENDDATE]') print('') print(self.HELP_TIME_OPTS) def do_schedule_listarchived(self, args): return self.print_schedule_summary('archived', args) #################### def help_schedule_list(self): print('schedule_list: List all actions') print('usage: schedule_list [BEGINDATE] [ENDDATE]') print('') print(self.HELP_TIME_OPTS) def do_schedule_list(self, args): return self.print_schedule_summary('all', args) 07070100000026000081B40000000000000000000000015D65A5B200001E11000000000000000000000000000000000000001F00000000spacecmd/src/spacecmd/shell.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 # use of exec # pylint: disable=W0122 import atexit import logging import os import readline import re import shlex import sys from cmd import Cmd from spacecmd.utils import * class UnknownCallException(Exception): def __init__(self): Exception.__init__(self) class SpacewalkShell(Cmd): __module_list = ['activationkey', 'configchannel', 'cryptokey', 'custominfo', 'distribution', 'errata', 'filepreservation', 'group', 'kickstart', 'misc', 'org', 'package', 'repo', 'report', 'schedule', 'snippet', 'softwarechannel', 'ssm', 'api', 'system', 'user', 'utils', 'scap'] # a SyntaxError is thrown if we don't wrap this in an 'exec' for module in __module_list: exec('from spacecmd.%s import *' % module) # maximum length of history file HISTORY_LENGTH = 1024 cmdqueue = [] completekey = 'tab' stdout = sys.stdout prompt_template = 'spacecmd {SSM:##}> ' current_line = '' # do nothing on an empty line emptyline = lambda self: None def __init__(self, options, conf_dir, config_parser): Cmd.__init__(self) self.session = '' self.current_user = '' self.server = '' self.ssm = {} self.config = {} self.postcmd(False, '') # make the options available everywhere self.options = options # make the configuration file available everywhere self.config_parser = config_parser # this is used when loading and saving caches self.conf_dir = conf_dir self.history_file = os.path.join(self.conf_dir, 'history') try: # don't split on hyphens or colons during tab completion newdelims = readline.get_completer_delims() newdelims = re.sub(':|-|/', '', newdelims) readline.set_completer_delims(newdelims) if not options.nohistory: try: if os.path.isfile(self.history_file): readline.read_history_file(self.history_file) readline.set_history_length(self.HISTORY_LENGTH) # always write the history file on exit atexit.register(readline.write_history_file, self.history_file) except IOError: logging.error('Could not read history file') # pylint: disable=W0702 except Exception as exc: # pylint: disable=W0702 logging.error("Exception occurred: {}".format(exc)) sys.exit(1) # handle shell exits and history substitution def precmd(self, line): # disable too-many-return-statements warning # pylint: disable=R0911 # remove leading/trailing whitespace line = re.sub(r'^\s+|\s+$', '', line) # don't do anything on empty lines if line == '': return '' # terminate the shell if re.match('quit|exit|eof', line, re.I): print('') sys.exit(0) # don't attempt to login for some commands if re.match('help|login|logout|whoami|history|clear', line, re.I): # login required for clear_caches or it fails with: # "SpacewalkShell instance has no attribute 'system_cache_file'" if not re.match('clear_caches', line, re.I): return line # login before attempting to run a command if not self.session: # disable no-member error message # pylint: disable=E1101 self.do_login('') if self.session == '': return '' parts = shlex.split(line) if parts: command = parts[0] else: return '' # print(the help message for a command if the user passed --help) if '--help' in parts or '-h' in parts: return 'help %s' % command # should we look for an item in the history? if command[0] != '!' or len(command) < 2: return line # remove the '!*' line from the history # disable no-member error message # pylint: disable=E1101 self.remove_last_history_item() history_match = False if command[1] == '!': # repeat the last command line = readline.get_history_item( readline.get_current_history_length()) if line: history_match = True else: logging.warning('%s: event not found', command) return '' # attempt to find a numbered history item if not history_match: try: number = int(command[1:]) line = readline.get_history_item(number) if line: history_match = True else: raise Exception except IndexError: pass except ValueError: pass # attempt to match the beginning of the string with a history item if not history_match: history_range = range(1, readline.get_current_history_length()) history_range.reverse() for i in history_range: item = readline.get_history_item(i) if re.match(command[1:], item): line = item history_match = True break # append the arguments to the substituted command if history_match: if parts[1:]: for arg in parts[1:]: line += " '%s'" % arg readline.add_history(line) print(line) return line else: logging.warning('%s: event not found', command) return '' @staticmethod def print_result(cmdresult, cmd): logging.debug(cmd + ": " + repr(cmdresult)) if cmd: try: if type(cmdresult).__name__ == 'str': print(cmdresult) else: for i in cmdresult: print(i) except TypeError: pass # update the prompt with the SSM size # pylint: disable=arguments-differ def postcmd(self, cmdresult, cmd): SpacewalkShell.print_result(cmdresult, cmd) self.prompt = re.sub('##', str(len(self.ssm)), self.prompt_template) def default(self, line): Cmd.default(self, line) raise UnknownCallException 07070100000027000081B40000000000000000000000015D65A5B200001661000000000000000000000000000000000000002100000000spacecmd/src/spacecmd/snippet.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from spacecmd.utils import * def help_snippet_list(self): print('snippet_list: List the available Kickstart snippets') print('usage: snippet_list') def do_snippet_list(self, args, doreturn=False): snippets = self.client.kickstart.snippet.listCustom(self.session) snippets = sorted([s.get('name') for s in snippets]) if doreturn: return snippets else: if snippets: print('\n'.join(snippets)) #################### def help_snippet_details(self): print('snippet_details: Show the contents of a snippet') print('usage: snippet_details SNIPPET ...') def complete_snippet_details(self, text, line, beg, end): return tab_completer(self.do_snippet_list('', True), text) def do_snippet_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_snippet_details() return add_separator = False snippets = self.client.kickstart.snippet.listCustom(self.session) snippet = None for name in args: for s in snippets: if s.get('name') == name: snippet = s break if not snippet: logging.warning('%s is not a valid snippet' % name) continue if add_separator: print(self.SEPARATOR) add_separator = True print('Name: %s' % snippet.get('name')) print('Macro: %s' % snippet.get('fragment')) print('File: %s' % snippet.get('file')) print('') print(snippet.get('contents')) #################### def help_snippet_create(self): print('snippet_create: Create a Kickstart snippet') print('''usage: snippet_create [options]) options: -n NAME -f FILE''') def do_snippet_create(self, args, update_name=''): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) contents = '' if is_interactive(options): # if update_name was passed, we're trying to update an existing snippet if update_name: options.name = update_name snippets = self.client.kickstart.snippet.listCustom(self.session) for s in snippets: if s.get('name') == update_name: contents = s.get('contents') break if not options.name: options.name = prompt_user('Name:', noblank=True) if self.user_confirm('Read an existing file [y/N]:', nospacer=True, ignore_yes=True): options.file = prompt_user('File:') else: (contents, _ignore) = editor(template=contents, delete=True) else: if not options.name: logging.error('A name is required for the snippet') return if not options.file: logging.error('A file is required') return if options.file: contents = read_file(options.file) print('') print('Snippet: %s' % options.name) print('Contents') print('--------') print(contents) if self.user_confirm(): self.client.kickstart.snippet.createOrUpdate(self.session, options.name, contents) #################### def help_snippet_update(self): print('snippet_update: Update a Kickstart snippet') print('usage: snippet_update NAME') def complete_snippet_update(self, text, line, beg, end): return tab_completer(self.do_snippet_list('', True), text) def do_snippet_update(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_snippet_update() return return self.do_snippet_create('', update_name=args[0]) #################### def help_snippet_delete(self): print('snippet_delete: Delete a Kickstart snippet') print('usage: snippet_delete NAME') def complete_snippet_delete(self, text, line, beg, end): return tab_completer(self.do_snippet_list('', True), text) def do_snippet_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_snippet_delete() return snippet = args[0] if self.user_confirm('Remove this snippet [y/N]:'): self.client.kickstart.snippet.delete(self.session, snippet) 07070100000028000081B40000000000000000000000015D65A5B2000180DC000000000000000000000000000000000000002900000000spacecmd/src/spacecmd/softwarechannel.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2011--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 try: from urllib import ContentTooShortError except ImportError: from urllib.error import ContentTooShortError try: from urllib import urlretrieve except ImportError: from urllib.request import urlretrieve try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * CHECKSUM = ['sha1', 'sha256', 'sha384', 'sha512'] def help_softwarechannel_list(self): print('softwarechannel_list: List all available software channels') print('''usage: softwarechannel_list [options]') options: -v verbose (display label and summary) -t tree view (pretty-print(child-channels)) ''') def do_softwarechannel_list(self, args, doreturn=False): arg_parser = get_argument_parser() arg_parser.add_argument('-v', '--verbose', action='store_true') arg_parser.add_argument('-t', '--tree', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if (options.tree): labels = self.list_base_channels() else: channels = self.client.channel.listAllChannels(self.session) labels = [c.get('label') for c in channels] # filter the list if arguments were passed if args: labels = filter_results(labels, args, True) if doreturn: return labels elif labels: if (options.verbose): for l in sorted(labels): details = self.client.channel.software.getDetails( self.session, l) print("%s : %s" % (l, details['summary'])) if (options.tree): for c in self.list_child_channels(parent=l): cdetails = self.client.channel.software.getDetails( self.session, c) print(" |-%s : %s" % (c, cdetails['summary'])) else: for l in sorted(labels): print("%s" % l) if (options.tree): for c in self.list_child_channels(parent=l): print(" |-%s" % c) #################### def help_softwarechannel_listmanageablechannels(self): print('softwarechannel_listmanageablechannels: List all software channels') print(' manageable by current user') print('''usage: softwarechannel_listmanageablechannels [options] options: -v verbose (display label and summary)''') def do_softwarechannel_listmanageablechannels(self, args, doreturn=False): arg_parser = get_argument_parser() arg_parser.add_argument('-v', '--verbose', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) channels = self.client.channel.listManageableChannels(self.session) labels = [c.get('label') for c in channels] # filter the list if arguments were passed if args: labels = filter_results(labels, args, True) if doreturn: return labels elif labels: if options.verbose: for l in sorted(labels): details = \ self.client.channel.software.getDetails(self.session, l) print("%s : %s" % (l, details['summary'])) else: for l in sorted(labels): print("%s" % l) #################### def help_softwarechannel_listbasechannels(self): print('softwarechannel_listbasechannels: List all base software channels') print('''usage: softwarechannel_listbasechannels [options]) options: -v verbose (display label and summary)''') def do_softwarechannel_listbasechannels(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-v', '--verbose', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) channels = self.list_base_channels() if channels: if (options.verbose): for c in sorted(channels): details = \ self.client.channel.software.getDetails(self.session, c) print("%s : %s" % (c, details['summary'])) else: print('\n'.join(sorted(channels))) #################### def help_softwarechannel_listchildchannels(self): print('softwarechannel_listchildchannels: List child software channels') print('usage:') print('softwarechannel_listchildchannels [options]') print('softwarechannel_listchildchannels : List all child channels') print('softwarechannel_listchildchannels CHANNEL : List children for a' + 'specific base channel') print('options:\n -v verbose (display label and summary)') def do_softwarechannel_listchildchannels(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-v', '--verbose', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if not args: channels = self.list_child_channels() else: channels = self.list_child_channels(parent=args[0]) if channels: if (options.verbose): for c in sorted(channels): details = \ self.client.channel.software.getDetails(self.session, c) print("%s : %s" % (c, details['summary'])) else: print('\n'.join(sorted(channels))) #################### def help_softwarechannel_listsystems(self): print('softwarechannel_listsystems: List all systems subscribed to') print(' a software channel') print('usage: softwarechannel_listsystems CHANNEL') def complete_softwarechannel_listsystems(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_listsystems(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_listsystems() return channel = args[0] systems = \ self.client.channel.software.listSubscribedSystems(self.session, channel) systems = [s.get('name') for s in systems] if doreturn: return systems else: if systems: print('\n'.join(sorted(systems))) #################### def help_softwarechannel_listpackages(self): print('softwarechannel_listpackages: List the most recent packages') print(' available from a software channel') print('usage: softwarechannel_listpackages CHANNEL') def complete_softwarechannel_listpackages(self, text, line, beg, end): if len(line.split(' ')) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_listpackages(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_listpackages() return channel = args[0] packages = self.client.channel.software.listLatestPackages(self.session, channel) packages = build_package_names(packages) if doreturn: return packages else: if packages: print('\n'.join(sorted(packages))) #################### def help_softwarechannel_listallpackages(self): print('softwarechannel_listallpackages: List all packages in a channel') print('usage: softwarechannel_listallpackages CHANNEL') def complete_softwarechannel_listallpackages(self, text, line, beg, end): if len(line.split(' ')) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_listallpackages(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_listallpackages() return channel = args[0] packages = self.client.channel.software.listAllPackages(self.session, channel) packages = build_package_names(packages) if doreturn: return packages else: if packages: print('\n'.join(sorted(packages))) #################### def filter_latest_packages(pkglist): # This takes a list of package dicts, and returns a new list # which contains only the latest version, for each arch # First we generate a dict, indexed by a compound (tuple) key based on # arch and name, so we can store the latest version of each package # for each arch. This approach avoids nested loops :) latest = {} for p in pkglist: tuplekey = p['name'], p['arch_label'] if tuplekey not in latest: latest[tuplekey] = p else: # Already have this package, is p newer? if p == latest_pkg(p, latest[tuplekey]): latest[tuplekey] = p # Then return the dict items as a list return latest.values() def help_softwarechannel_listlatestpackages(self): print('softwarechannel_listlatestpackages: List the newest version of all packages in a channel') print('usage: softwarechannel_listlatestpackages CHANNEL') def complete_softwarechannel_listlatestpackages(self, text, line, beg, end): if len(line.split(' ')) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_listlatestpackages(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_listlatestpackages() return channel = args[0] allpackages = self.client.channel.software.listAllPackages(self.session, channel) latestpackages = filter_latest_packages(allpackages) packages = build_package_names(latestpackages) if doreturn: return packages else: if packages: print('\n'.join(sorted(packages))) #################### def help_softwarechannel_setdetails(self): print('softwarechannel_setdetails: Modify details of a software channel') print('''usage: softwarechannel_setdetails [options] <CHANNEL ...>) options, at least one of which must be given: -n NAME -d DESCRIPTION -s SUMMARY -c CHECKSUM %s -m MAINTAINER_NAME -e MAINTAINER_EMAIL -p MAINTAINER_PHONE -u GPG_URL -i GPG_ID -f GPG_FINGERPRINT''' % CHECKSUM) def complete_softwarechannel_setdetails(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_setdetails(self, args): # pylint: disable=R0911 arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-s', '--summary') arg_parser.add_argument('-d', '--description') arg_parser.add_argument('-c', '--checksum') arg_parser.add_argument('-m', '--maintainer_name') arg_parser.add_argument('-e', '--maintainer_email') arg_parser.add_argument('-p', '--maintainer_phone') arg_parser.add_argument('-u', '--gpg_url') arg_parser.add_argument('-i', '--gpg_id') arg_parser.add_argument('-f', '--gpg_fingerprint') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_setdetails() return new_details = {} if options.name: new_details['name'] = options.name if options.summary: new_details['summary'] = options.summary if options.description: new_details['description'] = options.description if options.checksum: new_details['checksum_label'] = options.checksum if options.maintainer_name: new_details['maintainer_name'] = options.maintainer_name if options.maintainer_email: new_details['maintainer_email'] = options.maintainer_email if options.maintainer_phone: new_details['maintainer_phone'] = options.maintainer_phone if options.gpg_url: new_details['gpg_key_url'] = options.gpg_url if options.gpg_id: new_details['gpg_key_id'] = options.gpg_id if options.gpg_fingerprint: new_details['gpg_key_fp'] = options.gpg_fingerprint if not new_details: logging.error('At least one attribute to set must be given') return # allow globbing of software channel names channels = filter_results(self.do_softwarechannel_list('', True), args) if len(channels) < 1: logging.info('No channels matching that label') return # channel names must be unique, so if we are asked to set it, # take special precautions: first: check if we're doing this for # more than one channel (because if we do, first might succeed, # all the rest will obviously fail), # second: check if there's any other channel already using this name. # Not reusing softwarechannel_check_existing() here because it # also fails on same label. if new_details.get('name'): if len(channels) > 1: logging.error('Setting same name for more than ' + \ 'one channel will fail') return logging.debug('checking other channels for name "%s"' % new_details.get('name')) for c in self.list_base_channels() + self.list_child_channels(): cd = self.client.channel.software.getDetails(self.session, c) if cd.get('name') == new_details.get('name'): logging.error('Name "%s" already in use by channel %s' % (cd.get('name'), cd.get('label'))) return # get confirmation print('Setting following attributes...') print('') if new_details.get('name'): print('Name: %s' % new_details.get('name')) if new_details.get('summary'): print('Summary: %s' % new_details.get('summary')) if new_details.get('description'): print('Description: %s' % new_details.get('description')) if new_details.get('checksum_label'): print('Checksum: %s' % new_details.get('checksum_label')) if new_details.get('maintainer_name'): print('Maintainer name: %s' % new_details.get('maintainer_name')) if new_details.get('maintainer_email'): print('Maintainer email: %s' % new_details.get('maintainer_email')) if new_details.get('maintainer_phone'): print('Maintainer phone: %s' % new_details.get('maintainer_phone')) if new_details.get('gpg_key_id'): print('GPG Key: %s' % new_details.get('gpg_key_id')) if new_details.get('gpg_key_fp'): print('GPG Fingerprint: %s' % new_details.get('gpg_key_fp')) if new_details.get('gpg_key_url'): print('GPG URL: %s' % new_details.get('gpg_key_url')) print('') print('... for the following channels:') print('\n'.join(channels)) print('') if not self.user_confirm('Apply? [y/N]:'): return logging.debug('new channel details dictionary:') logging.debug(new_details) num_changed = 0 for channel in channels: logging.debug('getting ID for channel %s' % channel) try: details = self.client.channel.software.getDetails(self.session, channel) except xmlrpclib.Fault as e: logging.error('Could not get details for %s' % channel) logging.error(e) return channel_id = details.get('id') logging.debug('setting details for channel %s (%d)' % (channel, channel_id)) try: self.client.channel.software.setDetails(self.session, channel_id, new_details) num_changed += 1 except xmlrpclib.Fault as e: logging.error('Error while setting details for %s' % channel) logging.error(e) return logging.info('Channels changed: %d' % num_changed) #################### def help_softwarechannel_details(self): print('softwarechannel_details: Show the details of a software channel') print('usage: softwarechannel_details <CHANNEL ...>') def complete_softwarechannel_details(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_details() return # allow globbing of software channel names channels = filter_results(self.do_softwarechannel_list('', True), args) add_separator = False for channel in channels: details = self.client.channel.software.getDetails( self.session, channel) systems = \ self.client.channel.software.listSubscribedSystems( self.session, channel) trees = self.client.kickstart.tree.list(self.session, channel) packages = \ self.client.channel.software.listAllPackages( self.session, channel) if add_separator: print(self.SEPARATOR) add_separator = True print('Label: %s' % details.get('label')) print('Name: %s' % details.get('name')) print('Architecture: %s' % details.get('arch_name')) print('Parent: %s' % details.get('parent_channel_label')) print('Systems Subscribed: %s' % len(systems)) print('Number of Packages: %i' % len(packages)) if details.get('summary'): print('') print('Summary') print('-------') print('\n'.join(wrap(details.get('summary')))) if details.get('description'): print('') print('Description') print('-----------') print('\n'.join(wrap(details.get('description')))) print('') print('GPG Key: %s' % details.get('gpg_key_id')) print('GPG Fingerprint: %s' % details.get('gpg_key_fp')) print('GPG URL: %s' % details.get('gpg_key_url')) print('GPG CHECK: %s' % details.get('gpg_check')) if trees: print('') print('Kickstart Trees') print('---------------') for tree in trees: print(tree.get('label')) if details.get('contentSources'): print('') print('Repos') print('-----') for repo in details.get('contentSources'): print(repo.get('label')) #################### def help_softwarechannel_listerrata(self): print('softwarechannel_listerrata: List the errata associated with a') print(' software channel') print('usage: softwarechannel_listerrata <CHANNEL ...> [from=yyyymmdd [to=yyyymmdd]]') def complete_softwarechannel_listerrata(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_listerrata(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_listerrata() return begin_date = None end_date = None # iterate over args and alter a copy of it (channels) channels = args[:] for arg in args: if arg[:5] == 'from=': begin_date = arg[5:] channels.remove(arg) elif arg[:3] == 'to=': end_date = arg[3:] channels.remove(arg) add_separator = False for channel in sorted(channels): if len(channels) > 1: print('Channel: %s' % channel) print('') if begin_date and end_date: errata = self.client.channel.software.listErrata(self.session, channel, parse_time_input(begin_date), parse_time_input(end_date)) elif begin_date: errata = self.client.channel.software.listErrata(self.session, channel, parse_time_input(begin_date)) else: errata = self.client.channel.software.listErrata(self.session, channel) print_errata_list(errata) if add_separator: print(self.SEPARATOR) add_separator = True #################### def help_softwarechannel_listarches(self): print("softwarechannel_listarches: lists the potential software") print(" channel architectures that can be created") print("usage: softwarechannel_listarches") print("options:") print(" -v verbose (display label and name)") def do_softwarechannel_listarches(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-v', '--verbose', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) arches = self.client.channel.software.listArches(self.session) for arch in sorted(arches): if (options.verbose): print("%s (%s)" % (arch["label"], arch["name"])) else: print("%s" % arch["label"]) #################### def help_softwarechannel_delete(self): print('softwarechannel_delete: Delete a software channel') print('usage: softwarechannel_delete <CHANNEL ...>') def complete_softwarechannel_delete(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_delete() return channels = args # find all matching channels to_delete = filter_results( self.do_softwarechannel_list('', True), channels) if not to_delete: return print('Channels') print('--------') print('\n'.join(sorted(to_delete))) if not self.user_confirm('Delete these channels [y/N]:'): return # delete child channels first to avoid errors parents = [] children = [] all_channels = self.client.channel.listSoftwareChannels(self.session) for channel in all_channels: if channel.get('label') in to_delete: if channel.get('parent_label'): children.append(channel.get('label')) else: parents.append(channel.get('label')) for channel in children + parents: self.client.channel.software.delete(self.session, channel) #################### def help_softwarechannel_update(self): print('softwarechannel_update: Update a software channel') print('''usage: softwarechannel_update LABEL(To identify the channel) [options] options: -l LABEL(Required) -n NAME -s SUMMARY -d DESCRIPTION -c CHECKSUM %s -u GPG-URL -i GPG-ID -f GPG-FINGERPRINT -g DISABLE-GPG-CHECK''' % CHECKSUM) def do_softwarechannel_update(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-l', '--label') arg_parser.add_argument('-s', '--summary') arg_parser.add_argument('-d', '--description') arg_parser.add_argument('-c', '--checksum') arg_parser.add_argument('-u', '--gpg_url') arg_parser.add_argument('-i', '--gpg_id') arg_parser.add_argument('-f', '--gpg_fingerprint') arg_parser.add_argument('-g', '--disable_gpg_check') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.label = prompt_user('Channel Label:', noblank=True) print('') print('New Name (blank to keep unchanged)') print('------------') print('') options.name = prompt_user('Name:') print('') print('New Summary (blank to keep unchanged)') print('------------') print('') options.summary = prompt_user('Summary:') print('') print('New Description (blank to keep unchanged)') print('------------') print('') options.description = prompt_user('Description:') print('') print('New Checksum type (blank to keep unchanged)') print('------------') print('\n'.join(sorted(self.CHECKSUM))) print('') options.checksum = prompt_user('Select:') print('') print('New GPG URL (blank to keep unchanged)') print('------------') print('') options.gpg_url = prompt_user('GPG URL:') print('') print('New GPG ID (blank to keep unchanged)') print('------------') print('') options.gpg_id = prompt_user('GPG ID:') print('') print('New GPG Fingerprint (blank to keep unchanged)') print('------------') print('') options.gpg_fingerprint = prompt_user('GPG Fingerprint:') print('') print('Disable GPG CHECK (blank to keep unchanged)') print('------------') print('') options.disable_gpg_check = prompt_user('Disable GPG Check [yes/no]? ') if not options.label: logging.error('A channel label is required to identify the channel') return if options.disable_gpg_check and options.disable_gpg_check not in ('yes','no'): logging.error('Only [yes/no] values are acceptable for --disable_gpg_check') return details = {} if options.name: details['name'] = options.name if options.summary: details['summary'] = options.summary if options.description: details['description'] = options.description if options.checksum: details['checksum_label'] = options.checksum if options.gpg_id: details['gpg_key_id'] = options.gpg_id if options.gpg_url: details['gpg_key_url'] = options.gpg_url if options.gpg_fingerprint: details['gpg_key_fp'] = options.gpg_fingerprint if options.disable_gpg_check: details['gpg_check'] = str(not options.disable_gpg_check.lower() == "yes") self.client.channel.software.setDetails(self.session, options.label, details) #################### def help_softwarechannel_create(self): print('softwarechannel_create: Create a software channel') print('''usage: softwarechannel_create [options]) options: -n NAME -l LABEL -s SUMMARY -p PARENT_CHANNEL -a ARCHITECTURE -c CHECKSUM %s -u GPG_URL -i GPG_ID -f GPG_FINGERPRINT -g DISABLE_GPG_CHECK''' % CHECKSUM) def do_softwarechannel_create(self, args): arches = self.client.channel.software.listArches(self.session) self.ARCH_LABELS = [x["label"].replace("channel-","") for x in arches] arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-l', '--label') arg_parser.add_argument('-s', '--summary') arg_parser.add_argument('-p', '--parent-channel') arg_parser.add_argument('-a', '--arch') arg_parser.add_argument('-c', '--checksum') arg_parser.add_argument('-u', '--gpg_url') arg_parser.add_argument('-i', '--gpg_id') arg_parser.add_argument('-f', '--gpg_fingerprint') arg_parser.add_argument('-g', '--disable_gpg_check', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.name = prompt_user('Channel Name:', noblank=True) options.label = prompt_user('Channel Label:', noblank=True) options.summary = prompt_user('Channel Summary:', noblank=True) print('Base Channels') print('-------------') print('\n'.join(sorted(self.list_base_channels()))) print('') options.parent_channel = \ prompt_user('Select Parent [blank to create a base channel]:') print('') print('Architecture') print('------------') print('\n'.join(sorted(self.ARCH_LABELS))) print('') options.arch = prompt_user('Select:') print('') print('Checksum type') print('------------') print('\n'.join(sorted(self.CHECKSUM))) print('') options.checksum = prompt_user('Select:') print('') print('GPG URL') print('------------') print('') options.gpg_url = prompt_user('GPG URL:') print('') print('GPG ID') print('------------') print('') options.gpg_id = prompt_user('GPG ID:') print('') print('GPG Fingerprint') print('---------------') print('') options.gpg_fingerprint = prompt_user('GPG Fingerprint:') print('') print('GPG CHECK') print('---------------') print('') options.disable_gpg_check = self.user_confirm('Disable GPG Check [y/N]? ', nospacer=True, ignore_yes=True) if validate_required_data(options): set_default_data(options) gpgData = get_gpg_data(options) self.client.channel.software.create(self.session, options.label, options.name, options.summary, 'channel-%s' % options.arch, options.parent_channel, options.checksum, gpgData, not options.disable_gpg_check ) #################### def get_gpg_data(options): gpgData = {} if options.gpg_url: gpgData['url'] = options.gpg_url if options.gpg_id: gpgData['id'] = options.gpg_id if options.gpg_fingerprint: gpgData['fingerprint'] = options.gpg_fingerprint return gpgData #################### def set_default_data(options): if not options.arch: options.arch = 'x86_64' if not options.checksum: options.checksum = 'sha256' if not options.parent_channel: options.parent_channel = '' # Summary is a required field, # but we don't want to break the interface # then if it is not provided it is set to the 'name' value if not options.summary: options.summary = options.name #################### def validate_required_data(options): if not options.name: logging.error('A channel name is required') return False if not options.label: logging.error('A channel label is required') return False return True ###################### def softwarechannel_check_existing(self, name, label): # Catch label or name which already exists, duplicate label throws a # descriptive xmlrpc error, but duplicate name results in ISE for c in self.list_base_channels() + self.list_child_channels(): cd = self.client.channel.software.getDetails(self.session, c) if cd['name'] == name: logging.error("Name %s already in use by channel %s" % (name, cd['label'])) return True if cd['label'] == label: logging.error("Label %s already in use by channel %s" % (label, cd['label'])) return True return False ######################## def help_softwarechannel_clone(self): print('softwarechannel_clone: Clone a software channel') print('''usage: softwarechannel_clone [options]) options: -s SOURCE_CHANNEL -n NAME -l LABEL -p PARENT_CHANNEL --gpg-copy/-g (copy SOURCE_CHANNEL GPG details) --gpg-url GPG_URL --gpg-id GPG_ID --gpg-fingerprint(GPG_FINGERPRINT) --disable-gpg-check DISABLE_GPG_CHECK -o do not clone any patches --regex/-x "s/foo/bar" : Optional regex replacement, replaces foo with bar in the clone name and label''') def do_softwarechannel_clone(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-l', '--label') arg_parser.add_argument('-s', '--source-channel') arg_parser.add_argument('-p', '--parent-channel') arg_parser.add_argument('-x', '--regex') arg_parser.add_argument('-o', '--original-state', action='store_true') arg_parser.add_argument('-g', '--gpg-copy', action='store_true') arg_parser.add_argument('--gpg-url') arg_parser.add_argument('--gpg-id') arg_parser.add_argument('--gpg-fingerprint') arg_parser.add_argument('--disable-gpg-check') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): print('Source Channels:') print('\n'.join(sorted(self.list_base_channels()))) print('\n'.join(sorted(self.list_child_channels()))) options.source_channel = prompt_user('Select source channel:',noblank=True) options.name = prompt_user('Channel Name:', noblank=True) options.label = prompt_user('Channel Label:', noblank=True) print('Base Channels:') print('\n'.join(sorted(self.list_base_channels()))) print('') options.parent_channel = \ prompt_user('Select Parent [blank to create a base channel]:') options.gpg_copy = \ self.user_confirm('Copy source channel GPG details? [y/N]:', ignore_yes=True) if not options.gpg_copy: options.gpg_url = prompt_user('GPG URL:') options.gpg_id = prompt_user('GPG ID:') options.gpg_fingerprint = prompt_user('GPG Fingerprint:') options.disable_gpg_check = prompt_user('Disable GPG Check [yes/no]? ') options.original_state = \ self.user_confirm('Original State (No Errata) [y/N]:', ignore_yes=True) else: if not options.source_channel: logging.error('A source channel is required') return if not options.name and not options.regex: logging.error('A channel name is required') return if not options.label and not options.regex: logging.error('A channel label is required') return if not options.original_state: options.original_state = False if options.regex: newvalues =do_regx_replacement(self,options.source_channel, options) options.label = newvalues['label'] if not options.name: options.name = newvalues['name'] if options.disable_gpg_check and options.disable_gpg_check not in ('yes', 'no'): logging.error('Only [yes/no] values are acceptable for --disable-gpg-check') return if self.softwarechannel_check_existing(options.name, options.label): return details = {'name': options.name, 'label': options.label} if options.parent_channel: details['parent_label'] = options.parent_channel clone_channel(self,options.source_channel, options, details) #################### def help_softwarechannel_clonetree(self): print('softwarechannel_clonetree: Clone a software channel and its child channels') print('''usage: softwarechannel_clonetree [options]) e.g softwarechannel_clonetree foobasechannel -p "my_" softwarechannel_clonetree foobasechannel -x "s/foo/bar" softwarechannel_clonetree foobasechannel -x "s/^/my_" options: -s/--source-channel SOURCE_CHANNEL -p/--prefix PREFIX (is prepended to the label and name of all channels) --gpg-copy/-g (copy GPG details for correspondoing source channel)) --gpg-url GPG_URL (applied to all channels) --gpg-id GPG_ID (applied to all channels) --gpg-fingerprint(GPG_FINGERPRINT (applied to all channels)) --disable-gpg-check DISABLE_GPG_CHECK(applied to all channels) -o do not clone any errata --regex/-x "s/foo/bar" : Optional regex replacement, replaces foo with bar in the clone name, label and description''') def do_softwarechannel_clonetree(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--source-channel') arg_parser.add_argument('-p', '--prefix') arg_parser.add_argument('-x', '--regex') arg_parser.add_argument('-o', '--original-state', action='store_true') arg_parser.add_argument('-g', '--gpg-copy', action='store_true') arg_parser.add_argument('--gpg-url') arg_parser.add_argument('--gpg-id') arg_parser.add_argument('--gpg-fingerprint') arg_parser.add_argument('--disable-gpg-check') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): print('Source Channels:') print('\n'.join(sorted(self.list_base_channels()))) options.source_channel = prompt_user('Select source channel:',noblank=True) options.prefix = prompt_user('Prefix:', noblank=True) options.gpg_copy = \ self.user_confirm('Copy source channel GPG details? [y/N]:', ignore_yes=True) if not options.gpg_copy: options.gpg_url = prompt_user('GPG URL:') options.gpg_id = prompt_user('GPG ID:') options.gpg_fingerprint = prompt_user('GPG Fingerprint:') options.disable_gpg_check = prompt_user('Disable GPG Check [yes/no]? ') options.original_state = \ self.user_confirm('Original State (No Errata) [y/N]:', ignore_yes=True) else: if not options.source_channel: logging.error('A source channel is required') return if not options.prefix and not options.regex: logging.error('A prefix or regex is required') return if not options.original_state: options.original_state = False if options.disable_gpg_check and options.disable_gpg_check not in ('yes', 'no'): logging.error('Only [yes/no] values are acceptable for --disable-gpg-check') return channels = [options.source_channel] if not options.source_channel in self.list_base_channels(): logging.error("Channel does not exist or is not a base channel!") self.help_softwarechannel_clonetree() return logging.debug("--child mode specified, finding children of %s\n" % options.source_channel) children = self.list_child_channels(parent=options.source_channel) logging.debug("Found children %s\n" % children) for c in children: channels.append(c) logging.debug("channels=%s" % channels) parent_channel = None for ch in channels: logging.debug("Cloning %s" % ch) label = None name = None if options.regex: # Expect option to be formatted like a sed-replacement, s/foo/bar newvalues = do_regx_replacement(self, ch, options) label = newvalues['label'] name = newvalues['name'] elif options.prefix: srcdetails = self.client.channel.software.getDetails(self.session, ch) label = options.prefix + srcdetails['label'] name = options.prefix + srcdetails['name'] else: # Shouldn't ever get here due to earlier checks logging.error("called without prefix or regex option!") return # Catch label or name which already exists if self.softwarechannel_check_existing(name, label): return details = {'name': name, 'label': label} if parent_channel: details['parent_label'] = parent_channel clone_channel(self, ch, options, details) # If this is the first call we are on the base-channel clone and we # need to set parent_channel to the new cloned base-channel label if not parent_channel: parent_channel = details['label'] ################### def clone_channel(self, channel, options, details) : if options.gpg_copy: srcdetails = self.client.channel.software.getDetails(self.session, channel) copy_gpg_values_from_source(details, srcdetails); if options.gpg_id: details['gpg_id'] = options.gpg_id if options.gpg_url: details['gpg_url'] = options.gpg_url if options.gpg_fingerprint: details['gpg_fingerprint'] = options.gpg_fingerprint if options.disable_gpg_check: details['gpg_check'] = str(not options.disable_gpg_check.lower() == "yes") apiversion = self.client.api.getVersion() if apiversion and int(apiversion) <= 19: details['summary'] = details['name'] # remove empty strings from the structure to_remove = [] for key in details: if details[key] == '': to_remove.append(key) for key in to_remove: del details[key] logging.info("Cloning %s as %s" % (channel, details['label'])) self.client.channel.software.clone(self.session, channel, details, options.original_state) ################### def copy_gpg_values_from_source(details, srcdetails): if srcdetails['gpg_key_url']: details['gpg_key_url'] = srcdetails['gpg_key_url'] logging.debug("copying gpg_key_url=%s" % srcdetails['gpg_key_url']) if srcdetails['gpg_key_id']: details['gpg_key_id'] = srcdetails['gpg_key_id'] logging.debug("copying gpg_key_id=%s" % srcdetails['gpg_key_id']) if srcdetails['gpg_key_fp']: details['gpg_key_fp'] = srcdetails['gpg_key_fp'] logging.debug("copying gpg_key_fp=%s" % srcdetails['gpg_key_fp']) if srcdetails['gpg_check']: details['gpg_check'] = str(srcdetails['gpg_check']) logging.debug("copying gpg_check=%s" % srcdetails['gpg_check']) #################### # If the -x/--regex option is passed, do a sed-style replacement over # the name, label and description. from the source channel to create # the name, label and description for the clone channel. # This makes it easier to clone based on a known naming convention def do_regx_replacement(self,channel, options): newvalues ={} # Expect option to be formatted like a sed-replacement, s/foo/bar findstr = options.regex.split("/")[1] replacestr = options.regex.split("/")[2] logging.debug("--regex selected with %s, replacing %s with %s" %(options.regex, findstr, replacestr)) srcdetails = self.client.channel.software.getDetails(self.session, channel) newvalues['name'] = re.sub(findstr, replacestr, srcdetails['name']) newvalues['label'] = re.sub(findstr, replacestr, channel) logging.debug("regex mode : %s %s %s" % (options.source_channel, newvalues['name'], newvalues['label'])) return newvalues; #################### def help_softwarechannel_addpackages(self): print('softwarechannel_addpackages: Add packages to a software channel') print('usage: softwarechannel_addpackages CHANNEL <PACKAGE ...>') def complete_softwarechannel_addpackages(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) elif len(parts) > 2: return tab_completer(self.get_package_names(True), text) def do_softwarechannel_addpackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_softwarechannel_addpackages() return # Take the first argument as the channel and validate it channel = args.pop(0) if not channel in self.do_softwarechannel_list('', True): logging.error("%s is not a valid channel" % channel) self.help_softwarechannel_addpackages() return # expand the arguments to search for packages package_names = [] for item in args: package_names.extend(self.do_package_search(item, True)) # get the package IDs from the names package_ids = [] for package in package_names: package_ids += self.get_package_id(package) if not package_ids: logging.warning('No packages to add') return print('Packages') print('--------') print('\n'.join(sorted(package_names))) if not self.user_confirm('Add these packages [y/N]:'): return self.client.channel.software.addPackages(self.session, channel, package_ids) #################### def help_softwarechannel_mergepackages(self): print('softwarechannel_mergepackages: Merge packages from one software channel into another') print('usage: softwarechannel_mergepackages SOURCE_CHANNEL TARGET_CHANNEL') def complete_softwarechannel_mergepackages(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) if len(parts) == 3: return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_mergepackages(self, args): ''' Does the same thing as do_softwarechannel_sync with the exception of deleting packages from the target channel. ''' self.do_softwarechannel_sync(args, deleteFromTarget=False) #################### def help_softwarechannel_removeerrata(self): print('softwarechannel_removeerrata: Remove patches from a ' + 'software channel') print('usage: softwarechannel_removeerrata CHANNEL <ERRATA:search:XXX ...>') def complete_softwarechannel_removeerrata(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) elif len(parts) > 2: return self.tab_complete_errata(text) def do_softwarechannel_removeerrata(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_removeerrata() return channel = args[0] errata_wanted = self.expand_errata(args[1:]) logging.debug('Retrieving the list of patches from source channel') channel_errata = self.client.channel.software.listErrata(self.session, channel) errata = filter_results([e.get('advisory_name') for e in channel_errata], errata_wanted) # keep the details for our matching errata so we can use them later errata_details = [] for erratum in channel_errata: if erratum.get('advisory_name') in errata: errata_details.append(erratum) # get the packages that resolve these errata so we can add them # to the channel afterwards package_ids = [] for erratum in errata: logging.debug('Retrieving packages for patch %s' % erratum) # get the packages affected by this errata packages = self.client.errata.listPackages(self.session, erratum) # only add packages that exist in the source channel for package in packages: if channel in package.get('providing_channels'): package_ids.append(package.get('id')) if not errata_details: logging.warning('No patches to remove') return print_errata_list(errata_details) print('') print('Packages') print('--------') print('\n'.join(sorted([ self.get_package_name(p) for p in package_ids if self.get_package_name(p) ]))) print('') print('Total Errata: %s' % str(len(errata)).rjust(3)) print('Total Packages: %s' % str(len(package_ids)).rjust(3)) if not self.user_confirm('Remove these patches [y/N]:'): return # remove the errata and the packages they brought in self.client.channel.software.removeErrata(self.session, channel, errata, True) #################### def help_softwarechannel_removepackages(self): print('softwarechannel_removepackages: Remove packages from a ' + 'software channel') print('usage: softwarechannel_removepackages CHANNEL <PACKAGE ...>') def complete_softwarechannel_removepackages(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) elif len(parts) > 2: # only tab complete packages in the channel package_names = [] try: packages = \ self.client.channel.software.listAllPackages(self.session, parts[1]) package_names = build_package_names(packages) except xmlrpclib.Fault: package_names = [] return tab_completer(package_names, text) def do_softwarechannel_removepackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_removepackages() return channel = args.pop(0) package_list = args # get all the packages in the channel packages = \ self.client.channel.software.listAllPackages(self.session, channel) # build full names for those packages installed_packages = build_package_names(packages) # find matching packages that are in the channel package_names = filter_results(installed_packages, package_list) # get the package IDs from the names package_ids = [] for package in package_names: package_ids += self.get_package_id(package) if not package_ids: logging.warning('No packages to remove') return print('Packages') print('--------') print('\n'.join(sorted(package_names))) if not self.user_confirm('Remove these packages [y/N]:'): return self.client.channel.software.removePackages(self.session, channel, package_ids) #################### def help_softwarechannel_adderratabydate(self): print('softwarechannel_adderratabydate: Add errata from one channel ' + 'into another channel based on a date range') print('usage: softwarechannel_adderratabydate [options] SOURCE DEST BEGINDATE ENDDATE') print('Date format : YYYYMMDD') print('Options:') print(' -p/--publish : Publish errata to the channel (don\'t clone)') def complete_softwarechannel_adderratabydate(self, text, line, beg, end): parts = line.split(' ') if len(parts) <= 3: return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_adderratabydate(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-p', '--publish', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if len(args) != 4: self.help_softwarechannel_adderratabydate() return source_channel = args[0] dest_channel = args[1] begin_date = args[2] end_date = args[3] if not re.match(r'\d{8}', begin_date): logging.error('%s is an invalid date' % begin_date) self.help_softwarechannel_adderratabydate() return if not re.match(r'\d{8}', end_date): logging.error('%s is an invalid date' % end_date) self.help_softwarechannel_adderratabydate() return # get the errata that are in the given date range logging.debug('Retrieving list of patches from source channel') errata = \ self.client.channel.software.listErrata(self.session, source_channel, parse_time_input(begin_date), parse_time_input(end_date)) if not errata: logging.warning('No patches found between the given dates') return if options.publish: # Just publish the errata one-by-one, rather than calling # do_softwarechannel_adderrata which clones the errata for e in errata: logging.info("Publishing errata %s to %s" % (e.get('advisory_name'), dest_channel)) self.client.errata.publish(self.session, e.get('advisory_name'), [dest_channel]) else: # call adderrata with the list of errata from the date range # this clones the errata and adds it to the channel return self.do_softwarechannel_adderrata('%s %s %s' % ( source_channel, dest_channel, ' '.join([e.get('advisory_name') for e in errata]))) #################### def help_softwarechannel_listerratabydate(self): print('softwarechannel_listerratabydate: list errata from channel' + 'based on a date range') print('usage: softwarechannel_listerratabydate CHANNEL BEGINDATE ENDDATE') print('Date format : YYYYMMDD') def complete_softwarechannel_listerratabydate(self, text, line, beg, end): parts = line.split(' ') if len(parts) <= 3: return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_listerratabydate(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 3: self.help_softwarechannel_listerratabydate() return channel = args[0] begin_date = args[1] end_date = args[2] if not re.match(r'\d{8}', begin_date): logging.error('%s is an invalid date' % begin_date) self.help_softwarechannel_listerratabydate() return if not re.match(r'\d{8}', end_date): logging.error('%s is an invalid date' % end_date) self.help_softwarechannel_listerratabydate() return # get the errata that are in the given date range logging.debug('Retrieving list of errata from channel %s' % channel) errata = \ self.client.channel.software.listErrata(self.session, channel, parse_time_input(begin_date), parse_time_input(end_date)) if not errata: logging.warning('No errata found between the given dates') return print_errata_list(errata) #################### def help_softwarechannel_adderrata(self): print('softwarechannel_adderrata: Add patches from one channel ' + 'into another channel') print('usage: softwarechannel_adderrata SOURCE DEST <ERRATA|search:XXX ...>') print('Options:') print(' -q/--quick : Don\'t display list of packages (slightly faster)') print(' -s/--skip : Skip errata which appear to exist already in DEST') def complete_softwarechannel_adderrata(self, text, line, beg, end): parts = line.split(' ') if len(parts) <= 3: return tab_completer(self.do_softwarechannel_list('', True), text) elif len(parts) > 3: return self.tab_complete_errata(text) def do_softwarechannel_adderrata(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-q', '--quick', action='store_true') arg_parser.add_argument('-s', '--skip', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 3: self.help_softwarechannel_adderrata() return allchannels = self.do_softwarechannel_list('', True) source_channel = args[0] if not source_channel in allchannels: logging.error("source channel %s does not exist!" % source_channel) self.help_softwarechannel_adderrata() return dest_channel = args[1] if not dest_channel in allchannels: logging.error("dest channel %s does not exist!" % dest_channel) self.help_softwarechannel_adderrata() return errata_wanted = self.expand_errata(args[2:]) logging.debug('Retrieving the list of patches from source channel') source_errata = self.client.channel.software.listErrata(self.session, source_channel) dest_errata = self.client.channel.software.listErrata(self.session, dest_channel) errata = filter_results([e.get('advisory_name') for e in source_errata], errata_wanted) logging.debug("errata = %s" % errata) if options.skip: # We just match the NNNN:MMMM of the XXXX-NNNN:MMMM as the # source errata will be RH[BES]A and the DEST errata will be CLA dest_errata_suffix = [x.get('advisory_name').split("-")[1] for x in dest_errata] logging.debug("dest_errata_suffix = %s" % dest_errata_suffix) toremove = [] for e in errata: if e.split("-")[1] in dest_errata_suffix: logging.debug("Skipping errata %s as it seems to be in %s" % (e, dest_channel)) toremove.append(e) for e in toremove: logging.debug("Removing %s from errata to be added" % e) errata.remove(e) logging.debug("skip-mode : reduced errata = %s" % errata) # keep the details for our matching errata so we can use them later errata_details = [] for erratum in source_errata: if erratum.get('advisory_name') in errata: errata_details.append(erratum) if not options.quick: # get the packages that resolve these errata so we can add them # to the channel afterwards package_ids = [] for erratum in errata: logging.debug('Retrieving packages for patch %s' % erratum) # get the packages affected by this errata packages = self.client.errata.listPackages(self.session, erratum) # only add packages that exist in the source channel for package in packages: if source_channel in package.get('providing_channels'): package_ids.append(package.get('id')) if not errata: logging.warning('No patch to add') return # show the user which errata will be added print_errata_list(errata_details) if not options.quick: print('') print('Packages') print('--------') print('\n'.join( sorted([self.get_package_name(p) for p in package_ids]))) print('') print('Total Errata: %s' % str(len(errata)).rjust(3)) if not options.quick: print('Total Packages: %s' % str(len(package_ids)).rjust(3)) if not self.user_confirm('Add these patches [y/N]:'): return # clone each erratum individually because the process is slow and it can # lead to timeouts on the server for erratum in errata: logging.debug('Cloning %s' % erratum) if self.check_api_version('10.11'): # This call is poorly documented, but it stops errata.clone # pushing EL6 packages into EL5 channels when the errata # package list contains both versions, ref bz678721 self.client.errata.cloneAsOriginal(self.session, dest_channel, [erratum]) else: logging.warning("Using the old errata.clone function") logging.warning("If you have base channels for multiple OS" + " versions, check no unexpected packages have been added") self.client.errata.clone(self.session, dest_channel, [erratum]) # regenerate the errata cache since we just cloned errata self.generate_errata_cache(True) #################### def help_softwarechannel_getorgaccess(self): print('softwarechannel_getorgaccess: Get the org-access for the software channel') print('usage: softwarechannel_getorgaccess [CHANNEL ...]') def complete_softwarechannel_getorgaccess(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_listmanageablechannels('', doreturn=True), text) def do_softwarechannel_getorgaccess(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) # If no args are passed, we dump the org access for all channels if not args: channels = self.do_softwarechannel_listmanageablechannels('', doreturn=True) else: # allow globbing of software channel names channels = filter_results( self.do_softwarechannel_listmanageablechannels('', doreturn=True), args) for channel in channels: logging.debug("Getting org-access for channel %s" % channel) sharing = self.client.channel.access.getOrgSharing( self.session, channel) print("%s : %s" % (channel, sharing)) if sharing == 'protected': # for protected channels list each organization's access status channel_orgs = self.client.channel.org.list(self.session, channel) for org in channel_orgs: print("\t%s : %s" % (org["org_name"], org["access_enabled"])) #################### def help_softwarechannel_setorgaccess(self): print('softwarechannel_setorgaccess: Set the org-access for the software channel') print('''usage : softwarechannel_setorgaccess <CHANNEL> [options]) -d,--disable : disable org access (private, no org sharing) -e,--enable : enable org access (public access to all trusted orgs) -p,--protected ORG : protected org access for ORG only (multiple instances of -p ORG are allowed)''') def complete_softwarechannel_setorgaccess(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_listmanageablechannels('', doreturn=True), text) def do_softwarechannel_setorgaccess(self, args, options=None): if not args: self.help_softwarechannel_setorgaccess() return if not options: arg_parser = get_argument_parser() arg_parser.add_argument('-e', '--enable', action='store_true') arg_parser.add_argument('-d', '--disable', action='store_true') arg_parser.add_argument('-p', '--protected', action='append') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_setorgaccess() return # allow globbing of software channel names channels = filter_results(self.do_softwarechannel_listmanageablechannels('', doreturn=True), args) # get the list of trusted organizations when we are dealing with protected channels if (options.protected): org_trust_list = self.client.org.trusts.listOrgs(self.session) for channel in channels: # If they just specify a channel and --enable/--disable # this implies public/private access if (options.enable): logging.info( "Making org sharing public for channel : %s " % channel) self.client.channel.access.setOrgSharing( self.session, channel, 'public') elif (options.disable): logging.info( "Making org sharing private for channel : %s " % channel) self.client.channel.access.setOrgSharing( self.session, channel, 'private') elif (options.protected): logging.info( "Making org sharing protected for channel : %s " % channel) self.client.channel.access.setOrgSharing( self.session, channel, 'protected') for org in org_trust_list: if org["org_name"] in options.protected: logging.info( "Enabling %s access for channel : %s " % (org["org_name"],channel)) self.client.channel.org.enableAccess( self.session, channel, org["org_id"]) else: logging.info( "Disabling %s access for channel : %s " % (org["org_name"],channel)) self.client.channel.org.disableAccess( self.session, channel, org["org_id"]) else: self.help_softwarechannel_setorgaccess() return #################### def help_softwarechannel_getorgaccesstree(self): print('softwarechannel_getorgaccesstree: Get the org-access for a software base channel and its children') print('usage: softwarechannel_getorgaccesstree [CHANNEL]') def complete_softwarechannel_getorgaccesstree(self, text, line, beg, end): return tab_completer(self.list_base_channels(), text) def do_softwarechannel_getorgaccesstree(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) # If no args are passed, we dump the org access for all base channels if not args: channels = self.list_base_channels() else: # allow globbing of software channel names channels = filter_results(self.list_base_channels(), args) if not channels: logging.error("Channel does not exist or is not a base channel!") self.help_softwarechannel_getorgaccesstree() return for channel in channels: do_softwarechannel_getorgaccess(self, channel) for child in self.list_child_channels(parent=channel): do_softwarechannel_getorgaccess(self, child) #################### def help_softwarechannel_setorgaccesstree(self): print('softwarechannel_setorgaccesstree: set the org-access for a software base channel and its children') print('''usage: softwarechannel_setorgaccesstree <CHANNEL> [options]) -d,--disable : disable org access (private, no org sharing) -e,--enable : enable org access (public access to all trusted orgs) -p,--protected ORG : protected org access for ORG only (multiple instances of -p ORG are allowed)''') def complete_softwarechannel_setorgaccesstree(self, text, line, beg, end): return tab_completer(self.list_base_channels(), text) def do_softwarechannel_setorgaccesstree(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-e', '--enable', action='store_true') arg_parser.add_argument('-d', '--disable', action='store_true') arg_parser.add_argument('-p', '--protected', action='append') (args, options) = parse_command_arguments(args, arg_parser) if not args or not (options.enable or options.disable or options.protected): self.help_softwarechannel_setorgaccesstree() return # allow globbing of software channel names channels = filter_results(self.list_base_channels(), args) if not channels: logging.error("Channel does not exist or is not a base channel!") self.help_softwarechannel_setorgaccesstree() return for channel in channels: do_softwarechannel_setorgaccess(self, [channel], options) for child in self.list_child_channels(parent=channel): do_softwarechannel_setorgaccess(self, [child], options) #################### def help_softwarechannel_regenerateneededcache(self): print('softwarechannel_regenerateneededcache: ') print('Regenerate the needed errata and package cache for all systems') print('') print('usage: softwarechannel_regenerateneededcache') def do_softwarechannel_regenerateneededcache(self, args): if self.user_confirm('Are you sure [y/N]: '): self.client.channel.software.regenerateNeededCache(self.session) #################### def help_softwarechannel_regenerateyumcache(self): print('softwarechannel_regenerateyumcache: ') print('Regenerate the YUM cache for a software channel') print('') print('''usage: softwarechannel_regenerateyumcache [options] <CHANNEL ...> options: -f force cache regeneration ''') print('') def complete_softwarechannel_regenerateyumcache(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_regenerateyumcache(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-f', '--force', action="store_true") (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_regenerateyumcache() return # allow globbing of software channel names channels = filter_results(self.do_softwarechannel_list('', True), args) for channel in channels: logging.debug('Regenerating YUM cache for %s' % channel) self.client.channel.software.regenerateYumCache(self.session, channel, _options.force) #################### # softwarechannel helper def is_softwarechannel(self, name): if not name: return return name in self.do_softwarechannel_list(name, True) def check_softwarechannel(self, name): if not name: logging.error("no softwarechannel label given") return False if not self.is_softwarechannel(name): logging.error("invalid softwarechannel label " + name) return False return True def dump_softwarechannel(self, name, replacedict=None, excludes=None): excludes = excludes or [] content = self.do_softwarechannel_listallpackages(name, doreturn=True) content = get_normalized_text( content, replacedict=replacedict, excludes=excludes) return content #################### def help_softwarechannel_diff(self): print('softwarechannel_diff: diff softwarechannel files') print('') print('usage: softwarechannel_diff SOURCE_CHANNEL TARGET_CHANNEL') def complete_softwarechannel_diff(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_softwarechannel_list('', True), text) if args == 3: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_diff(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1 and len(args) != 2: self.help_softwarechannel_diff() return source_channel = args[0] if not self.check_softwarechannel(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_softwarechannel_getcorresponding"): # can a corresponding channel name be found automatically? target_channel = self.do_softwarechannel_getcorresponding( source_channel) if not self.check_softwarechannel(target_channel): return # softwarechannel do not contain references to other components, # therefore there is no need to use replace dicts source_data = self.dump_softwarechannel(source_channel, None) target_data = self.dump_softwarechannel(target_channel, None) return diff(source_data, target_data, source_channel, target_channel) #################### def dump_softwarechannel_errata(self, name): errata = self.client.channel.software.listErrata(self.session, name) result = [] for erratum in errata: result.append('%s %s' % ( erratum.get('advisory_name').ljust(14), wrap(erratum.get('advisory_synopsis'), 50)[0])) result.sort() return result def help_softwarechannel_errata_diff(self): print('softwarechannel_errata_diff: diff softwarechannel files') print('') print('usage: softwarechannel_errata_diff SOURCE_CHANNEL TARGET_CHANNEL') def complete_softwarechannel_errata_diff(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_softwarechannel_list('', True), text) if args == 3: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_errata_diff(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1 and len(args) != 2: self.help_softwarechannel_errata_diff() return source_channel = args[0] if not self.check_softwarechannel(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_softwarechannel_getcorresponding"): # try to find the corresponding channel automatically target_channel = self.do_softwarechannel_getcorresponding( source_channel) if not self.check_softwarechannel(target_channel): return # softwarechannel do not contain references to other components, # therefore there is no need to use replace dicts source_data = self.dump_softwarechannel_errata(source_channel) target_data = self.dump_softwarechannel_errata(target_channel) return diff(source_data, target_data, source_channel, target_channel) #################### def help_softwarechannel_sync(self): print('softwarechannel_sync: ') print('sync the packages of two software channels') print('') print('''usage: softwarechannel_sync SOURCE_CHANNEL TARGET_CHANNEL [options]) -q,--quiet : quiet mode (omits the output of common packages in both channels)''') def complete_softwarechannel_sync(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_softwarechannel_list('', True), text) if args == 3: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_sync(self, args, deleteFromTarget=True): arg_parser = get_argument_parser() arg_parser.add_argument('-q', '--quiet', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if len(args) != 1 and len(args) != 2: self.help_softwarechannel_sync() if deleteFromTarget else self.help_softwarechannel_mergepackages() return source_channel = args[0] if not self.check_softwarechannel(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_softwarechannel_getcorresponding"): # can a corresponding channel name be found automatically? target_channel = self.do_softwarechannel_getcorresponding( source_channel) if not self.check_softwarechannel(target_channel): return command = "syncing" if deleteFromTarget else "merging" logging.info("%s packages from softwarechannel %s to %s", command, source_channel, target_channel) # use API call instead of spacecmd function # to get detailed infos about the packages # and not just their names source_packages = self.client.channel.software.listAllPackages( self.session, source_channel) target_packages = self.client.channel.software.listAllPackages( self.session, target_channel) # get the package IDs source_ids = set() for package in source_packages: try: source_ids.add(package['id']) except KeyError: logging.error("failed to read key id") continue target_ids = set() for package in target_packages: try: target_ids.add(package['id']) except KeyError: logging.error("failed to read key id") continue if not options.quiet: print("packages common in both channels:") for i in (source_ids & target_ids): print(self.get_package_name(i)) print('') else: logging.info("Omitting common packages in both specified channels") # check for packages only in the source channel source_only = source_ids.difference(target_ids) if source_only: print('packages to add to channel "' + target_channel + '":') for i in source_only: print(self.get_package_name(i)) print('') # check for packages only in the target channel target_only = target_ids.difference(source_ids) if target_only and deleteFromTarget: print('packages to remove from channel "' + target_channel + '":') for i in target_only: print(self.get_package_name(i)) print('') if source_only or target_only: print("summary:") print(" " + source_channel + ": " + str(len(source_ids)).rjust(5) + " packages") print(" " + target_channel + ": " + str(len(target_ids)).rjust(5) + " packages") print(" add " + str( len(source_only)).rjust(5) + " packages to " + target_channel) if deleteFromTarget: print(" remove " + str( len(target_only)).rjust(5) + " packages from " + target_channel) if not self.user_confirm('Perform these changes to channel ' + target_channel + ' [y/N]:'): return if deleteFromTarget: self.client.channel.software.addPackages(self.session, target_channel, list(source_only)) self.client.channel.software.removePackages(self.session, target_channel, list(target_only)) else: self.client.channel.software.mergePackages(self.session, source_channel, target_channel) #################### def help_softwarechannel_errata_sync(self): print('softwarechannel_errata_sync: ') print('sync errata of two software channels') print('') print('usage: softwarechannel_errata_sync SOURCE_CHANNEL TARGET_CHANNEL') def complete_softwarechannel_errata_sync(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_softwarechannel_list('', True), text) if args == 3: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_errata_sync(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1 and len(args) != 2: self.help_softwarechannel_errata_sync() return source_channel = args[0] if not self.check_softwarechannel(source_channel): return target_channel = None if len(args) == 2: target_channel = args[1] elif hasattr(self, "do_softwarechannel_getcorresponding"): # try to find a corresponding channel name automatically target_channel = self.do_softwarechannel_getcorresponding( source_channel) if not self.check_softwarechannel(target_channel): return logging.info("syncing errata from softwarechannel " + source_channel + " to " + target_channel) source_errata = self.client.channel.software.listErrata( self.session, source_channel) target_errata = self.client.channel.software.listErrata( self.session, target_channel) # store unique errata data in a set source_ids = set() for erratum in source_errata: try: source_ids.add(erratum.get('advisory_name')) except KeyError: logging.error("failed to read key id") continue target_ids = set() for erratum in target_errata: try: target_ids.add(erratum.get('advisory_name')) except KeyError: logging.error("failed to read key id") continue print("errata common in both channels:") for i in (source_ids & target_ids): print(i) print('') # check for errata only in the source channel source_only = list(source_ids.difference(target_ids)) source_only.sort() if source_only: print('errata to add to channel "' + target_channel + '":') for i in source_only: print(i) print('') # check for errata only in the target channel target_only = list(target_ids.difference(source_ids)) target_only.sort() if target_only: print('errata to remove from channel "' + target_channel + '":') for i in target_only: print(i) print('') if source_only or target_only: print("summary:") print(" " + source_channel + ": " + str(len(source_ids)).rjust(5), "errata") print(" " + target_channel + ": " + str(len(target_ids)).rjust(5), "errata") print(" add ", str( len(source_only)).rjust(5), "errata to ", target_channel) print(" remove", str( len(target_only)).rjust(5), "errata from", target_channel) if not self.user_confirm('Perform these changes to channel ' + target_channel + ' [y/N]:'): return for erratum in source_only: print(erratum) self.client.errata.publish(self.session, erratum, [target_channel]) # alternative: # channel.software.mergeErrata: Merges all errata from one channel into another # channel.software.removeErrata: # string channelLabel - target channel. # array: # string - advisoryName - name of an erratum to remove # boolean removePackages - True to remove packages from the channel self.client.channel.software.removeErrata(self.session, target_channel, target_only, False) #################### def help_softwarechannel_errata_merge(self): print('softwarechannel_errata_merge: ') print('merge errata of one software channel into another') print('') print('usage: softwarechannel_errata_merge SOURCE_CHANNEL TARGET_CHANNEL [FROM_DATE] [TO_DATE]') print('example: softwarechannel_errata_merge OldSoftChannel NewSoftChannel 2018-01-01') print('note: Providing only FROM_DATE will merge all errata since that date.') print('note: When no date is provided, all errata will be merged.') def complete_softwarechannel_errata_merge(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') args = len(parts) if args == 2: return tab_completer(self.do_softwarechannel_list('', True), text) if args == 3: return tab_completer(self.do_softwarechannel_list('', True), text) return [] def do_softwarechannel_errata_merge(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) FROM_DATE = datetime.strptime("1970-01-01", "%Y-%m-%d") TO_DATE = datetime.now() + timedelta(1) #tomorrow if len(args) < 2 or len(args) > 4: self.help_softwarechannel_errata_merge() return if len(args) >= 3: try: FROM_DATE = datetime.strptime(args[2], "%Y-%m-%d") except ValueError: print('wrong dateformat: ' + args[2] + ' please specify as: YYYY-MM-DD') return if len(args) == 4: try: TO_DATE = datetime.strptime(args[3], "%Y-%m-%d") except ValueError: print('wrong dateformat: ' + args[3] + ' please specify as: YYYY-MM-DD') return source_channel = args[0] target_channel = args[1] if not self.check_softwarechannel(source_channel) or not self.check_softwarechannel(target_channel): return logging.info("merging errata from softwarechannel " + source_channel + " to " + target_channel) source_errata = self.client.channel.software.listErrata( self.session, source_channel) # filter out errata which are not in the specified timeframe (if given) for erratum in source_errata[:]: erratum_date = datetime.strptime(erratum['issue_date'].split(' ')[0], "%Y-%m-%d") if not (FROM_DATE <= erratum_date <= TO_DATE): source_errata.remove(erratum) target_errata = self.client.channel.software.listErrata( self.session, target_channel) # store unique errata data in a set source_ids = set() for erratum in source_errata: try: source_ids.add(erratum.get('advisory_name')) except KeyError: logging.error("failed to read key id") continue target_ids = set() for erratum in target_errata: try: target_ids.add(erratum.get('advisory_name')) except KeyError: logging.error("failed to read key id") continue # check for errata only in the source channel source_only = list(source_ids.difference(target_ids)) if len(source_only) == 0: print('{} contains no errata which is not already present in {}'.format(source_channel, target_channel)) return source_only.sort() if source_only: print('errata to add to channel {}: '.format(target_channel)) for i in source_only: print(i) print('') print("summary:") print(" " + source_channel + ": " + str(len(source_ids)).rjust(5) + " errata") print(" " + target_channel + ": " + str(len(target_ids)).rjust(5) + " errata") print (" add " + str( len(source_only)).rjust(5) + " errata to " + target_channel) if not self.user_confirm('Perform these changes to channel {} [y/N]:'.format(target_channel)): return self.client.channel.software.mergeErrata(self.session, source_channel, target_channel, source_only) #################### def help_softwarechannel_syncrepos(self): print('softwarechannel_syncrepos: ') print('Sync users repos for a software channel') print('') print('usage: softwarechannel_syncrepos <CHANNEL ...>') print('Options:') print(' -e/--no-errata : Do not sync errata') print(' -f/--fail : Terminate upon any error') print(' -k/--sync-kickstart : Create kickstartable tree') print(' -l/--latest : Only download latest package versions when repo syncs') def complete_softwarechannel_syncrepos(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_syncrepos(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-e', '--no-errata', action='store_true', default=False) arg_parser.add_argument('-f', '--fail', action='store_true', default=False) arg_parser.add_argument('-k', '--sync-kickstart', action='store_true', default=False) arg_parser.add_argument('-l', '--latest', action='store_true', default=False) (args, options) = parse_command_arguments(args, arg_parser) params = dict((i.replace('_', '-'), getattr(options, i)) for i in ['no_errata', 'fail', 'sync_kickstart', 'latest']) if not args: self.help_softwarechannel_syncrepos() return # allow globbing of software channel names channels = filter_results(self.do_softwarechannel_list('', True), args) for channel in channels: logging.debug('Syncing repos for %s' % channel) self.client.channel.software.syncRepo(self.session, channel, params) #################### def help_softwarechannel_setsyncschedule(self): print('softwarechannel_setsyncschedule: ') print('Sets the repo sync schedule for a software channel') print('') print('usage: softwarechannel_setsyncschedule <CHANNEL> <SCHEDULE>') print('Options:') print(' -e/--no-errata : Do not sync errata') print(' -f/--fail : Terminate upon any error') print(' -k/--sync-kickstart : Create kickstartable tree') print(' -l/--latest : Only download latest package versions when repo syncs') print('') print('The schedule is specified in Quartz CronTrigger format without enclosing quotes.') print('For example, to set a schedule of every day at 1am, <SCHEDULE> would be 0 0 1 * * ?') print('If <SCHEDULE> is left empty, it will be disabled.') print('') def complete_softwarechannel_setsyncschedule(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_setsyncschedule(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-e', '--no-errata', action='store_true', default=False) arg_parser.add_argument('-f', '--fail', action='store_true', default=False) arg_parser.add_argument('-k', '--sync-kickstart', action='store_true', default=False) arg_parser.add_argument('-l', '--latest', action='store_true', default=False) # Set glob = false, otherwise this will generate a com.redhat.rhn.taskomatic.InvalidParamException: Cron trigger. (args, options) = parse_command_arguments(args, arg_parser, glob=False) params = dict((i.replace('_', '-'), getattr(options, i)) for i in ['no_errata', 'fail', 'sync_kickstart', 'latest']) if not len(args) in [1, 7]: self.help_softwarechannel_setsyncschedule() return channel = args[0] schedule = ' '.join(args[1:]) if len(args) == 7 else '' self.client.channel.software.syncRepo(self.session, channel, schedule, params) #################### def help_softwarechannel_removesyncschedule(self): print('softwarechannel_removesyncschedule: ') print('Removes the repo sync schedule for a software channel') print('') print('usage: softwarechannel_removesyncschedule <CHANNEL>') def complete_softwarechannel_removesyncschedule(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_removesyncschedule(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args) == 1: self.help_softwarechannel_removesyncschedule() return channel = args[0] self.client.channel.software.syncRepo(self.session, channel, '') #################### def help_softwarechannel_listsyncschedule(self): print('softwarechannel_listsyncschedule: List sync schedules for all software channels') print('usage:') print('softwarechannel_listsyncschedule : List all channels') def do_softwarechannel_listsyncschedule(self, args): # Get a list of all channels and sync schedules channels = self.client.channel.listAllChannels(self.session) schedules = self.client.taskomatic.org.listActiveSchedules(self.session) chan_name = {} chan_sched = {} # Build an array of channel names indexed by internal channel id number for c in channels: chan_name[ c['id'] ] = c['label'] chan_sched[ c['id'] ] = '' # Build an array of schedules indexed by internal channel id number for s in schedules: chan_sched[int(s['data_map']['channel_id'])] = s['cron_expr'] # Print headers csched_fmt = '{0:>5s} {1:<40s} {2:<20s}' print(csched_fmt.format('key', 'Channel Name', 'Update Schedule')) print(csched_fmt.format('-----', '---------------------', '---------------')) # Sort and print(the channel names and associated repo-sync schedule (if any)) for key,value in sorted(chan_name.items()): print(csched_fmt.format(str(key), value, chan_sched[int(key)])) #################### def help_softwarechannel_addrepo(self): print('softwarechannel_addrepo: Add a repo to a software channel') print('usage: softwarechannel_addrepo CHANNEL REPO') def complete_softwarechannel_addrepo(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) elif len(parts) == 3: return tab_completer(self.do_repo_list('', True), text) def do_softwarechannel_addrepo(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_softwarechannel_addrepo() return channel = args[0] repo = args[1] self.client.channel.software.associateRepo(self.session, channel, repo) #################### def help_softwarechannel_removerepo(self): print('softwarechannel_removerepo: Remove a repo from a software channel') print('usage: softwarechannel_removerepo CHANNEL REPO') def complete_softwarechannel_removerepo(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_softwarechannel_list('', True), text) elif len(parts) == 3: try: details = self.client.channel.software.getDetails(self.session, parts[1]) repos = [r.get('label') for r in details.get('contentSources')] except xmlrpclib.Fault: return return tab_completer(repos, text) def do_softwarechannel_removerepo(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_softwarechannel_removerepo() return channel = args[0] repo = args[1] self.client.channel.software.disassociateRepo(self.session, channel, repo) #################### def help_softwarechannel_listrepos(self): print('softwarechannel_listrepos: List the repos for a software channel') print('usage: softwarechannel_listrepos CHANNEL') def complete_softwarechannel_listrepos(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_listrepos(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_listrepos() else: details = self.client.channel.software.getDetails(self.session, args[0]) repos = [r.get('label') for r in details.get('contentSources')] if repos: print('\n'.join(sorted(repos))) else: self.help_softwarechannel_listrepos() #################### def help_softwarechannel_mirrorpackages(self): print('softwarechannel_mirrorpackages: Download packages of a given channel') print('usage: softwarechannel_mirrorpackages CHANNEL') print('Options:') print(' -l/--latest : Only mirror latest package version') def complete_softwarechannel_mirrorpackages(self, text, line, beg, end): return tab_completer(self.do_softwarechannel_list('', True), text) def do_softwarechannel_mirrorpackages(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-l', '--latest', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_softwarechannel_mirrorpackages() return channel = args[0] if not (options.latest): packages = \ self.client.channel.software.listAllPackages(self.session, channel) else: packages = \ self.client.channel.software.listLatestPackages( self.session, channel) for package in packages: package_url = self.client.packages.getPackageUrl( self.session, package['id']) package_file = self.client.packages.getDetails( self.session, package['id']).get('file') if os.path.isfile(package_file): print("Skipping", package_file) else: print("Fetching package", package_file) try: urlretrieve(package_url, package_file) except ContentTooShortError: logging.error( "Received package %s from channel %s is broken. Content is too short", package_file, channel) except IOError: logging.error("Could not fetch package %s from channel %s" % (package_file, channel)) 07070100000029000081B40000000000000000000000015D65A5B2000016D6000000000000000000000000000000000000001D00000000spacecmd/src/spacecmd/ssm.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 from spacecmd.utils import * def help_ssm(self): print('The System Set Manager (SSM) is a group of systems that you ') print('can perform tasks on as a whole.') print('') print('Adding Systems:') print('> ssm_add group:rhel5-x86_64') print('> ssm_add channel:rhel-x86_64-server-5') print('> ssm_add search:device:vmware') print('> ssm_add host.example.com') print('') print('Intersections:') print('> ssm_add group:rhel5-x86_64') print('> ssm_intersect group:web-servers') print('') print('Using the SSM:') print('> system_installpackage ssm zsh') print('> system_runscript ssm') #################### def help_ssm_add(self): print('ssm_add: Add systems to the SSM') print('usage: ssm_add <SYSTEMS>') print('') print("see 'help ssm' for more details") print('') print(self.HELP_SYSTEM_OPTS) def complete_ssm_add(self, text, line, beg, end): return self.tab_complete_systems(text) def do_ssm_add(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_ssm_add() return systems = self.expand_systems(args) if not systems: logging.warning('No systems found') return for system in systems: if system in self.ssm: logging.warning('%s is already in the list' % system) continue else: self.ssm[system] = self.get_system_id(system) logging.debug('Added %s' % system) if self.ssm: logging.debug('Systems Selected: %i' % len(self.ssm)) # save the SSM for use between sessions save_cache(self.ssm_cache_file, self.ssm) #################### def help_ssm_intersect(self): print('ssm_intersect: Replace the current SSM with the intersection') print(' of the current list of systems and the list of') print(' systems passed as arguments') print('usage: ssm_intersect <SYSTEMS>') print('') print("see 'help ssm' for more details") print('') print(self.HELP_SYSTEM_OPTS) def complete_ssm_intersect(self, text, line, beg, end): return self.tab_complete_systems(text) def do_ssm_intersect(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_ssm_intersect() return systems = self.expand_systems(args) if not systems: logging.warning('No systems found') return # tmp_ssm placeholder to gather systems that are both in original ssm # selection and newly selected group tmp_ssm = {} for system in systems: if system in self.ssm: logging.debug('%s is in both groups: leaving in SSM' % system) tmp_ssm[system] = self.ssm[system] # set self.ssm to tmp_ssm, which now holds the intersection self.ssm = tmp_ssm # save the SSM for use between sessions save_cache(self.ssm_cache_file, self.ssm) if self.ssm: logging.debug('Systems Selected: %i' % len(self.ssm)) #################### def help_ssm_remove(self): print('ssm_remove: Remove systems from the SSM') print('usage: ssm_remove <SYSTEMS>') print('') print("see 'help ssm' for more details") print('') print(self.HELP_SYSTEM_OPTS) def complete_ssm_remove(self, text, line, beg, end): return self.tab_complete_systems(text) def do_ssm_remove(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_ssm_remove() return systems = self.expand_systems(args) if not systems: logging.warning('No systems found') return for system in systems: # double-check for existance in case of duplicate names if system in self.ssm: logging.debug('Removed %s' % system) del self.ssm[system] logging.debug('Systems Selected: %i' % len(self.ssm)) # save the SSM for use between sessions save_cache(self.ssm_cache_file, self.ssm) #################### def help_ssm_list(self): print('ssm_list: List the systems currently in the SSM') print('usage: ssm_list') print('') print("see 'help ssm' for more details") def do_ssm_list(self, args): systems = sorted(self.ssm) if systems: print('\n'.join(systems)) logging.debug('Systems Selected: %i' % len(systems)) #################### def help_ssm_clear(self): print('ssm_clear: Remove all systems from the SSM') print('usage: ssm_clear') def do_ssm_clear(self, args): self.ssm = {} # save the SSM for use between sessions save_cache(self.ssm_cache_file, self.ssm) 0707010000002A000081B40000000000000000000000015D65A5B20001D9D1000000000000000000000000000000000000002000000000spacecmd/src/spacecmd/system.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2013--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # unused argument # pylint: disable=W0613 # wildcard import # pylint: disable=W0401,W0614 # invalid function name # pylint: disable=C0103 import shlex try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from operator import itemgetter from xml.parsers.expat import ExpatError from spacecmd.utils import * __PKG_COMPARISONS = {0: 'Same', 1: 'Only here', 2: 'Newer here', 3: 'Only there', 4: 'Newer there'} def print_package_comparison(self, results): max_name = max_length(map(itemgetter('package_name'), results), minimum=7) # sometimes 'this_system' or 'other_system' can be None tmp_this = [] tmp_other = [] for item in results: tmp_this.append(str(item.get('this_system'))) tmp_other.append(str(item.get('other_system'))) max_this = max_length(tmp_this, minimum=11) max_other = max_length(tmp_other, minimum=12) max_comparison = 10 # print(headers) print('%s %s %s %s' % ( 'Package'.ljust(max_name), 'This System'.ljust(max_this), 'Other System'.ljust(max_other), 'Difference'.ljust(max_comparison))) print('%s %s %s %s' % ( '-' * max_name, '-' * max_this, '-' * max_other, '-' * max_comparison)) for item in results: # don't show packages that are the same if item.get('comparison') == 0: continue print('%s %s %s %s' % ( item.get('package_name').ljust(max_name), str(item.get('this_system')).ljust(max_this), str(item.get('other_system')).ljust(max_other), __PKG_COMPARISONS[item.get('comparison')])) #################### def manipulate_child_channels(self, args, remove=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: if remove: self.help_system_removechildchannels() else: self.help_system_addchildchannels() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() args.pop(0) else: systems = self.expand_systems(args.pop(0)) new_channels = args print('Systems') print('-------') print('\n'.join(sorted(["%s" % x for x in systems]))) print('') if remove: print('Removing Channels') print('-----------------') else: print('Adding Channels') print('---------------') print('\n'.join(sorted(new_channels))) if not self.user_confirm(): return for system in systems: system_id = self.get_system_id(system) if not system_id: continue child_channels = \ self.client.system.listSubscribedChildChannels(self.session, system_id) child_channels = [c.get('label') for c in child_channels] if remove: for channel in new_channels: if channel in child_channels: child_channels.remove(channel) else: for channel in new_channels: if channel not in child_channels: child_channels.append(channel) self.client.system.setChildChannels(self.session, system_id, child_channels) #################### def help_system_list(self): print('system_list: List all system profiles') print('usage: system_list') def do_system_list(self, args, doreturn=False): if doreturn: return self.get_system_names() else: if self.get_system_names(): print('\n'.join(sorted(['%s : %s' % (v, k) for k, v in self.get_system_names_ids().items()]))) #################### def help_system_reboot(self): print('system_reboot: Reboot a system') print('''usage: system_reboot <SYSTEMS> [options]) options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_reboot(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_reboot(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_reboot() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) print('') print('Start Time: %s' % options.start_time) print('') print('Systems') print('-------') print('\n'.join(sorted(systems))) if not self.user_confirm('Reboot these systems [y/N]:'): return for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.scheduleReboot(self.session, system_id, options.start_time) #################### def help_system_search(self): print('system_search: List systems that match the given criteria') print('usage: system_search QUERY') print('') print('Available Fields:') print('\n'.join(self.SYSTEM_SEARCH_FIELDS)) print('') print('Examples:') print('> system_search device:vmware') print('> system_search ip:192.168.82') def do_system_search(self, args, doreturn=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_system_search() return query = args[0] if re.search(':', query): try: (field, value) = query.split(':') except ValueError: logging.error('Invalid query') return [] else: field = 'name' value = query if not value: logging.warning('Invalid query') return [] results = [] if field == 'name': results = self.client.system.search.nameAndDescription(self.session, value) key = 'name' elif field == 'id': # build an array of key/value pairs from our local system cache self.generate_system_cache() results = [{'id': k, 'name': self.all_systems[k]} for k in self.all_systems] key = 'id' elif field == 'ip': results = self.client.system.search.ip(self.session, value) key = 'ip' elif field == 'hostname': results = self.client.system.search.hostname(self.session, value) key = 'hostname' elif field == 'device': results = self.client.system.search.deviceDescription(self.session, value) key = 'hw_description' elif field == 'vendor': results = self.client.system.search.deviceVendorId(self.session, value) key = 'hw_vendor_id' elif field == 'driver': results = self.client.system.search.deviceDriver(self.session, value) key = 'hw_driver' elif field == 'uuid': results = self.client.system.search.uuid(self.session, value) key = 'uuid' else: logging.warning('Invalid search field') return [] systems = [] max_size = 0 for s in results: # only use real matches, not the fuzzy ones we get back if re.search(value, "%s" % s.get(key), re.I): if len(s.get('name')) > max_size: max_size = len(s.get('name')) systems.append((s.get('name'), s.get(key), s.get('id'))) if doreturn: return [s[2] for s in systems] else: if systems: for s in sorted(systems): if key == 'name': print(s[0]) else: print('%s %s' % (s[0].ljust(max_size), str(s[1]).strip())) #################### def help_system_runscript(self): print('system_runscript: Schedule a script to run on the list of') print(' systems provided') print('''usage: system_runscript <SYSTEMS> [options]) options: -u USER -g GROUP -t TIMEOUT -s START_TIME -l LABEL -f FILE''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_runscript(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_runscript(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-u', '--user') arg_parser.add_argument('-g', '--group') arg_parser.add_argument('-t', '--timeout') arg_parser.add_argument('-s', '--start-time') arg_parser.add_argument('-l', '--label') arg_parser.add_argument('-f', '--file') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_runscript() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) if not systems: logging.warning('No systems selected') return if is_interactive(options): options.user = prompt_user('User [root]:') options.group = prompt_user('Group [root]:') # defaults if not options.user: options.user = 'root' if not options.group: options.group = 'root' try: options.timeout = prompt_user('Timeout (in seconds) [600]:') if options.timeout: options.timeout = int(options.timeout) else: options.timeout = 600 except ValueError: logging.error('Invalid timeout') return options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) options.label = prompt_user('Label/Short Description [default]:') if options.label == "": options.label = None options.file = prompt_user('Script File [create]:') # read the script provided by the user if options.file: keep_script_file = True script_contents = read_file(os.path.abspath(options.file)) else: # have the user write their script (script_contents, options.file) = editor('#!/bin/bash') keep_script_file = False if not script_contents: logging.error('No script provided') return else: if not options.user: options.user = 'root' if not options.group: options.group = 'root' if not options.label: options.label = None if not options.timeout: options.timeout = 600 else: options.timeout = int(options.timeout) if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) if not options.file: logging.error('A script file is required') return script_contents = read_file(options.file) keep_script_file = True # display a summary print('') print('User: %s' % options.user) print('Group: %s' % options.group) print('Timeout: %i seconds' % options.timeout) print('Start Time: %s' % options.start_time) print('') if options.label: print('Label: %s' % options.label) print('Script Contents') print('---------------') print(script_contents) print('Systems') print('-------') print('\n'.join(sorted(systems))) # have the user confirm if not self.user_confirm(): return scheduled = 0 if self.check_api_version('10.11'): logging.debug('Scheduling all systems for the same action') # schedule all systems for the same action system_ids = [self.get_system_id(s) for s in systems] if not options.label: action_id = self.client.system.scheduleScriptRun(self.session, system_ids, options.user, options.group, options.timeout, script_contents, options.start_time) else: action_id = self.client.system.scheduleScriptRun(self.session, options.label, system_ids, options.user, options.group, options.timeout, script_contents, options.start_time) logging.info('Action ID: %i' % action_id) scheduled = len(system_ids) else: # older versions of the API require each system to be # scheduled individually for system in systems: system_id = self.get_system_id(system) if not system_id: continue try: action_id = \ self.client.system.scheduleScriptRun(self.session, system_id, options.user, options.group, options.timeout, script_contents, options.start_time) logging.info('Action ID: %i' % action_id) scheduled += 1 except xmlrpclib.Fault as detail: logging.debug(detail) logging.error('Failed to schedule %s' % system) logging.info('Scheduled: %i system(s)' % scheduled) # don't delete a pre-existing script that the user provided if not keep_script_file: try: os.remove(options.file) except OSError: logging.error('Could not remove %s' % options.file) #################### def help_system_listhardware(self): print('system_listhardware: List the hardware details of a system') print('usage: system_listhardware <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listhardware(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listhardware(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listhardware() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue cpu = self.client.system.getCpu(self.session, system_id) memory = self.client.system.getMemory(self.session, system_id) devices = self.client.system.getDevices(self.session, system_id) network = self.client.system.getNetworkDevices(self.session, system_id) # Solaris systems don't have these value s for v in ('cache', 'vendor', 'family', 'stepping'): if not cpu.get(v): cpu[v] = '' try: dmi = self.client.system.getDmi(self.session, system_id) except ExpatError: dmi = None if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) print('') if network: print('Network') print('-------') count = 0 for device in network: if count: print('') count += 1 print('Interface: %s' % device.get('interface')) print('MAC Address: %s' % device.get('hardware_address').upper()) print('IP Address: %s' % device.get('ip')) print('Netmask: %s' % device.get('netmask')) print('Broadcast: %s' % device.get('broadcast')) print('Module: %s' % device.get('module')) print('') print('CPU') print('---') print('Count: %i' % cpu.get('count')) print('Arch: %s' % cpu.get('arch')) print('MHz: %s' % cpu.get('mhz')) print('Cache: %s' % cpu.get('cache')) print('Vendor: %s' % cpu.get('vendor')) print('Model: %s' % re.sub(r'\s+', ' ', cpu.get('model'))) print('') print('Memory') print('------') print('RAM: %i' % memory.get('ram')) print('Swap: %i' % memory.get('swap')) if dmi: print('') print('DMI') print('Vendor: %s' % dmi.get('vendor')) print('System: %s' % dmi.get('system')) print('Product: %s' % dmi.get('product')) print('Board: %s' % dmi.get('board')) print('') print('Asset') print('-----') for asset in dmi.get('asset').split(') ('): print(re.sub(r'\)|\(', '', asset)) print('') print('BIOS Release: %s' % dmi.get('bios_release')) print('BIOS Vendor: %s' % dmi.get('bios_vendor')) print('BIOS Version: %s' % dmi.get('bios_version')) if devices: print('') print('Devices') print('-------') count = 0 for device in devices: if count: print('') count += 1 if device.get('description') is None: print('Description: None') else: print('Description: %s' % ( wrap(device.get('description'), 60)[0])) print('Driver: %s' % device.get('driver')) print('Class: %s' % device.get('device_class')) print('Bus: %s' % device.get('bus')) #################### def help_system_installpackage(self): print('system_installpackage: Install a package on a system') print('''usage: system_installpackage <SYSTEMS> <PACKAGE ...> [options]) options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_installpackage(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return tab_completer(self.get_package_names(), text) def do_system_installpackage(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_installpackage() return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() # remove 'ssm' from the argument list args.pop(0) else: systems = self.expand_systems(args.pop(0)) packages_to_install = args # get the ID for each system system_ids = [] for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue system_ids.append(system_id) jobs = {} if self.check_api_version('10.11'): for package in packages_to_install: logging.debug('Finding the latest version of %s' % package) avail_packages = \ self.client.system.listLatestAvailablePackage(self.session, system_ids, package) for system in avail_packages: system_id = system.get('id') if system_id not in jobs: jobs[system_id] = [] # add this package to the system's queue jobs[system_id].append(system.get('package').get('id')) else: # XXX: Satellite 5.3 compatibility for system_id in system_ids: logging.debug('Getting available packages for %s' % self.get_system_name(system_id)) avail_packages = \ self.client.system.listLatestInstallablePackages(self.session, system_id) for package in avail_packages: if package.get('name') in packages_to_install: if system_id not in jobs: jobs[system_id] = [] jobs[system_id].append(package.get('id')) if not jobs: logging.warning('No packages to install') return add_separator = False warnings = [] for system_id in jobs: if add_separator: print(self.SEPARATOR) add_separator = True # warn the user if the request can not be 100% fulfilled if len(jobs[system_id]) != len(packages_to_install): # stash the warnings and show at the end so the user can see them warnings.append(system_id) print('%s:' % self.get_system_name(system_id)) for package_id in jobs[system_id]: print(self.get_package_name(package_id)) # show the warnings to the user if warnings: print('') for system_id in warnings: logging.warning('%s does not have access to all requested packages' % self.get_system_name(system_id)) print('') print('Start Time: %s' % options.start_time) if not self.user_confirm('Install these packages [y/N]:'): return scheduled = 0 for system_id in jobs: try: self.client.system.schedulePackageInstall(self.session, system_id, jobs[system_id], options.start_time) scheduled += 1 except xmlrpclib.Fault: logging.error('Failed to schedule %s' % self.get_system_name(system_id)) logging.info('Scheduled %i system(s)' % scheduled) #################### def help_system_removepackage(self): print('system_removepackage: Remove a package from a system') print('''usage: system_removepackage <SYSTEMS> <PACKAGE ...> [options]) options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_removepackage(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return tab_completer(self.get_package_names(), text) def do_system_removepackage(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_removepackage() return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() # remove 'ssm' from the argument list args.pop(0) else: systems = self.expand_systems(args.pop(0)) package_list = args # get all matching package names logging.debug('Finding matching packages') matching_packages = \ filter_results(self.get_package_names(True), package_list, True) jobs = {} for package_name in matching_packages: logging.debug('Finding systems with %s' % package_name) installed_systems = {} for package_id in self.get_package_id(package_name): for system in self.client.system.listSystemsWithPackage(self.session, package_id): installed_systems[system.get('name')] = package_id # each system has a list of packages to remove so that only one # API call needs to be made to schedule all the package removals # for each system for system in systems: if system in installed_systems.keys(): if system not in jobs: jobs[system] = [] jobs[system].append(installed_systems[system]) add_separator = False for system in jobs: if add_separator: print(self.SEPARATOR) add_separator = True print('%s:' % system) for package in jobs[system]: print(self.get_package_name(package)) if not jobs: return print('') print('Start Time: %s' % options.start_time) if not self.user_confirm('Remove these packages [y/N]:'): return scheduled = 0 for system in jobs: system_id = self.get_system_id(system) if not system_id: continue try: action_id = self.client.system.schedulePackageRemove(self.session, system_id, jobs[system], options.start_time) logging.info('Action ID: %i' % action_id) scheduled += 1 except xmlrpclib.Fault: logging.error('Failed to schedule %s' % system) logging.info('Scheduled %i system(s)' % scheduled) #################### def help_system_upgradepackage(self): print('system_upgradepackage: Upgrade a package on a system') print('''usage: system_upgradepackage <SYSTEMS> <PACKAGE ...>|* [options]') options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_upgradepackage(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return tab_completer(self.get_package_names(), text) def do_system_upgradepackage(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') # this will come handy for individual packages, as we call # self.do_system_installpackage anyway orig_args = args (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_upgradepackage() return # install and upgrade for individual packages are the same if not '.*' in args[1:]: return self.do_system_installpackage(orig_args) # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() # remove 'ssm' from the argument list args.pop(0) else: systems = self.expand_systems(args.pop(0)) # make a dictionary of each system and the package IDs to install jobs = {} for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue packages = \ self.client.system.listLatestUpgradablePackages(self.session, system_id) if packages: package_ids = [p.get('to_package_id') for p in packages] jobs[system] = package_ids else: logging.warning('No upgrades available for %s' % system) if not jobs: return add_separator = False for system in jobs: if add_separator: print(self.SEPARATOR) add_separator = True print(system) print('-' * len(system)) # build a temporary list so we can sort by package name package_names = [] for package in jobs[system]: name = self.get_package_name(package) if name: package_names.append(name) else: logging.error("Couldn't get name for package %i" % package) print('\n'.join(sorted(package_names))) print('') print('Start Time: %s' % options.start_time) if not self.user_confirm('Upgrade these packages [y/N]:'): return scheduled = 0 for system in jobs: system_id = self.get_system_id(system) try: self.client.system.schedulePackageInstall(self.session, system_id, jobs[system], options.start_time) scheduled += 1 except xmlrpclib.Fault: logging.error('Failed to schedule %s' % system) logging.info('Scheduled %i system(s)' % scheduled) #################### def help_system_listupgrades(self): print('system_listupgrades: List the available upgrades for a system') print('usage: system_listupgrades <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listupgrades(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listupgrades(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listupgrades() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue packages = \ self.client.system.listLatestUpgradablePackages(self.session, system_id) if not packages: logging.warning('No upgrades available for %s' % system) continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print(system) print('-' * len(system)) latest_packages = filter_latest_packages(packages, 'to_version', 'to_release', 'to_epoch') for package in sorted(latest_packages.values(), key=itemgetter('name')): print(build_package_names({ 'name': package['name'], 'version': package['to_version'], 'release': package['to_release'], 'epoch': package['to_epoch'], 'arch': package['to_arch'] })) #################### def help_system_listinstalledpackages(self): print('system_listinstalledpackages: List the installed packages on a') print(' system') print('usage: system_listinstalledpackages <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listinstalledpackages(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listinstalledpackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listinstalledpackages() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue packages = self.client.system.listPackages(self.session, system_id) if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) print('') print('\n'.join(build_package_names(packages))) #################### def help_system_listconfigchannels(self): print('system_listconfigchannels: List the config channels of a system') print('usage: system_listconfigchannels <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listconfigchannels(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listconfigchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listconfigchannels() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) try: channels = self.client.system.config.listChannels(self.session, system_id) except xmlrpclib.Fault: logging.warning('%s does not support configuration channels' % system) continue print('\n'.join([c.get('label') for c in channels])) #################### def print_configfiles(self, quiet, filelist): # Figure out correct indentation to allow pretty table output max_path = max_length([f['path'] for f in filelist], minimum=10) max_type = max_length(["file", "directory", "symlink"], minimum=10) max_label = max_length([f['channel_label'] for f in filelist], minimum=15) # print(header when not in quiet mode) if not quiet: print('%s %s %s' % ( 'path'.ljust(max_path), 'type'.ljust(max_type), 'label/type'.ljust(max_label))) print('%s %s %s' % ( '-' * max_path, '-' * max_type, '-' * max_label)) for f in filelist: print('%s %s %s' % (f['path'].ljust(max_path), f['type'].ljust(max_type), f['channel_label'].ljust(max_label))) def help_system_listconfigfiles(self): print('system_listconfigfiles: List the managed config files of a system') print('''usage: system_listconfigfiles <SYSTEMS>') options: -s/--sandbox : list only system-sandbox files -l/--local : list only locally managed files -c/--central : list only centrally managed files -q/--quiet : quiet mode (omits the header)''') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listconfigfiles(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listconfigfiles(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--sandbox', action='store_true') arg_parser.add_argument('-l', '--local', action='store_true') arg_parser.add_argument('-c', '--central', action='store_true') arg_parser.add_argument('-q', '--quiet', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if not options.sandbox and not options.local and not options.central: logging.debug("No sandbox/local/central option specified, listing ALL") options.sandbox = True options.local = True options.central = True if not args: self.help_system_listconfigfiles() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) try: # Pass 0 for system-sandbox files # Pass 1 for locally managed or centrally managed files = self.client.system.config.listFiles(self.session, system_id, 0) files += self.client.system.config.listFiles(self.session, system_id, 1) except xmlrpclib.Fault: logging.warning('%s does not support configuration channels' % system) continue # For system sandbox or locally managed files, there is no # channel_label so we add a descriptive label for these files toprint = [] for f in files: if f['channel_type']['label'] == 'server_import': f['channel_label'] = "system_sandbox" if options.sandbox: toprint.append(f) elif f['channel_type']['label'] == 'local_override': f['channel_label'] = "locally_managed" if options.local: toprint.append(f) elif f['channel_type']['label'] == 'normal': if options.central: toprint.append(f) else: logging.error("Error, unexpected channel type label %s" % f['channel_type']['label']) return self.print_configfiles(options.quiet, toprint) #################### def help_system_addconfigfile(self): print('system_addconfigfile: Create a configuration file') print('Note this is only for system sandbox or locally-managed files') print('Centrally managed files should be created via configchannel_addfile') print('''usage: system_addconfigfile [SYSTEM] [options] options: -S/--sandbox : list only system-sandbox files -L/--local : list only locally managed files -p PATH -r REVISION -o OWNER [default: root] -g GROUP [default: root] -m MODE [defualt: 0644] -x SELINUX_CONTEXT -d path is a directory -s path is a symlink -b path is a binary (or other file which needs base64 encoding) -t SYMLINK_TARGET -f local path to file contents Note re binary/base64: Some text files, notably those containing trailing newlines, those containing ASCII escape characters (or other charaters not allowed in XML) need to be sent as binary (-b). Some effort is made to auto- detect files which require this, but you may need to explicitly specify. ''') def complete_system_addconfigfile(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_addconfigfile(self, args, update_path=''): arg_parser = get_argument_parser() arg_parser.add_argument('-S', '--sandbox', action='store_true') arg_parser.add_argument('-L', '--local', action='store_true') arg_parser.add_argument('-p', '--path') arg_parser.add_argument('-o', '--owner') arg_parser.add_argument('-g', '--group') arg_parser.add_argument('-m', '--mode') arg_parser.add_argument('-x', '--selinux-ctx') arg_parser.add_argument('-t', '--target-path') arg_parser.add_argument('-f', '--file') arg_parser.add_argument('-r', '--revision') arg_parser.add_argument('-s', '--symlink', action='store_true') arg_parser.add_argument('-b', '--binary', action='store_true') arg_parser.add_argument('-d', '--directory', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) file_info = None # the system name can be passed in if args: options.system = args[0] interactive = is_interactive(options) if interactive: if not options.system: while True: print('Systems') print('----------------------') print('\n'.join(sorted(self.do_system_list('', True)))) print('') options.system = prompt_user('Select:', noblank=True) # ensure the user enters a valid system if options.system in self.do_system_list('', True): break else: print('') logging.warning('%s is not a valid system' % options.system) print('') if update_path: options.path = update_path else: options.path = prompt_user('Path:', noblank=True) while not options.local and not options.sandbox: answer = prompt_user('System-Sandbox or Locally-Managed? [S/L]:') if re.match('L', answer, re.I): options.local = True localopt = 1 elif re.match('S', answer, re.I): options.sandbox = True localopt = 0 # Set the int variable (required by the API calls) for sandbox/local localopt = 0 if options.local: logging.debug("Selected locally-managed") localopt = 1 elif options.sandbox: logging.debug("Selected system-sandbox") else: logging.error("Must choose system-sandbox or locally-managed option") self.help_system_addconfigfile() return if not options.system: logging.error("Must provide system") self.help_system_addconfigfile() return system_id = self.get_system_id(options.system) logging.debug("Got ID %s for system %s" % (system_id, options.system)) # check if this file already exists try: file_info = self.client.system.config.lookupFileInfo(self.session, system_id, [options.path], localopt) if file_info: logging.debug("Found existing file_info %s" % file_info) except xmlrpclib.Fault: logging.debug("No existing file information found for %s" % options.path) file_info = self.configfile_getinfo(args, options, file_info, interactive) if self.user_confirm(): if options.symlink: self.client.system.config.createOrUpdateSymlink(self.session, system_id, options.path, file_info, localopt) else: self.client.system.config.createOrUpdatePath(self.session, system_id, options.path, options.directory, file_info, localopt) #################### def help_system_addconfigchannels(self): print('system_addconfigchannels: Add config channels to a system') print('''usage: system_addconfigchannels <SYSTEMS> <CHANNEL ...> [options] options: -t add channels to the top of the list -b add channels to the bottom of the list''') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_addconfigchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return tab_completer(self.do_configchannel_list('', True), text) def do_system_addconfigchannels(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-t', '--top', action='store_true') arg_parser.add_argument('-b', '--bottom', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_addconfigchannels() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() args.pop(0) else: systems = self.expand_systems(args.pop(0)) channels = args if is_interactive(options): answer = prompt_user('Add to top or bottom? [T/b]:') if re.match('b', answer, re.I): options.top = False else: options.top = True else: if options.bottom: options.top = False else: options.top = True system_ids = [self.get_system_id(s) for s in systems] self.client.system.config.addChannels(self.session, system_ids, channels, options.top) #################### def help_system_removeconfigchannels(self): print('system_removeconfigchannels: Remove config channels from a system') print('usage: system_removeconfigchannels <SYSTEMS> <CHANNEL ...>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_removeconfigchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return tab_completer(self.do_configchannel_list('', True), text) def do_system_removeconfigchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_removeconfigchannels() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() args.pop(0) else: systems = self.expand_systems(args.pop(0)) channels = args system_ids = [self.get_system_id(s) for s in systems] self.client.system.config.removeChannels(self.session, system_ids, channels) #################### def help_system_setconfigchannelorder(self): print('system_setconfigchannelorder: Set the ranked order of configuration channels') print('usage: system_setconfigchannelorder <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_setconfigchannelorder(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_setconfigchannelorder(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_setconfigchannelorder() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args.pop(0)) # get the current configuration channels from the first system # in the list system_id = self.get_system_id(systems[0]) new_channels = self.client.system.config.listChannels(self.session, system_id) new_channels = [c.get('label') for c in new_channels] # call an interface for the user to make selections all_channels = self.do_configchannel_list('', True) new_channels = config_channel_order(all_channels, new_channels) print('') print('New Configuration Channels') print('--------------------------') for i, new_channel in enumerate(new_channels, 1): print('[%i] %s' % (i, new_channel)) if not self.user_confirm(): return system_ids = [self.get_system_id(s) for s in systems] self.client.system.config.setChannels(self.session, system_ids, new_channels) #################### def help_system_deployconfigfiles(self): print('system_deployconfigfiles: Deploy all configuration files for a system') print('''usage: system_deployconfigfiles <SYSTEMS> [options] options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_deployconfigfiles(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_deployconfigfiles(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_deployconfigfiles() return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) if not systems: return print('') print('Start Time: %s' % options.start_time) print('') print('Systems') print('-------') print('\n'.join(sorted(systems))) message = 'Deploy ALL configuration files to these systems [y/N]:' if not self.user_confirm(message): return system_ids = [self.get_system_id(s) for s in systems] self.client.system.config.deployAll(self.session, system_ids, options.start_time) logging.info('Scheduled deployment for %i system(s)' % len(system_ids)) #################### def help_system_delete(self): print('system_delete: Delete a system profile') print('''usage: system_delete [options] <SYSTEMS> options: -c TYPE - Possible values: * 'FAIL_ON_CLEANUP_ERR' - fail in case of cleanup error, * 'NO_CLEANUP' - do not cleanup, just delete, * 'FORCE_DELETE' - Try cleanup first but delete server anyway in case of error ''') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_delete(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_delete(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-c', '--cleanuptype', default='NO_CLEANUP', choices=['FAIL_ON_CLEANUP_ERR', 'NO_CLEANUP', 'FORCE_DELETE']) (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_delete() return system_ids = [] # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) # get the system ID for each system for system in systems: system_id = self.get_system_id(system) if not system_id: continue system_ids.append(system_id) if not system_ids: logging.warning('No systems to delete') return # make the column the right size colsize = max_length([self.get_system_name(s) for s in system_ids]) if colsize < 7: colsize = 7 print('%s System ID' % 'Profile'.ljust(colsize)) print('%s ---------' % ('-' * colsize)) # print(a summary for the user) for system_id in system_ids: print('%s %i' % (self.get_system_name(system_id).ljust(colsize), system_id)) if not self.user_confirm('Delete these systems [y/N]:'): return self.client.system.deleteSystems(self.session, system_ids, options.cleanuptype) logging.info('%i system(s) scheduled for removal', len(system_ids)) # regenerate the system name cache self.generate_system_cache(True, delay=1) # remove these systems from the SSM and update the cache all(self.ssm.pop(system_name) for system_name in list(systems)) save_cache(self.ssm_cache_file, self.ssm) #################### def help_system_lock(self): print('system_lock: Lock a system') print('usage: system_lock <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_lock(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_lock(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_lock() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue self.client.system.setLockStatus(self.session, system_id, True) #################### def help_system_unlock(self): print('system_unlock: Unlock a system') print('usage: system_unlock <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_unlock(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_unlock(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_unlock() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue self.client.system.setLockStatus(self.session, system_id, False) #################### def help_system_rename(self): print('system_rename: Rename a system profile') print('usage: system_rename OLDNAME NEWNAME') def complete_system_rename(self, text, line, beg, end): if len(line.split(' ')) == 2: return tab_completer(self.get_system_names(), text) def do_system_rename(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_system_rename() return (old_name, new_name) = args system_id = self.get_system_id(old_name) if not system_id: return print('%s (%s) -> %s' % (old_name, system_id, new_name)) if not self.user_confirm(): return self.client.system.setProfileName(self.session, system_id, new_name) # regenerate the cache of systems self.generate_system_cache(True) # update the SSM if old_name in self.ssm: self.ssm.remove(old_name) self.ssm.append(new_name) #################### def help_system_listcustomvalues(self): print('system_listcustomvalues: List the custom values for a system') print('usage: system_listcustomvalues <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listcustomvalues(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listcustomvalues(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listcustomvalues() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) add_separator = False for system in systems: if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) print('') system_id = self.get_system_id(system) if not system_id: continue values = self.client.system.getCustomValues(self.session, system_id) for v in values: print('%s = %s' % (v, values[v])) #################### def help_system_addcustomvalue(self): print('system_addcustomvalue: Set a custom value for a system') print('usage: system_addcustomvalue KEY VALUE <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_addcustomvalue(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_custominfo_listkeys('', True), text) elif len(parts) >= 4: return self.tab_complete_systems(text) def do_system_addcustomvalue(self, args): if not isinstance(args, list): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 3: self.help_system_addcustomvalue() return key = args[0] value = args[1] # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args[2:]) for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.setCustomValues(self.session, system_id, {key: value}) #################### def help_system_updatecustomvalue(self): print('system_updatecustomvalue: Update a custom value for a system') print('usage: system_updatecustomvalue KEY VALUE <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_updatecustomvalue(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_custominfo_listkeys('', True), text) elif len(parts) >= 4: return self.tab_complete_systems(text) def do_system_updatecustomvalue(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 3: self.help_system_updatecustomvalue() return return self.do_system_addcustomvalue(args) #################### def help_system_removecustomvalues(self): print('system_removecustomvalues: Remove a custom value for a system') print('usage: system_removecustomvalues <SYSTEMS> <KEY ...>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_removecustomvalues(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) == 3: return tab_completer(self.do_custominfo_listkeys('', True), text) def do_system_removecustomvalues(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_removecustomvalues() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) keys = args[1:] if not self.user_confirm('Delete these values [y/N]:'): return for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.deleteCustomValues(self.session, system_id, keys) #################### def help_system_addnote(self): print('system_addnote: Set a note for a system') print('''usage: system_addnote <SYSTEM> [options] options: -s SUBJECT -b BODY''') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_addnote(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_addnote(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--subject') arg_parser.add_argument('-b', '--body') (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 1: self.help_system_addnote() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) if is_interactive(options): options.subject = prompt_user('Subject of the Note:', noblank=True) message = 'Note Body (ctrl-D to finish):' options.body = prompt_user(message, noblank=True, multiline=True) else: if not options.subject: logging.error('A subject is required') return if not options.body: logging.error('A body is required') return for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.addNote(self.session, system_id, options.subject, options.body) #################### def help_system_deletenotes(self): print('system_deletenotes: Delete notes from a system') print('usage: system_deletenotes <SYSTEM> <ID|*>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_deletenotes(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_deletenotes(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listnotes() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() args.pop(0) else: systems = self.expand_systems(args.pop(0)) note_ids = args if not args: logging.warning('No notes to delete') return for system in systems: system_id = self.get_system_id(system) if not system_id: continue if '.*' in note_ids: self.client.system.deleteNotes(self.session, system_id) else: for note_id in note_ids: try: note_id = int(note_id) except ValueError: logging.warning('%s is not a valid note ID' % note_id) continue # deleteNote does not throw an exception self.client.system.deleteNote(self.session, system_id, note_id) #################### def help_system_listnotes(self): print('system_listnotes: List the available notes for a system') print('usage: system_listnotes <SYSTEM>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listnotes(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listnotes(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listnotes() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) add_separator = False for system in sorted(systems): if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) print('') system_id = self.get_system_id(system) if not system_id: continue notes = self.client.system.listNotes(self.session, system_id) for n in notes: print('%d. %s (%s)' % (n['id'], n['subject'], n['creator'])) print(n['note']) print('') #################### #################### def help_system_listfqdns(self): print('system_listfqdns: List the associated FQDNs for a system') print('usage: system_listfqdns <SYSTEM>') print(self.HELP_SYSTEM_OPTS) def complete_system_listfqdns(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listfqdns(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not len(args): self.help_system_listfqdns() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) add_separator = False for system in sorted(systems): if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) print('') system_id = self.get_system_id(system) if not system_id: continue fqdns = self.client.system.listFqdns(self.session, system_id) for f in fqdns: print(f) #################### def help_system_setbasechannel(self): print("system_setbasechannel: Set a system's base software channel") print('usage: system_setbasechannel <SYSTEMS> CHANNEL') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_setbasechannel(self, text, line, beg, end): if len(line.split(' ')) == 2: return self.tab_complete_systems(text) elif len(line.split(' ')) == 3: return tab_completer(self.list_base_channels(), text) def do_system_setbasechannel(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_system_setbasechannel() return new_channel = args.pop() # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) add_separator = False for system in systems: system_id = self.get_system_id(system) if not system_id: continue old = self.client.system.getSubscribedBaseChannel(self.session, system_id) if add_separator: print(self.SEPARATOR) add_separator = True print('System: %s' % system) print('Old Base Channel: %s' % old.get('label')) print('New Base Channel: %s' % new_channel) if not self.user_confirm(): return for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.setBaseChannel(self.session, system_id, new_channel) #################### def help_system_schedulechangechannels(self): print("system_schedulechangechannels: Schedule changing a system's software channels") print('''usage: system_setbasechannel <SYSTEMS> [options] options: -b BASE_CHANNEL base channel label -c CHILD_CHANNEL child channel labels (allowed multiple times) -s START_TIME time defaults to now''') print(self.HELP_SYSTEM_OPTS) def complete_system_schedulechangechannels(self, text, line, beg, end): if len(line.split(' ')) == 2: return self.tab_complete_systems(text) def do_system_schedulechangechannels(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-b', '--base') arg_parser.add_argument('-c', '--child', action='append', default=[]) arg_parser.add_argument('-s', '--start-time', action='store') (args, options) = parse_command_arguments(args, arg_parser) # import pdb; # pdb.set_trace() if len(args) < 1: self.help_system_schedulechangechannels() return if not options.base: logging.error('A base channel is required') return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) baseChannel = options.base childChannels = options.child or [] add_separator = False for system in systems: system_id = self.get_system_id(system) if not system_id: continue oldBase = self.client.system.getSubscribedBaseChannel(self.session, system_id) oldKids = self.client.system.listSubscribedChildChannels(self.session, system_id) if add_separator: print(self.SEPARATOR) add_separator = True print('System: %s' % system) print('Old Base Channel: %s' % oldBase.get('label')) print('Old Child Channels: %s' % ', '.join([k.get('label') for k in oldKids])) print('New Base Channel: %s' % baseChannel) print('New Child channels %s' % ', '.join(childChannels)) if not self.user_confirm(): return for system in systems: system_id = self.get_system_id(system) if not system_id: continue actionId = self.client.system.scheduleChangeChannels(self.session, system_id, baseChannel, childChannels, options.start_time) print('Scheduled action id: %s' % actionId) #################### def help_system_listbasechannel(self): print('system_listbasechannel: List the base channel for a system') print('usage: system_listbasechannel <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listbasechannel(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listbasechannel(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listbasechannel() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) channel = \ self.client.system.getSubscribedBaseChannel(self.session, system_id) print(channel.get('label')) #################### def help_system_listchildchannels(self): print('system_listchildchannels: List the child channels for a system') print('usage: system_listchildchannels <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listchildchannels(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listchildchannels(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listchildchannels() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) channels = \ self.client.system.listSubscribedChildChannels(self.session, system_id) print('\n'.join(sorted([c.get('label') for c in channels]))) #################### def help_system_addchildchannels(self): print("system_addchildchannels: Add child channels to a system") print('usage: system_addchildchannels <SYSTEMS> <CHANNEL ...>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_addchildchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return tab_completer(self.list_child_channels(), text) def do_system_addchildchannels(self, args): self.manipulate_child_channels(args) #################### def help_system_listcrashedsystems(self): print("system_listcrashedsystems: List all systems that have experienced a crash and reported by spacewalk-abrt") print('usage: system_listcrashedsystems') print('') def do_system_listcrashedsystems(self, args): print('') print('Count | System ID | Profile Name') print('--------------------------------') res = self.client.system.listUserSystems(self.session) for s in res: res_crash = self.client.system.crash.listSystemCrashes(self.session, s['id']) if res_crash: print("%d : %s : %s" % (len(res_crash), s['id'], s['name'])) ###### def help_system_deletecrashes(self): print('system_deletecrashes: Delete crashes reported by spacewalk-abrt.') print('usage: Delete all crashes for all systems : system_deletecrashes [--verbose]') print('usage: Delete all crashes for a single system: system_deletecrashes -i sys_id [--verbose]') print('usage: Delete a single crash record : system_deletecrashes -c crash_id [--verbose]') print('') def print_msg(string_msg, flag_verbose): if flag_verbose: print(string_msg) def do_system_deletecrashes(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-i', '--sysid') arg_parser.add_argument('-c', '--crashid') arg_parser.add_argument('-v', '--verbose', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if options.crashid: print_msg("Deleting crash with id %s." % options.crashid, options.verbose) self.client.system.crash.deleteCrash(self.session, int(options.crashid)) return sys_id = [] if options.sysid: sys_id.append(options.sysid) prompt_string = "Deleting all crashes from system with systemid %s [y/N]:" % options.sysid else: # all systems prompt_string = 'Deleting all crashes from all systems [y/N]:' systems = self.client.system.listUserSystems(self.session) for s in systems: sys_id.append(s['id']) confirm = prompt_user(prompt_string) if re.match('n', confirm, re.I): return for s_id in sys_id: list_crash = self.client.system.crash.listSystemCrashes(self.session, int(s_id)) for crash in list_crash: print_msg("Deleting crash with id %s from system %s." % (crash['id'], s_id), options.verbose) self.client.system.crash.deleteCrash(self.session, int(crash['id'])) ####### def help_system_listcrashesbysystem(self): print('system_listcrashesbysystem: List all reported crashes for a system.') print('usage: system_listcrashesbysystem -i sys_id') print('') def do_system_listcrashesbysystem(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-i', '--sysid') (args, options) = parse_command_arguments(args, arg_parser) if not options.sysid: print("# System id must be provided.") print("usage: system_listcrashesbysystem -i sys_id") return l_crashes = self.client.system.crash.listSystemCrashes(self.session, int(options.sysid)) print('') print('Crash ID | Crash Name') print('---------------------') for cr in l_crashes: print("| %s | %s" % (cr['id'], cr['crash'])) ####### def help_system_getcrashfiles(self): print('system_getcrashfiles: Download all files for a crash record.') print('usage: system_getcrashfiles -c crash_id [--verbose]') print('usage: system_getcrashfiles -c crash_id [--dest_folder=/tmp/crash_files] [--verbose]') print('') def do_system_getcrashfiles(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-c', '--crashid') arg_parser.add_argument('-d', '--dest_folder') arg_parser.add_argument('-v', '--verbose', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if not options.crashid: print("# Crash id must be provided.") print("usage: system_getcrashfiles -c crash_id [--dest_folder=/tmp/crash_files] [--verbose]") return if not options.verbose: options.verbose = "&>/dev/null" # create date stamp date_stamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') if not options.dest_folder: options.dest_folder = "files_for_" + "crashid_" + options.crashid + "_" + date_stamp else: options.dest_folder += "_" + date_stamp # create folder os.system("mkdir %s" % options.dest_folder) l_files = self.client.system.crash.listSystemCrashFiles(self.session, int(options.crashid)) for f in l_files: file_url = self.client.system.crash.getCrashFileUrl(self.session, f['id']) os.system("wget --directory-prefix=%s --tries=1 --no-check-certificate %s %s" % ( options.dest_folder, file_url, options.verbose)) print('') print("# All files we downloaded to %s." % options.dest_folder) print('') #################### def help_system_removechildchannels(self): print("system_removechildchannels: Remove child channels from a system") print('usage: system_removechildchannels <SYSTEMS> <CHANNEL ...>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_removechildchannels(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return tab_completer(self.list_child_channels(), text) def do_system_removechildchannels(self, args): self.manipulate_child_channels(args, True) #################### def help_system_details(self): print('system_details: Show the details of a system profile') print('usage: system_details <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_details(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_details(self, args, short=False): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_details() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue last_checkin = \ self.client.system.getName(self.session, system_id).get('last_checkin') details = self.client.system.getDetails(self.session, system_id) if self.check_api_version('10.16'): uuid = self.client.system.getUuid(self.session, system_id) else: uuid = None registered = self.client.system.getRegistrationDate(self.session, system_id) if add_separator: print(self.SEPARATOR) add_separator = True print('Name: %s' % details.get('profile_name')) print('System ID: %i' % system_id) if uuid: print('UUID: %s' % uuid) print('Locked: %s' % details.get('lock_status')) print('Registered: %s' % registered) print('Last Checkin: %s' % last_checkin) print('OSA Status: %s' % details.get('osa_status')) print('Last Boot: %s' % details.get('last_boot')) if 'contact_method' in details: print('Contact Method:%s' % details.get('contact_method')) # only print(basic information if requested) if short: continue network = self.client.system.getNetwork(self.session, system_id) entitlements = self.client.system.getEntitlements(self.session, system_id) base_channel = \ self.client.system.getSubscribedBaseChannel(self.session, system_id) child_channels = \ self.client.system.listSubscribedChildChannels(self.session, system_id) groups = self.client.system.listGroups(self.session, system_id) kernel = self.client.system.getRunningKernel(self.session, system_id) keys = self.client.system.listActivationKeys(self.session, system_id) ranked_config_channels = [] try: config_channels = \ self.client.system.config.listChannels(self.session, system_id) except xmlrpclib.Fault as exc: # 10003 - unsupported operation if exc.faultCode == 10003: logging.debug(exc.faultString) else: logging.warning(exc.faultString) else: for channel in config_channels: ranked_config_channels.append(channel.get('label')) print('') print('Hostname: %s' % network.get('hostname')) print('IP Address: %s' % network.get('ip')) print('Kernel: %s' % kernel) if keys: print('') print('Activation Keys') print('---------------') print('\n'.join(sorted(keys))) print('') print('Software Channels') print('-----------------') print(base_channel.get('label')) for channel in child_channels: print(' |-- %s' % channel.get('label')) if ranked_config_channels: print('') print('Configuration Channels') print('----------------------') print('\n'.join(ranked_config_channels)) print('') print('Entitlements') print('------------') print('\n'.join(sorted(entitlements))) if groups: print('') print('System Groups') print('-------------') for group in groups: if group.get('subscribed') == 1: print(group.get('system_group_name')) #################### def help_system_listerrata(self): print('system_listerrata: List available errata for a system') print('usage: system_listerrata <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listerrata(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listerrata(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listerrata() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) print('') errata = self.client.system.getRelevantErrata(self.session, system_id) print_errata_list(errata) #################### def help_system_applyerrata(self): print('system_applyerrata: Apply errata to a system') print('''usage: system_applyerrata [options] <SYSTEMS> [ERRATA|search:XXX ...] options: -s START_TIME''') print('') print(self.HELP_TIME_OPTS) print('') print(self.HELP_SYSTEM_OPTS) def complete_system_applyerrata(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return self.tab_complete_errata(text) def do_system_applyerrata(self, args): # this is really just an entry point to do_errata_apply # and the whole parsing of the start time needed is done # there; here we only make sure we accept this option arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_applyerrata() return # use the systems applyed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() args.pop(0) else: systems = self.expand_systems(args.pop(0)) # allow globbing and searching of errata errata_list = self.expand_errata(args) if not errata_list or not systems: return # reconstruct options so we can pass them to do_errata_apply opts = [] if options.start_time: opts.append('-s ' + options.start_time) return self.do_errata_apply(' '.join(opts + errata_list), systems) #################### def help_system_listevents(self): print('system_listevents: List the event history for a system') print('usage: system_listevents <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listevents(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listevents(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listevents() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) add_separator = False for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) events = self.client.system.getEventHistory(self.session, system_id) for e in events: print('') print('Summary: %s' % e.get('summary')) print('Completed: %s' % e.get('completed')) print('Details: %s' % e.get('details')) #################### def help_system_listentitlements(self): print('system_listentitlements: List the entitlements for a system') print('usage: system_listentitlements <SYSTEMS>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_listentitlements(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_listentitlements(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_listentitlements() return add_separator = False # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue if add_separator: print(self.SEPARATOR) add_separator = True if len(systems) > 1: print('System: %s' % system) entitlements = self.client.system.getEntitlements(self.session, system_id) print('\n'.join(sorted(entitlements))) #################### def help_system_addentitlements(self): print('system_addentitlements: Add entitlements to a system') print('usage: system_addentitlements <SYSTEMS> ENTITLEMENT') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_addentitlements(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) return tab_completer(self.ENTITLEMENTS, text) def do_system_addentitlements(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_addentitlements() return entitlement = args.pop() for e in self.ENTITLEMENTS: if re.match(entitlement, e, re.I): entitlement = e break # use the systems applyed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.addEntitlements(self.session, system_id, [entitlement]) #################### def help_system_removeentitlement(self): print('system_removeentitlement: Remove an entitlement from a system') print('usage: system_removeentitlement <SYSTEMS> ENTITLEMENT') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_removeentitlement(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) return tab_completer(self.ENTITLEMENTS, text) def do_system_removeentitlement(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_removeentitlement() return entitlement = args.pop() for e in self.ENTITLEMENTS: if re.match(entitlement, e, re.I): entitlement = e break # use the systems applyed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.removeEntitlements(self.session, system_id, [entitlement]) #################### def help_system_listpackageprofiles(self): print('system_listpackageprofiles: List all package profiles') print('usage: system_listpackageprofiles') def do_system_listpackageprofiles(self, args, doreturn=False): profiles = self.client.system.listPackageProfiles(self.session) profiles = [p.get('name') for p in profiles] if doreturn: return profiles else: if profiles: print('\n'.join(sorted(profiles))) #################### def help_system_deletepackageprofile(self): print('system_deletepackageprofile: Delete a package profile') print('usage: system_deletepackageprofile PROFILE') def complete_system_deletepackageprofile(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return self.tab_complete_systems( self.do_system_listpackageprofiles('', True), text) def do_system_deletepackageprofile(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_deletepackageprofile() return label = args[0] if not self.user_confirm('Delete this profile [y/N]:'): return all_profiles = self.client.system.listPackageProfiles(self.session) profile_id = 0 for profile in all_profiles: if label == profile.get('name'): profile_id = profile.get('id') if not profile_id: logging.warning('%s is not a valid profile' % label) return self.client.system.deletePackageProfile(self.session, profile_id) #################### def help_system_createpackageprofile(self): print('system_createpackageprofile: Create a package profile') print('''usage: system_createpackageprofile SYSTEM [options] options: -n NAME -d DESCRIPTION''') def complete_system_createpackageprofile(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) def do_system_createpackageprofile(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-n', '--name') arg_parser.add_argument('-d', '--description') (args, options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_system_createpackageprofile() return system_id = self.get_system_id(args[0]) if not system_id: return if is_interactive(options): options.name = prompt_user('Profile Label:', noblank=True) options.description = prompt_user('Description:', multiline=True) else: if not options.name: logging.error('A profile name is required') return if not options.description: logging.error('A profile description is required') return self.client.system.createPackageProfile(self.session, system_id, options.name, options.description) logging.info("Created package profile '%s'" % options.name) #################### def help_system_comparepackageprofile(self): print('system_comparepackageprofile: Compare a system against a package profile') print('usage: system_comparepackageprofile <SYSTEMS> PROFILE') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_comparepackageprofile(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return self.tab_complete_systems(text) elif len(parts) > 2: return self.tab_complete_systems( self.do_system_listpackageprofiles('', True), parts[-1]) def do_system_comparepackageprofile(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_comparepackageprofile() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() args.pop(0) else: systems = self.expand_systems(args[:-1]) profile = args[-1] add_separator = False for system in systems: system_id = self.get_system_id(system) if not system_id: continue results = self.client.system.comparePackageProfile(self.session, system_id, profile) if add_separator: print(self.SEPARATOR) add_separator = True print('%s:' % system) self.print_package_comparison(results) #################### def help_system_comparepackages(self): print('system_comparepackages: Compare the packages between two systems') print('usage: system_comparepackages SOME_SYSTEM ANOTHER_SYSTEM') def complete_system_comparepackages(self, text, line, beg, end): return tab_completer(self.get_system_names(), text) def do_system_comparepackages(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_system_comparepackages() return this_system = self.get_system_id(args[0]) other_system = self.get_system_id(args[1]) results = self.client.system.comparePackages(self.session, this_system, other_system) self.print_package_comparison(results) #################### def help_system_syncpackages(self): print('system_syncpackages: Sync packages between two systems') print('''usage: system_syncpackages SOURCE TARGET [options] options: -s START_TIME''') print('') print(self.HELP_TIME_OPTS) def complete_system_syncpackages(self, text, line, beg, end): return tab_completer(self.get_system_names(), text) def do_system_syncpackages(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_system_syncpackages() return (source, target) = args source_id = self.get_system_id(source) target_id = self.get_system_id(target) if not source_id or not target_id: return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # show a comparison and ask for confirmation self.do_system_comparepackages('%s %s' % (source_id, target_id)) print('') print('Start Time: %s' % options.start_time) if not self.user_confirm('Sync packages [y/N]:'): return # get package IDs packages = self.client.system.listPackages(self.session, source_id) package_names = build_package_names(packages) package_ids = [] for name in package_names: p_ids = self.get_package_id(name) # filter out invalid package IDs if p_ids: package_ids += p_ids self.client.system.scheduleSyncPackagesWithSystem(self.session, target_id, source_id, package_ids, options.start_time) #################### def filter_latest_packages(pkglist, version_key='version', release_key='release', epoch_key='epoch'): # Returns a dict, indexed by a compound (tuple) key based on # arch and name, so we can store the latest version of each package # for each arch. This approach avoids nested loops :) latest = {} for p in pkglist: if 'arch_label' in p: tuplekey = p['name'], p['arch_label'] elif 'arch' in p: # Fixup arch==AMD64 which is returned for some reason p['arch'] = re.sub('AMD64', 'x86_64', p['arch']) tuplekey = p['name'], p['arch'] else: logging.error("Failed to filter package list, package %s" % p + "found with no arch or arch_label") return None if not tuplekey in latest: latest[tuplekey] = p else: # Already have this package, is p newer? if p == latest_pkg(p, latest[tuplekey], version_key, release_key, epoch_key): latest[tuplekey] = p return latest def print_comparison_withchannel(self, channelnewer, systemnewer, channelmissing, channel_latest): # Figure out correct indentation to allow pretty table output results = channelnewer + systemnewer + channelmissing tmp_names = [] tmp_system = [] tmp_channel = [] for item in results: name_string = "%(name)s.%(arch)s" % item tmp_names.append(name_string) # Create two version-string lists, one for the version in the results # list, and another with the version string from the channel_latest # dict, if the channel contains a matching package version_string = "%(version)s-%(release)s" % item tmp_system.append(version_string) key = item['name'], item['arch'] if key in channel_latest: version_string = "%(version)s-%(release)s" % channel_latest[key] tmp_channel.append(version_string) max_name = max_length(tmp_names, minimum=7) max_system = max_length(tmp_system, minimum=11) max_channel = max_length(tmp_channel, minimum=15) max_comparison = 25 # print(headers) print('%s %s %s %s' % ( 'Package'.ljust(max_name), 'System Version'.ljust(max_system), 'Channel Version'.ljust(max_channel), 'Difference'.ljust(max_comparison))) print('%s %s %s %s' % ( '-' * max_name, '-' * max_system, '-' * max_channel, '-' * max_comparison)) # Then print(the packages) for item in channelnewer: name_string = "%(name)s.%(arch)s" % item version_string = "%(version)s-%(release)s" % item key = item['name'], item['arch'] if key in channel_latest: channel_version = "%(version)s-%(release)s" % channel_latest[key] else: channel_version = '-' print('%s %s %s %s' % ( name_string.ljust(max_name), version_string.ljust(max_system), channel_version.ljust(max_channel), "Channel_newer_than_system".ljust(max_comparison))) for item in systemnewer: name_string = "%(name)s.%(arch)s" % item version_string = "%(version)s-%(release)s" % item key = item['name'], item['arch'] if key in channel_latest: channel_version = "%(version)s-%(release)s" % channel_latest[key] else: channel_version = '-' print('%s %s %s %s' % ( name_string.ljust(max_name), version_string.ljust(max_system), channel_version.ljust(max_channel), "System_newer_than_channel".ljust(max_comparison))) for item in channelmissing: name_string = "%(name)s.%(arch)s" % item version_string = "%(version)s-%(release)s" % item channel_version = '-' print('%s %s %s %s' % ( name_string.ljust(max_name), version_string.ljust(max_system), channel_version.ljust(max_channel), "Missing_in_channel".ljust(max_comparison))) def help_system_comparewithchannel(self): print('system_comparewithchannel: Compare the installed packages on a') print(' system with those in the channels it is') print(' registerd to, or optionally some other') print(' channel') print('usage: system_comparewithchannel <SYSTEMS> [options]') print('options:') print(' -c/--channel : Specific channel to compare against,') print(' default is those subscribed to, including') print(' child channels') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_comparewithchannel(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_comparewithchannel(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-c', '--channel') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_comparewithchannel() return # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) channel_latest = {} for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue instpkgs = self.client.system.listPackages(self.session, system_id) logging.debug("Got %d packages installed in system %s" % (len(instpkgs), system)) # We need to filter to get only the latest installed packages, # because multiple versions (e.g kernel) can be installed packages = filter_latest_packages(instpkgs) logging.debug("Got latest %d packages installed in system %s" % (len(packages.keys()), system)) channels = [] if options.channel: # User specified a specific channel, check it exists allch = self.client.channel.listSoftwareChannels(self.session) allch_labels = [c['label'] for c in allch] if not options.channel in allch_labels: logging.error("Specified channel does not exist") self.help_system_comparewithchannel() return channels = [options.channel] logging.debug("User specified channel %s" % options.channel) else: # No specified channel, so we create a list of all channels the # system is subscribed to basech = self.client.system.getSubscribedBaseChannel(self.session, system_id) if not basech: logging.error("system %s is not subscribed to any channel!" % system) logging.error("Please subscribe to a channel, or specify a" + "channel to compare with") return logging.debug("base channel %s for %s" % (basech['name'], system)) childch = self.client.system.listSubscribedChildChannels( self.session, system_id) channels = [basech['label']] for c in childch: channels.append(c['label']) # Get the latest packages in each channel latestpkgs = {} for c in channels: if not c in channel_latest: logging.debug("Getting packages for channel %s" % c) pkgs = self.client.channel.software.listAllPackages( self.session, c) # filter_latest_packages Returns a dict of latest packages # indexed by name,arch tuple, which we add to the dict-of-dict # channel_latest, to avoid getting the same channel data # multiple times when processing more than one system channel_latest[c] = filter_latest_packages(pkgs) # Merge the channel latest dicts into one latestpkgs dict # We handle collisions and only store the latest version # We do this for every channel of every system, since the mix of # subscribed channels may be different for key in channel_latest[c].keys(): if not key in latestpkgs: latestpkgs[key] = channel_latest[c][key] else: p_newest = latest_pkg(channel_latest[c][key], latestpkgs[key]) latestpkgs[key] = p_newest if len(systems) > 1: print('\nSystem: %s' % system) # Iterate over the installed packages channelnewer = [] systemnewer = [] channelmissing = [] for key in packages: syspkg = packages.get(key) if key in latestpkgs: chpkg = latestpkgs.get(key) newest = latest_pkg(syspkg, chpkg) if syspkg == newest: systemnewer.append(syspkg) elif chpkg == newest: channelnewer.append(syspkg) else: channelmissing.append(syspkg) self.print_comparison_withchannel(channelnewer, systemnewer, channelmissing, latestpkgs) #################### def help_system_schedulehardwarerefresh(self): print('system_schedulehardwarerefresh: Schedule a hardware refresh for a system') print('''usage: system_schedulehardwarerefresh <SYSTEMS> [options] options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_schedulehardwarerefresh(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_schedulehardwarerefresh(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_schedulehardwarerefresh() return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.scheduleHardwareRefresh(self.session, system_id, options.start_time) #################### def help_system_schedulepackagerefresh(self): print('system_schedulepackagerefresh: Schedule a software package refresh for a system') print('''usage: system_schedulepackagerefresh <SYSTEMS> [options]) options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def complete_system_schedulepackagerefresh(self, text, line, beg, end): return self.tab_complete_systems(text) def do_system_schedulepackagerefresh(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_schedulepackagerefresh() return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in systems: system_id = self.get_system_id(system) if not system_id: continue self.client.system.schedulePackageRefresh(self.session, system_id, options.start_time) #################### def help_system_show_packageversion(self): print('system_show_packageversion: Shows version of installed package on given system(s)') print('usage: system_show_packageversion <SYSTEM> <PACKAGE>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_show_packageversion(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) return tab_completer(self.get_package_names(), text) def do_system_show_packageversion(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_system_show_packageversion() return if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) print("Package\tVersion\tRelease\tEpoch\tArch\tSystem") print("==============================================") for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue instpkgs = self.client.system.listPackages(self.session, system_id) searchpkg = args[1] for pkg in instpkgs: if pkg.get('name') == searchpkg: print("%s\t%s\t%s\t%s\t%s\t%s" % (pkg.get('name'), pkg.get('version'), pkg.get('release'), pkg.get('epoch'), pkg.get('arch_label'), system)) #################### def help_system_setcontactmethod(self): print('system_setcontactmethod: Set the contact method for given system(s).') print('Available contact methods: ' + str(self.CONTACT_METHODS)) print('usage: system_setcontactmethod <SYSTEMS> <CONTACT_METHOD>') print('') print(self.HELP_SYSTEM_OPTS) def complete_system_setcontactmethod(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return self.tab_complete_systems(text) else: return tab_completer(self.CONTACT_METHODS, text) def do_system_setcontactmethod(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_system_setcontactmethod() return contact_method = args.pop() details = {'contact_method': contact_method} # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) for system in sorted(systems): system_id = self.get_system_id(system) if not system_id: continue self.client.system.setDetails(self.session, system_id, details) #################### def help_system_scheduleapplyconfigchannels(self): print("system_scheduleapplyconfigchannels: Schedule applying the assigned config channels to the System (Minion only)") print('''usage: scheduleapplyconfigchannels <SYSTEMS> [options] options: -s START_TIME''') print('') print(self.HELP_SYSTEM_OPTS) print('') print(self.HELP_TIME_OPTS) def do_system_scheduleapplyconfigchannels(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-s', '--start-time') (args, options) = parse_command_arguments(args, arg_parser) if not args: self.help_system_scheduleapplyconfigchannels() return # get the start time option # skip the prompt if we are running with --yes # use "now" if no start time was given if is_interactive(options) and self.options.yes != True: options.start_time = prompt_user('Start Time [now]:') options.start_time = parse_time_input(options.start_time) else: if not options.start_time: options.start_time = parse_time_input('now') else: options.start_time = parse_time_input(options.start_time) # use the systems listed in the SSM if re.match('ssm', args[0], re.I): systems = self.ssm.keys() else: systems = self.expand_systems(args) if not systems: return print('') print('Start Time: %s' % options.start_time) print('') print('Systems') print('-------') print('\n'.join(sorted(systems))) message = 'Schedule applying config channels to these systems [y/N]:' if not self.user_confirm(message): return system_ids = [self.get_system_id(s) for s in systems] actionId = self.client.system.config.scheduleApplyConfigChannel(self.session, system_ids, options.start_time, False) print('Scheduled action id: %s' % actionId) #################### 0707010000002B000081B40000000000000000000000015D65A5B200004AA8000000000000000000000000000000000000001E00000000spacecmd/src/spacecmd/user.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2013--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import shlex from getpass import getpass try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from spacecmd.utils import * def help_user_create(self): print('user_create: Create an user') print('''usage: user_create [options]) options: -u USERNAME -f FIRST_NAME -l LAST_NAME -e EMAIL -p PASSWORD --pam enable PAM authentication''') def do_user_create(self, args): arg_parser = get_argument_parser() arg_parser.add_argument('-u', '--username') arg_parser.add_argument('-f', '--first-name') arg_parser.add_argument('-l', '--last-name') arg_parser.add_argument('-e', '--email') arg_parser.add_argument('-p', '--password') arg_parser.add_argument('--pam', action='store_true') (args, options) = parse_command_arguments(args, arg_parser) if is_interactive(options): options.username = prompt_user('Username:', noblank=True) options.first_name = prompt_user('First Name:', noblank=True) options.last_name = prompt_user('Last Name:', noblank=True) options.email = prompt_user('Email:', noblank=True) options.pam = self.user_confirm('PAM Authentication [y/N]:', nospacer=True, integer=True, ignore_yes=True) options.password = '' while options.password == '': password1 = getpass('Password: ') password2 = getpass('Repeat Password: ') if password1 == password2: options.password = password1 elif password1 == '': logging.warning('Password must be at least 5 characters') else: logging.warning("Passwords don't match") else: if not options.username: logging.error('A username is required') return if not options.first_name: logging.error('A first name is required') return if not options.last_name: logging.error('A last name is required') return if not options.email: logging.error('An email address is required') return if not options.password and not options.pam: logging.error('A password is required') return if options.pam: options.pam = 1 # API requires a non-None password even though it's not used # when PAM is enabled if options.password: logging.warning("Note password field is ignored for PAM mode") options.password = "" else: options.pam = 0 self.client.user.create(self.session, options.username, options.password, options.first_name, options.last_name, options.email, options.pam) #################### def help_user_delete(self): print('user_delete: Delete an user') print('usage: user_delete NAME') def complete_user_delete(self, text, line, beg, end): return tab_completer(self.do_user_list('', True), text) def do_user_delete(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_user_delete() return name = args[0] if self.user_confirm('Delete this user [y/N]:'): self.client.user.delete(self.session, name) #################### def help_user_disable(self): print('user_disable: Disable an user account') print('usage: user_disable NAME') def complete_user_disable(self, text, line, beg, end): return tab_completer(self.do_user_list('', True), text) def do_user_disable(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_user_disable() return name = args[0] self.client.user.disable(self.session, name) #################### def help_user_enable(self): print('user_enable: Enable an user account') print('usage: user_enable NAME') def complete_user_enable(self, text, line, beg, end): return tab_completer(self.do_user_list('', True), text) def do_user_enable(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 1: self.help_user_enable() return name = args[0] self.client.user.enable(self.session, name) #################### def help_user_list(self): print('user_list: List all users') print('usage: user_list') def do_user_list(self, args, doreturn=False): users = self.client.user.listUsers(self.session) users = [u.get('login') for u in users] if doreturn: return users else: if users: print('\n'.join(sorted(users))) #################### def help_user_listavailableroles(self): print('user_listavailableroles: List all available roles for users') print('usage: user_listavailableroles') def do_user_listavailableroles(self, args, doreturn=False): roles = self.client.user.listAssignableRoles(self.session) if doreturn: return roles else: if roles: print('\n'.join(sorted(roles))) #################### def help_user_addrole(self): print('user_addrole: Add a role to an user account') print('usage: user_addrole USER ROLE') def complete_user_addrole(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) == 3: return tab_completer(self.do_user_listavailableroles('', True), text) def do_user_addrole(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_user_addrole() return user = args[0] role = args[1] self.client.user.addRole(self.session, user, role) #################### def help_user_removerole(self): print('user_removerole: Remove a role from an user account') print('usage: user_removerole USER ROLE') def complete_user_removerole(self, text, line, beg, end): parts = line.split(' ') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) == 3: # only list the roles currently assigned to this user roles = self.client.user.listRoles(self.session, parts[1]) return tab_completer(roles, text) def do_user_removerole(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_user_removerole() return user = args[0] role = args[1] self.client.user.removeRole(self.session, user, role) #################### def help_user_details(self): print('user_details: Show the details of an user') print('usage: user_details USER ...') def complete_user_details(self, text, line, beg, end): return tab_completer(self.do_user_list('', True), text) def do_user_details(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if not args: self.help_user_details() return add_separator = False for user in args: try: details = self.client.user.getDetails(self.session, user) roles = self.client.user.listRoles(self.session, user) groups = \ self.client.user.listAssignedSystemGroups(self.session, user) default_groups = \ self.client.user.listDefaultSystemGroups(self.session, user) except xmlrpclib.Fault: logging.warning('%s is not a valid user' % user) continue org_details = self.client.org.getDetails(self.session, details.get('org_id')) organization = org_details.get('name') if add_separator: print(self.SEPARATOR) add_separator = True print('Username: %s' % user) print('First Name: %s' % details.get('first_name')) print('Last Name: %s' % details.get('last_name')) print('Email Address: %s' % details.get('email')) print('Organization: %s' % organization) print('Last Login: %s' % details.get('last_login_date')) print('Created: %s' % details.get('created_date')) print('Enabled: %s' % details.get('enabled')) if roles: print('') print('Roles') print('-----') print('\n'.join(sorted(roles))) if groups: print('') print('Assigned Groups') print('---------------') print('\n'.join(sorted([g.get('name') for g in groups]))) if default_groups: print('') print('Default Groups') print('--------------') print('\n'.join(sorted([g.get('name') for g in default_groups]))) #################### def help_user_addgroup(self): print('user_addgroup: Add a group to an user account') print('usage: user_addgroup USER <GROUP ...>') def complete_user_addgroup(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_group_list('', True), parts[-1]) def do_user_addgroup(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_user_addgroup() return user = args.pop(0) groups = args self.client.user.addAssignedSystemGroups(self.session, user, groups, False) #################### def help_user_adddefaultgroup(self): print('user_adddefaultgroup: Add a default group to an user account') print('usage: user_adddefaultgroup USER <GROUP ...>') def complete_user_adddefaultgroup(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: return tab_completer(self.do_group_list('', True), parts[-1]) def do_user_adddefaultgroup(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_user_adddefaultgroup() return user = args.pop(0) groups = args self.client.user.addDefaultSystemGroups(self.session, user, groups) #################### def help_user_removegroup(self): print('user_removegroup: Remove a group to an user account') print('usage: user_removegroup USER <GROUP ...>') def complete_user_removegroup(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: # only list the groups currently assigned to this user groups = self.client.user.listAssignedSystemGroups(self.session, parts[1]) return tab_completer([g.get('name') for g in groups], parts[-1]) def do_user_removegroup(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_user_removegroup() return user = args.pop(0) groups = args self.client.user.removeAssignedSystemGroups(self.session, user, groups, True) #################### def help_user_removedefaultgroup(self): print('user_removedefaultgroup: Remove a default group from an ' + 'user account') print('usage: user_removedefaultgroup USER <GROUP ...>') def complete_user_removedefaultgroup(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append('') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: # only list the groups currently assigned to this user groups = self.client.user.listDefaultSystemGroups(self.session, parts[1]) return tab_completer([g.get('name') for g in groups], parts[-1]) def do_user_removedefaultgroup(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) < 2: self.help_user_removedefaultgroup() return user = args.pop(0) groups = args self.client.user.removeDefaultSystemGroups(self.session, user, groups) #################### def help_user_setfirstname(self): print('user_setfirstname: Set an user accounts first name field') print('usage: user_setfirstname USER FIRST_NAME') def complete_user_setfirstname(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append(' ') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: return def do_user_setfirstname(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_user_setfirstname() return user = args.pop(0) details = {'first_name': args.pop(0)} self.client.user.setDetails(self.session, user, details) #################### def help_user_setlastname(self): print('user_setlastname: Set an user accounts last name field') print('usage: user_setlastname USER LAST_NAME') def complete_user_setlastname(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append(' ') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: return def do_user_setlastname(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_user_setlastname() return user = args.pop(0) details = {'last_name': args.pop(0)} self.client.user.setDetails(self.session, user, details) #################### def help_user_setemail(self): print('user_setemail: Set an user accounts email field') print('usage: user_setemail USER EMAIL') def complete_user_setemail(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append(' ') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: return def do_user_setemail(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_user_setemail() return user = args.pop(0) details = {'email': args.pop(0)} self.client.user.setDetails(self.session, user, details) #################### def help_user_setprefix(self): print('user_setprefix: Set an user accounts name prefix field') print('usage: user_setprefix USER PREFIX') def complete_user_setprefix(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append(' ') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: return def do_user_setprefix(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) > 2: self.help_user_setprefix() return user = args.pop(0) if not args: # clearing prefix with a space currently does not work # spacewalk requires a space to clear the prefix but the # space seems to be stripped when submitted to the API gateway # attempts to use %x20 and \u0020 (among others) also fail details = {'prefix': ' '} else: details = {'prefix': args.pop(0)} self.client.user.setDetails(self.session, user, details) #################### def help_user_setpassword(self): print('user_setpassword: Set an user accounts name prefix field') print('usage: user_setpassword USER PASSWORD') def complete_user_setpassword(self, text, line, beg, end): parts = shlex.split(line) if line[-1] == ' ': parts.append(' ') if len(parts) == 2: return tab_completer(self.do_user_list('', True), text) elif len(parts) > 2: return def do_user_setpassword(self, args): arg_parser = get_argument_parser() (args, _options) = parse_command_arguments(args, arg_parser) if len(args) != 2: self.help_user_setpassword() return user = args.pop(0) details = {'password': args.pop(0)} self.client.user.setDetails(self.session, user, details) 0707010000002C000081B40000000000000000000000015D65A5B2000061E2000000000000000000000000000000000000001F00000000spacecmd/src/spacecmd/utils.py# # Licensed under the GNU General Public License Version 3 # # 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 3 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. # # Copyright 2013 Aron Parsons <aronparsons@gmail.com> # Copyright (c) 2011--2018 Red Hat, Inc. # # NOTE: the 'self' variable is an instance of SpacewalkShell # wildcard import # pylint: disable=W0401,W0614 # unused argument # pylint: disable=W0613 # invalid function name # pylint: disable=C0103 import logging import os import pickle import re import readline import shlex import sys import time import argparse try: from xmlrpc import client as xmlrpclib except ImportError: import xmlrpclib from collections import deque from datetime import datetime, timedelta from difflib import unified_diff from tempfile import mkstemp from textwrap import wrap from subprocess import Popen, PIPE try: import json except ImportError: import simplejson as json # python < 2.6 import rpm from spacecmd.argumentparser import SpacecmdArgumentParser __EDITORS = ['vim', 'vi', 'nano', 'emacs'] class CustomJsonEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj,xmlrpclib.DateTime): return datetime.fromtimestamp(time.mktime(obj.timetuple())).strftime("%F %T") return json.JSONEncoder.default(self, obj) def get_argument_parser(): return SpacecmdArgumentParser() def parse_command_arguments(command_args, argument_parser, glob=True): try: parts = shlex.split(command_args) # allow simple globbing if glob: parts = [re.sub(r'\*', '.*', a) for a in parts] argument_parser.add_argument('leftovers', nargs='*', help=argparse.SUPPRESS) opts = argument_parser.parse_args(args=parts) if opts.leftovers: leftovers = opts.leftovers else: leftovers = [] return leftovers, opts except IndexError: return None, None # check if any named options were passed to the function, and if so, # declare that the function is non-interactive # note: because we do it this way, default options are not passed into # OptionParser, as it would make determining if any options were passed # too complex def is_interactive(options): for key in options.__dict__: if options.__dict__[key]: return False return True def load_cache(cachefile): data = {} expire = datetime.now() logging.debug('Loading cache from %s', cachefile) if os.path.isfile(cachefile): try: inputfile = open(cachefile, 'rb') data = pickle.load(inputfile) inputfile.close() except EOFError: # If cache generation is interrupted (e.g by ctrl-c) you can end up # with an EOFError exception due to the partial picked file # So we catch this error and remove the corrupt partial file # If you don't do this then spacecmd will fail with an unhandled # exception until the partial file is manually removed logging.warning("Loading cache file %s failed", cachefile) logging.warning("Cache generation was probably interrupted," + "removing corrupt %s", cachefile) os.remove(cachefile) except IOError: logging.error("Couldn't load cache from %s", cachefile) if isinstance(data, (list, dict)): if 'expire' in data: expire = data['expire'] del data['expire'] else: logging.debug('%s does not exist', cachefile) return data, expire def save_cache(cachefile, data, expire=None): if expire: data['expire'] = expire try: output = open(cachefile, 'wb') pickle.dump(data, output, pickle.HIGHEST_PROTOCOL) output.close() except IOError: logging.error("Couldn't write to %s", cachefile) if 'expire' in data: del data['expire'] def tab_completer(options, text): return [o for o in options if re.match(text, o)] def filter_results(items, patterns, search=False): matches = [] compiled_patterns = [] for pattern in patterns: if search: compiled_patterns.append(re.compile(pattern, re.I)) else: # If in "match" mode, we don't want to match substrings compiled_patterns.append(re.compile("^" + pattern + "$", re.I)) for item in items: for pattern in compiled_patterns: if search: result = pattern.search(item) else: result = pattern.match(item) if result: matches.append(item) break return matches def editor(template='', delete=False): # create a temporary file (descriptor, file_name) = mkstemp(prefix='spacecmd.') if template and descriptor: try: handle = os.fdopen(descriptor, 'w') handle.write(template) handle.close() except IOError: logging.warning('Could not open the temporary file') # use the user's specified editor if 'EDITOR' in os.environ: if __EDITORS[0] != os.environ['EDITOR']: __EDITORS.insert(0, os.environ['EDITOR']) success = False for editor_cmd in __EDITORS: try: exit_code = os.spawnlp(os.P_WAIT, editor_cmd, editor_cmd, file_name) if exit_code == 0: success = True break else: logging.error('Editor exited with code %i', exit_code) except OSError: pass if not success: logging.error('No editors found') return '' if os.path.isfile(file_name) and exit_code == 0: try: # read the session (format = username:session) handle = open(file_name, 'r') contents = handle.read() handle.close() if delete: try: os.remove(file_name) file_name = '' except OSError: logging.error('Could not remove %s', file_name) return (contents, file_name) except IOError: logging.error('Could not read %s', file_name) return ([], '') def prompt_user(prompt, noblank=False, multiline=False): try: while True: if multiline: print(prompt) userinput = sys.stdin.read() else: try: # python 2 must call raw_input() because input() # also evaluates the user input and that causes # problems. userinput = raw_input('%s ' % prompt) except NameError: # python 3 replaced raw_input() with input()... # it no longer evaulates the user input. userinput = input('%s ' % prompt) if noblank: if userinput != '': break else: break except EOFError: print('') return '' if userinput != '': last = readline.get_current_history_length() - 1 if last >= 0: readline.remove_history_item(last) return userinput # parse time input from the user and return xmlrpclib.DateTime def parse_time_input(userinput=''): timestamp = None if userinput == '' or re.match('now', userinput, re.I): timestamp = datetime.now() # handle YYYMMDDHHMM times if not timestamp: match = re.match(r'^(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?(\d{2})?$', userinput) if match: date_format = '%Y%m%d' # YYYYMMDD if not match.group(4) and not match.group(5): timestamp = time.strptime('%s%s%s' % (match.group(1), match.group(2), match.group(3)), date_format) # YYYYMMDDHH elif not match.group(5): date_format += '%H' timestamp = time.strptime('%s%s%s%s' % (match.group(1), match.group(2), match.group(3), match.group(4)), date_format) # YYYYMMDDHHMM elif not match.group(6): date_format += '%H%M' timestamp = time.strptime('%s%s%s%s%s' % (match.group(1), match.group(2), match.group(3), match.group(4), match.group(5)), date_format) # YYYYMMDDHHMMSS else: date_format += '%H%M%S' timestamp = time.strptime('%s%s%s%s%s%s' % (match.group(1), match.group(2), match.group(3), match.group(4), match.group(5), match.group(6)), date_format) if timestamp: # 2.5 has a nice little datetime.strptime() function... timestamp = datetime(*(timestamp)[0:7]) # handle time differences (e.g., +1m, +2h) if not timestamp: match = re.search(r'^(\+|-)?(\d+)(s|m|h|d)$', userinput, re.I) if match and len(match.groups()) >= 2: sign = match.group(1) number = int(match.group(2)) unit = match.group(3) if sign == '-': number = -number if re.match('s', unit, re.I): delta = timedelta(seconds=number) elif re.match('m', unit, re.I): delta = timedelta(minutes=number) elif re.match('h', unit, re.I): delta = timedelta(hours=number) elif re.match('d', unit, re.I): delta = timedelta(days=number) timestamp = datetime.now() + delta if timestamp: return xmlrpclib.DateTime(timestamp.timetuple()) logging.error('Invalid time provided') return # Compares 2 package objects (dicts) and returns the newest one. # If the objects are the same, we return None def latest_pkg(pkg1, pkg2, version_key='version', release_key='release', epoch_key='epoch'): # Sometimes empty epoch is a space, sometimes its an empty string, which # breaks the comparison, strip it here to fix t1 = (pkg1[epoch_key].strip(), pkg1[version_key], pkg1[release_key]) t2 = (pkg2[epoch_key].strip(), pkg2[version_key], pkg2[release_key]) result = rpm.labelCompare(t1, t2) # pylint: disable=no-member if result == 1: return pkg1 if result == -1: return pkg2 return None # build a proper RPM name from the various parts def build_package_names(packages): single = False if not isinstance(packages, list): packages = [packages] single = True package_names = [] for p in packages: package = '%s-%s-%s' % ( p.get('name'), p.get('version'), p.get('release')) if p.get('epoch') != ' ' and p.get('epoch') != '': package += ':%s' % p.get('epoch') if p.get('arch'): # system.listPackages uses AMD64 instead of x86_64 arch = re.sub('AMD64', 'x86_64', p.get('arch')) package += '.%s' % arch elif p.get('arch_label'): package += '.%s' % p.get('arch_label') package_names.append(package) if single: return package_names[0] package_names.sort() return package_names def print_errata_summary(erratum): # Workaround - recent spacewalk lacks the "date" key # on some listErrata calls if erratum.get('date') is None: erratum['date'] = erratum.get('issue_date') if erratum['date'] is None: erratum['date'] = "no_date" date_parts = erratum['date'].split() if len(date_parts) > 1: erratum['date'] = date_parts[0] print('%s %s %s' % ( erratum.get('advisory_name').ljust(14), wrap(erratum.get('advisory_synopsis'), 50)[0].ljust(50), erratum.get('date').rjust(8))) def print_errata_list(errata): rhsa = [] rhea = [] rhba = [] for erratum in errata: if re.match('security', erratum.get('advisory_type'), re.I): rhsa.append(erratum) elif re.match('bug fix', erratum.get('advisory_type'), re.I): rhba.append(erratum) elif re.match('product enhancement', erratum.get('advisory_type'), re.I): rhea.append(erratum) else: logging.warning('%s is an unknown errata type', erratum.get('advisory_name')) continue if not errata: return if rhsa: print('Security Errata') print('---------------') for erratum in rhsa: print_errata_summary(erratum) if rhba: if rhsa: print('') print('Bug Fix Errata') print('--------------') for erratum in rhba: print_errata_summary(erratum) if rhea: if rhsa or rhba: print('') print('Enhancement Errata') print('------------------') for erratum in rhea: print_errata_summary(erratum) def config_channel_order(all_channels=None, new_channels=None): all_channels = all_channels or [] new_channels = new_channels or [] while True: print('Current Selections') print('------------------') for i, new_channel in enumerate(new_channels, 1): print('%i. %s' % (i, new_channel)) print('') action = prompt_user('a[dd], r[emove], c[lear], d[one]:') if re.match('a', action, re.I): print('') print('Available Configuration Channels') print('--------------------------------') for c in sorted(all_channels): print(c) print('') channel = prompt_user('Channel:') if channel not in all_channels: logging.warning('Invalid channel') continue try: rank = int(prompt_user('New Rank:')) if channel in new_channels: new_channels.remove(channel) new_channels.insert(rank - 1, channel) except IndexError: logging.warning('Invalid rank') continue except ValueError: logging.warning('Invalid rank') continue elif re.match('r', action, re.I): channel = prompt_user('Channel:') if channel not in all_channels: logging.warning('Invalid channel') continue new_channels.remove(channel) elif re.match('c', action, re.I): print('Clearing current selections') new_channels = [] continue elif re.match('d', action, re.I): break print('') return new_channels def list_locales(): if not os.path.isdir('/usr/share/zoneinfo'): return [] zones = [] for item in os.listdir('/usr/share/zoneinfo'): path = os.path.join('/usr/share/zoneinfo', item) if os.path.isdir(path): try: for subitem in os.listdir(path): zones.append(os.path.join(item, subitem)) except IOError: logging.error('Could not read %s', path) else: zones.append(item) return zones # find the longest string in a list def max_length(items, minimum=0): max_size = 1 for item in items: if len(item) > max_size: max_size = len(item) if max_size < minimum: max_size = minimum return max_size # read in a file def read_file(filename): handle = open(filename, 'r') contents = handle.read() handle.close() return contents def parse_str(s, type_to=None): """ Similar to 'read :: Read a => String -> a' in Haskell. >>> parse_str('1234567', int) 1234567 >>> parse_str('1234567') 1234567 >>> parse_str('abcXYZ012') 'abcXYZ012' >>> d = dict(channelLabel="foo-i386-5") >>> d = parse_str('{"channelLabel": "foo-i386-5"}') >>> assert d["channelLabel"] == 'foo-i386-5' """ try: if type_to is not None and isinstance(type_to, type): return type_to(s) if re.match(r'[1-9]\d*', s): return int(s) if re.match(r'{.*}', s): return json.loads(s) # retry with json module return str(s) except ValueError: return str(s) def parse_list_str(list_s, sep=","): """ simple parser for a list of items separated with "," (comma) or given separator chars. >>> assert parse_list_str("") == [] >>> assert parse_list_str("a,b") == ["a", "b"] >>> assert parse_list_str("a,b,") == ["a", "b"] >>> assert parse_list_str("a:b:", ":") == ["a", "b"] """ return [p for p in list_s.split(sep) if p] def parse_api_args(args, sep=','): """ Simple JSON-like expression parser. :param args: a list of strings may be separated with sep, and each string represents parameters passed to API later. :type args: `str` :param sep: A char to separate paramters in `args` :type sep: `str` :rtype: rpc arg objects, [arg] :: [string] >>> parse_api_args('') [] >>> parse_api_args('1234567') [1234567] >>> parse_api_args('abcXYZ012') ['abcXYZ012'] >>> assert parse_api_args('{"channelLabel": "foo-i386-5"}')[0]["channelLabel"] == "foo-i386-5" >>> (i, s, d) = parse_api_args('1234567,abcXYZ012,{"channelLabel": "foo-i386-5"}') >>> assert i == 1234567 >>> assert s == "abcXYZ012" >>> assert d["channelLabel"] == "foo-i386-5" >>> (i, s, d) = parse_api_args('[1234567,"abcXYZ012",{"channelLabel": "foo-i386-5"}]') >>> assert i == 1234567 >>> assert s == "abcXYZ012" >>> assert d["channelLabel"] == "foo-i386-5" """ if not args: return [] try: x = json.loads(args) ret = x if isinstance(x, list) else [x] except ValueError: ret = [parse_str(a) for a in parse_list_str(args, sep)] return ret def json_dump(obj, fp, indent=4, **kwargs): json.dump(obj, fp, ensure_ascii=False, indent=indent, **kwargs) def json_dump_to_file(obj, filename): json_data = json.dumps(obj, indent=4, sort_keys=True) if json_data is None: logging.error("Could not generate json data object!") return False try: fd = open(filename, 'w') fd.write(json_data) fd.close() except IOError as E: logging.error("Could not open file %s for writing, permissions?", filename) print(E.strerror) return False return True def json_read_from_file(filename): try: data = open(filename).read() try: jsondata = json.loads(data) return jsondata except ValueError: print("could not read in data from %s" % filename) except IOError: print("could not open file %s for reading, check permissions?" % filename) return None def get_string_diff_dicts(string1, string2, sep="-"): """ compares two strings and determine, if one string can be transformed into the other by simple string replacements. If these strings are closly related, it returns two dictonaries of regular expressions. The first dictionary can be used to transfrom type 1 strings into type 2 strings. The second dictionary vice versa. These replacements blocks must be separated by "-". Example: string1: rhel6-x86_64-dev-application1 string2: rhel6-x86_64-qas-application1 Result: dict1: {'(^|-)dev(-|$)': '\\1DIFF(dev|qas)\\2'} dict2: {'(^|-)qas(-|$)': '\\1DIFF(dev|qas)\\2'} """ replace1 = {} replace2 = {} if string1 == string2: logging.info("Skipping usage of common strings: both strings are equal") return [None, None] substrings1 = deque(string1.split(sep)) substrings2 = deque(string2.split(sep)) while substrings1 and substrings2: sub1 = substrings1.popleft() sub2 = substrings2.popleft() if sub1 == sub2: # equal, nothing to do pass else: # TODO: replace only if len(sub1) == len(sub2) ? replace1['(^|-)' + sub1 + '(-|$)'] = r'\1' + "DIFF(" + sub1 + "|" + sub2 + ")" + r'\2' replace2['(^|-)' + sub2 + '(-|$)'] = r'\1' + "DIFF(" + sub1 + "|" + sub2 + ")" + r'\2' if substrings1 or substrings2: logging.info("Skipping usage of common strings: number of substrings differ") return [None, None] return [replace1, replace2] def replace(line, replacedict): if replacedict: for source in replacedict: line = re.sub(source, replacedict[source], line) return line def get_normalized_text(text, replacedict=None, excludes=None): # parts of the data inside the spacewalk component information # are not really differences between two instances. # Therefore parts of the data will be modified before the diff: # - specific lines, starting with a defined keyword, will be excluded # - specific character sequences will be replaced with the same text in both instances. # Example: # we want to compare two related activationkeys: # "1-rhel6-x86_64-dev" and "1-rhel6-x86_64-prd" # ("dev" for "development" and "prd" for "production"). # We assume that the "dev" activationkey "1-rhel6-x86_64-dev" # has references to other "dev" components, # while the "prd" activationkey "1-rhel6-x86_64-prd" # has references to other "prd" components. # Therefore we replace all occurrences of "dev" in "1-rhel6-x86_64-dev" # and all occurrences of "prd" in "1-rhel6-x86_64-prd" # with the common string "DIFF(dev|prd)". # What differences are to be replaced is guessed # from the name differences of there components. # This will not work always, but it help in a lot of cases. normalized_text = [] if text: for st in text: for line in st.split("\n"): if not excludes: normalized_text.append(replace(line, replacedict)) # We do it this way instead of passing a tuple to # line.startswith to allow compatibility with python 2.4 elif not [e for e in excludes if line.startswith(e)]: normalized_text.append(replace(line, replacedict)) else: logging.debug("excluding line: " + line) return normalized_text def diff(source_data, target_data, source_channel, target_channel): return list(unified_diff(source_data, target_data, source_channel, target_channel)) def file_is_binary(self, path): """Tries to determine whether the file is a binary. Assumes binary if it can't categorize the file as plaintext""" try: process = Popen(["file", "-b", "--mime-type", path], stdout=PIPE) output = process.communicate()[0] exit_code = process.wait() if exit_code != 0: return True if output.startswith("text/"): return False except OSError: pass return True def string_to_bool(input_string): if not isinstance(input_string, bool): return input_string.lower().rstrip(' ') == 'true' return input_string 0707010000002D000041FD0000000000000000000000015D65A5B200000000000000000000000000000000000000000000000F00000000spacecmd/tests0707010000002E000081B40000000000000000000000015D65A5B20000070C000000000000000000000000000000000000001A00000000spacecmd/tests/helpers.py# coding: utf-8 """ Testing helpers """ import time import pytest import hashlib from mock import MagicMock from io import StringIO class FileHandleMock(StringIO): """ Filehandle mock """ def __init__(self): self._init_params = None StringIO.__init__(self) self._closed = False def __call__(self, *args, **kwargs): self.__init_params = args, kwargs return self def close(self): """ Bypass closing. """ self._closed = True def get_content(self): """ Get file content. """ self.seek(0) return self.read() def get_init_args(self): """ Get initial arguments. """ return self.__init_params[0] def get_init_kwargs(self): """ Get initial keywords. """ return self.__init_params[1] @pytest.fixture def shell(): """ Create fake shell. """ base = MagicMock() base.session = hashlib.sha256(str(time.time()).encode("utf-8")).hexdigest() base.client = MagicMock() base.client.activationkey = MagicMock() base.do_activationkey_list = MagicMock(return_value="do_activation_list") return base def assert_expect(calls, *expectations): """ Check expectations. Function accepts a list of calls of some mock and the corresponding expectations. Result is counted as passed, when calls are identical to the expectations and no more, no less. Otherwise an assertion error is raised. :param calls: Mock's call_args_list :param expectations: expectations array. """ expectations = list(expectations) for call in calls: assert call[0][0] == next(iter(expectations)) expectations.pop(0) assert not expectations 0707010000002F000081B40000000000000000000000015D65A5B200015B09000000000000000000000000000000000000002500000000spacecmd/tests/test_activationkey.py# coding: utf-8 """ Test activation key methods. """ from mock import MagicMock, patch import pytest import time import hashlib import spacecmd.activationkey from xmlrpc import client as xmlrpclib from helpers import shell class TestSCActivationKey: """ Test activation key. """ def test_completer_ak_addpackages(self, shell): """ Test tab completer activation keys on addpackages. """ text = "Communications satellite used by the military for star wars." completer = MagicMock() with patch("spacecmd.activationkey.tab_completer", completer): spacecmd.activationkey.complete_activationkey_addpackages(shell, text, "do this", None, None) assert completer.called call_id, ret_text = completer.call_args_list[0][0] assert call_id == "do_activation_list" assert ret_text == text class TestSCActivationKeyMethods: """ Test actuvation key methods. """ def test_do_activationkey_addpackages_noargs(self, shell): """ Test add packages method call shows help on no args. """ shell.help_activationkey_addpackages = MagicMock() shell.client.activationkey.addPackages = MagicMock() spacecmd.activationkey.do_activationkey_addpackages(shell, "") assert shell.help_activationkey_addpackages.called def test_do_activationkey_addpackages_help_args(self, shell): """ Test add packages method call shows help on help args passed. """ shell.help_activationkey_addpackages = MagicMock() shell.client.activationkey.addPackages = MagicMock() spacecmd.activationkey.do_activationkey_addpackages(shell, "help") assert shell.help_activationkey_addpackages.called def test_do_activationkey_addpackages_args(self, shell): """ Test add packages method call shows help on args passed. """ shell.help_activationkey_addpackages = MagicMock() shell.client.activationkey.addPackages = MagicMock() spacecmd.activationkey.do_activationkey_addpackages(shell, "call something here") assert not shell.help_activationkey_addpackages.called assert shell.client.activationkey.addPackages.called session, fun, args = shell.client.activationkey.addPackages.call_args_list[0][0] assert session == shell.session assert fun == "call" assert isinstance(args, list) assert len(args) == 2 for arg in args: assert arg["name"] in ["something", "here"] def test_do_activationkey_removepackages_noargs(self, shell): """ Test remove packages method call shows help on no args. """ shell.help_activationkey_removepackages = MagicMock() shell.client.activationkey.removePackages = MagicMock() # TODO: Add help for remove packages! spacecmd.activationkey.do_activationkey_removepackages(shell, "") assert not shell.help_activationkey_removePackages.called def test_do_activationkey_removepackages_help_args(self, shell): """ Test remove packages method call shows help if only one argument is passed. """ shell.help_activationkey_removepackages = MagicMock() shell.client.activationkey.removePackages = MagicMock() spacecmd.activationkey.do_activationkey_removepackages(shell, "key") assert shell.help_activationkey_removepackages.called def test_do_activationkey_removepackages_args(self, shell): """ Test remove packages method calls "removePackages" API call. """ shell.help_activationkey_removepackages = MagicMock() shell.client.activationkey.removePackages = MagicMock() spacecmd.activationkey.do_activationkey_removepackages(shell, "key package") assert not shell.help_activationkey_removepackages.called assert shell.client.activationkey.removePackages.called session, fun, args = shell.client.activationkey.removePackages.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert "name" in args[0] assert args[0]["name"] == "package" def test_do_activationkey_addgroups_noargs(self, shell): """ Test addgroup without args calls help. """ shell.help_activationkey_addgroups = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() spacecmd.activationkey.do_activationkey_addgroups(shell, "") assert shell.help_activationkey_addgroups.called def test_do_activationkey_addgroups_help_args(self, shell): """ Test add groups method call shows help if only one argument is passed. """ shell.help_activationkey_addgroups = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() spacecmd.activationkey.do_activationkey_addgroups(shell, "key") assert shell.help_activationkey_addgroups.called def test_do_activationkey_addgroups_args(self, shell): """ Test "addgroups" method calls "addServerGroups" API call. """ shell.help_activationkey_addgroups = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() shell.client.systemgroup.getDetails = MagicMock(return_value={"id": 42}) spacecmd.activationkey.do_activationkey_addgroups(shell, "key group") assert not shell.help_activationkey_addgroups.called assert shell.client.activationkey.addServerGroups.called session, fun, args = shell.client.activationkey.addServerGroups.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert args == [42] def test_do_activationkey_removegroups_noargs(self, shell): """ Test removegroup without args calls help. """ shell.help_activationkey_removegroups = MagicMock() shell.client.activationkey.removeServerGroups = MagicMock() spacecmd.activationkey.do_activationkey_removegroups(shell, "") assert shell.help_activationkey_removegroups.called def test_do_activationkey_removegroups_help_args(self, shell): """ Test remove groups method call shows help if only one argument is passed. """ shell.help_activationkey_removegroups = MagicMock() shell.client.activationkey.removeServerGroups = MagicMock() spacecmd.activationkey.do_activationkey_removegroups(shell, "key") assert shell.help_activationkey_removegroups.called assert not shell.client.activationkey.removeServerGroups.called def test_do_activationkey_removegroups_args(self, shell): """ Test "removegroups" method calls "removeServerGroups" API call. """ shell.help_activationkey_removegroups = MagicMock() shell.client.activationkey.removeServerGroups = MagicMock() shell.client.systemgroup.getDetails = MagicMock(return_value={"id": 42}) spacecmd.activationkey.do_activationkey_removegroups(shell, "key group") assert not shell.help_activationkey_removegroups.called assert shell.client.activationkey.removeServerGroups.called session, fun, args = shell.client.activationkey.removeServerGroups.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert args == [42] def test_do_activationkey_addentitlements_noargs(self, shell): """ Test addentitlements without args calls help. """ shell.help_activationkey_addentitlements = MagicMock() shell.client.activationkey.addEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_addentitlements(shell, "") assert shell.help_activationkey_addentitlements.called assert not shell.client.activationkey.addEntitlements.called def test_do_activationkey_addentitlements_help_args(self, shell): """ Test addentitlements method call shows help if only one argument is passed. """ shell.help_activationkey_addentitlements = MagicMock() shell.client.activationkey.addEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_addentitlements(shell, "key") assert shell.help_activationkey_addentitlements.called assert not shell.client.activationkey.addEntitlements.called def test_do_activationkey_addentitlements_args(self, shell): """ Test "addentitlements" method calls "addEntitlements" API call. """ shell.help_activationkey_addentitlements = MagicMock() shell.client.activationkey.addEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_addentitlements(shell, "key entitlement") assert not shell.help_activationkey_addentitlements.called assert shell.client.activationkey.addEntitlements.called session, fun, args = shell.client.activationkey.addEntitlements.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert args == ['entitlement'] def test_do_activationkey_addentitlements_noargs(self, shell): """ Test addentitlements without args calls help. """ shell.help_activationkey_addentitlements = MagicMock() shell.client.activationkey.addEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_addentitlements(shell, "") assert shell.help_activationkey_addentitlements.called assert not shell.client.activationkey.addEntitlements.called def test_do_activationkey_addentitlements_help_args(self, shell): """ Test addentitlements method call shows help if only one argument is passed. """ shell.help_activationkey_addentitlements = MagicMock() shell.client.activationkey.addEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_addentitlements(shell, "key") assert shell.help_activationkey_addentitlements.called assert not shell.client.activationkey.addEntitlements.called def test_do_activationkey_addentitlements_args(self, shell): """ Test "addentitlements" method calls "addEntitlements" API call. """ shell.help_activationkey_addentitlements = MagicMock() shell.client.activationkey.addEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_addentitlements(shell, "key entitlement") assert not shell.help_activationkey_addentitlements.called assert shell.client.activationkey.addEntitlements.called session, fun, args = shell.client.activationkey.addEntitlements.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert args == ['entitlement'] def test_do_activationkey_removeentitlements_noargs(self, shell): """ Test removeentitlements without args calls help. """ shell.help_activationkey_removeentitlements = MagicMock() shell.client.activationkey.removeEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_removeentitlements(shell, "") assert shell.help_activationkey_removeentitlements.called assert not shell.client.activationkey.removeEntitlements.called def test_do_activationkey_removeentitlements_help_args(self, shell): """ Test removeentitlements method call shows help if only one argument is passed. """ shell.help_activationkey_removeentitlements = MagicMock() shell.client.activationkey.removeEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_removeentitlements(shell, "key") assert shell.help_activationkey_removeentitlements.called assert not shell.client.activationkey.removeEntitlements.called def test_do_activationkey_removeentitlements_args(self, shell): """ Test "removeentitlements" method calls "removeEntitlements" API call. """ shell.help_activationkey_removeentitlements = MagicMock() shell.client.activationkey.removeEntitlements = MagicMock() spacecmd.activationkey.do_activationkey_removeentitlements(shell, "key entitlement") assert not shell.help_activationkey_removeentitlements.called assert shell.client.activationkey.removeEntitlements.called session, fun, args = shell.client.activationkey.removeEntitlements.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert args == ['entitlement'] def test_do_activationkey_addchildchannels_noargs(self, shell): """ Test addchildchannels without args calls help. """ shell.help_activationkey_addchildchannels = MagicMock() shell.client.activationkey.addChildChannels = MagicMock() spacecmd.activationkey.do_activationkey_addchildchannels(shell, "") assert shell.help_activationkey_addchildchannels.called assert not shell.client.activationkey.addChildChannels.called def test_do_activationkey_addchildchannels_help_args(self, shell): """ Test addchildchannels method call shows help if only one argument is passed. """ shell.help_activationkey_addchildchannels = MagicMock() shell.client.activationkey.addChildChannels = MagicMock() spacecmd.activationkey.do_activationkey_addchildchannels(shell, "key") assert shell.help_activationkey_addchildchannels.called assert not shell.client.activationkey.addChildChannels.called def test_do_activationkey_addchildchannels_args(self, shell): """ Test "addchildchannels" method calls "addChildChannels" API call. """ shell.help_activationkey_addchildchannels = MagicMock() shell.client.activationkey.addChildChannels = MagicMock() spacecmd.activationkey.do_activationkey_addchildchannels(shell, "key some_channel") assert not shell.help_activationkey_addchildchannels.called assert shell.client.activationkey.addChildChannels.called session, fun, args = shell.client.activationkey.addChildChannels.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert args == ['some_channel'] def test_do_activationkey_removechildchannels_noargs(self, shell): """ Test removechildchannels without args calls help. """ shell.help_activationkey_removechildchannels = MagicMock() shell.client.activationkey.removeChildChannels = MagicMock() spacecmd.activationkey.do_activationkey_removechildchannels(shell, "") assert shell.help_activationkey_removechildchannels.called assert not shell.client.activationkey.removeChildChannels.called def test_do_activationkey_removechildchannels_help_args(self, shell): """ Test removechildchannels method call shows help if only one argument is passed. """ shell.help_activationkey_removechildchannels = MagicMock() shell.client.activationkey.removeChildChannels = MagicMock() spacecmd.activationkey.do_activationkey_removechildchannels(shell, "key") assert shell.help_activationkey_removechildchannels.called assert not shell.client.activationkey.removeChildChannels.called def test_do_activationkey_removechildchannels_args(self, shell): """ Test "removechildchannels" method calls "removeChildChannels" API call. """ shell.help_activationkey_removechildchannels = MagicMock() shell.client.activationkey.removeChildChannels = MagicMock() spacecmd.activationkey.do_activationkey_removechildchannels(shell, "key some_channel") assert not shell.help_activationkey_removechildchannels.called assert shell.client.activationkey.removeChildChannels.called session, fun, args = shell.client.activationkey.removeChildChannels.call_args_list[0][0] assert session == shell.session assert fun == "key" assert isinstance(args, list) assert len(args) == 1 assert args == ['some_channel'] def test_do_activationkey_listchildchannels_noargs(self, shell): """ Test listchildchannels command triggers help on no args """ shell.help_activationkey_listchildchannels = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value={"child_channel_labels"}) spacecmd.activationkey.do_activationkey_listchildchannels(shell, "") assert shell.help_activationkey_listchildchannels.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_listchildchannels_args(self, shell): """ Test listchildchannels command prints child channels by the activation key passed. """ shell.help_activationkey_listchildchannels = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value={ "child_channel_labels": ["one", "two", "three"] }) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_listchildchannels(shell, "key") assert mprint.call_args_list[0][0][0] == "one\nthree\ntwo" # Sorted def test_do_activationkey_listbasechannel_noargs(self, shell): """ Test listbasechannels command triggers help on no args """ shell.help_activationkey_listbasechannel = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value={"base_channel_label"}) spacecmd.activationkey.do_activationkey_listbasechannel(shell, "") assert shell.help_activationkey_listbasechannel.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_listbasechannel_args(self, shell): """ Test listbasechannels command prints base channel by the activation key passed. """ shell.help_activationkey_listbasechannel = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value={ "base_channel_label": "Darth Vader", }) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_listbasechannel(shell, "key") assert mprint.call_args_list[0][0][0] == "Darth Vader" def test_do_activationkey_listgroups_noargs(self, shell): """ Test listgroups command triggers help on no args """ shell.help_activationkey_listgroups = MagicMock() shell.client.activationkey.getDetails = MagicMock() shell.client.systemgroup.getDetails = MagicMock(site_effect=[{"name": "RD-2D"}, {"name": "C-3PO"}]) spacecmd.activationkey.do_activationkey_listgroups(shell, "") assert shell.help_activationkey_listgroups.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_listgroups_args(self, shell): """ Test listgroups command prints groups by the activation key passed. """ shell.help_activationkey_listgroups = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value={"server_group_ids": [2, 3]}) shell.client.systemgroup.getDetails = MagicMock(side_effect=[{"name": "RD-2D"}, {"name": "C-3PO"}]) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_listgroups(shell, "key") assert len(mprint.call_args_list) == 2 assert mprint.call_args_list[0][0][0] == "RD-2D" assert mprint.call_args_list[1][0][0] == "C-3PO" def test_do_activationkey_listentitlements_noargs(self, shell): """ Test listentitlements command triggers help on no args """ shell.help_activationkey_listentitlements = MagicMock() shell.client.activationkey.getDetails = MagicMock() spacecmd.activationkey.do_activationkey_listentitlements(shell, "") assert shell.help_activationkey_listentitlements.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_listentitlements_args(self, shell): """ Test listentitlements command prints entitlements by the activation key passed. """ shell.help_activationkey_listentitlements = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value={"entitlements": ["one", "two", "three"]}) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_listentitlements(shell, "key") assert mprint.call_args_list[0][0][0] == 'one\ntwo\nthree' def test_do_activationkey_listpackages_noargs(self, shell): """ Test listpackages command triggers help on no args """ shell.help_activationkey_listpackages = MagicMock() shell.client.activationkey.getDetails = MagicMock() spacecmd.activationkey.do_activationkey_listpackages(shell, "") assert shell.help_activationkey_listpackages.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_listpackages_args_arch(self, shell): """ Test listpackages command prints packages by the activation key passed with the arch included. """ shell.help_activationkey_listpackages = MagicMock() shell.client.activationkey.getDetails = MagicMock( return_value={"packages": [ {"name": "libzypp", "arch": "ZX80"}, {"name": "java-11-openjdk-devel", "arch": "CBM64"}, ]} ) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_listpackages(shell, "key") assert mprint.called assert len(mprint.call_args_list) == 2 # keep ordering assert mprint.call_args_list[0][0][0] == "libzypp.ZX80" assert mprint.call_args_list[1][0][0] == "java-11-openjdk-devel.CBM64" def test_do_activationkey_listpackages_args_noarch(self, shell): """ Test listpackages command prints packages by the activation key passed without architecture included. """ shell.help_activationkey_listpackages = MagicMock() shell.client.activationkey.getDetails = MagicMock( return_value={"packages": [ {"name": "libzypp"}, {"name": "java-11-openjdk-devel"}, ]} ) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_listpackages(shell, "key") assert mprint.called assert len(mprint.call_args_list) == 2 # keep ordering assert mprint.call_args_list[0][0][0] == "libzypp" assert mprint.call_args_list[1][0][0] == "java-11-openjdk-devel" def test_do_activationkey_listconfigchannels_noargs(self, shell): """ Test listconfigchannels command triggers help on no args """ shell.help_activationkey_listconfigchannels = MagicMock() shell.client.activationkey.listConfigChannels = MagicMock() spacecmd.activationkey.do_activationkey_listconfigchannels(shell, "") assert shell.help_activationkey_listconfigchannels.called assert not shell.client.activationkey.listConfigChannels.called def test_do_activationkey_listconfigchannels_args(self, shell): """ Test listconfigchannels command prints entitlements by the activation key passed. """ channels = [ {"label": "commodore64"}, {"label": "pascal_for_msdos"}, {"label": "lightsaber_patches"} ] shell.help_activationkey_listconfigchannels = MagicMock() shell.client.activationkey.listConfigChannels = MagicMock(return_value=channels) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_listconfigchannels(shell, "key") assert mprint.called assert mprint.call_args_list[0][0][0] == 'commodore64\nlightsaber_patches\npascal_for_msdos' def test_do_activationkey_addconfigchannels_noargs(self, shell): """ Test addconfigchannels command triggers help on no args. """ shell.help_activationkey_addconfigchannels = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() spacecmd.activationkey.do_activationkey_addconfigchannels(shell, "") assert shell.help_activationkey_addconfigchannels.called assert not shell.client.activationkey.addConfigChannels.called def test_do_activationkey_addconfigchannels_unknown_noargs(self, shell): """ Test addconfigchannels command raises an Exception on unknown passed args. """ shell.help_activationkey_addconfigchannels = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() with pytest.raises(Exception) as exc: spacecmd.activationkey.do_activationkey_addconfigchannels(shell, "--you-shall-not-pass=True") assert "unrecognized arguments" in str(exc) assert not shell.help_activationkey_addconfigchannels.called assert not shell.client.activationkey.addConfigChannels.called @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) def test_do_activationkey_addconfigchannels_check_args_noninteractive(self, shell): """ Test addconfigchannels command calls addConfigChannels API function on params added. """ shell.help_activationkey_addconfigchannels = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() spacecmd.activationkey.do_activationkey_addconfigchannels(shell, "key rd2d-upgrade -b") assert not shell.help_activationkey_addconfigchannels.called assert shell.client.activationkey.addConfigChannels.called session, keys, channels, order = shell.client.activationkey.addConfigChannels.call_args_list[0][0] assert shell.session == session assert len(keys) == len(channels) == 1 assert "key" in keys assert "rd2d-upgrade" in channels assert bool == type(order) assert not order shell.client.activationkey.addConfigChannels = MagicMock() spacecmd.activationkey.do_activationkey_addconfigchannels(shell, "key rd2d-upgrade") session, keys, channels, order = shell.client.activationkey.addConfigChannels.call_args_list[0][0] assert shell.session == session assert len(keys) == len(channels) == 1 assert "key" in keys assert "rd2d-upgrade" in channels assert bool == type(order) assert order shell.client.activationkey.addConfigChannels = MagicMock() spacecmd.activationkey.do_activationkey_addconfigchannels(shell, "key rd2d-upgrade -t") session, keys, channels, order = shell.client.activationkey.addConfigChannels.call_args_list[0][0] assert shell.session == session assert len(keys) == len(channels) == 1 assert "key" in keys assert "rd2d-upgrade" in channels assert bool == type(order) assert order def test_do_activationkey_removeconfigchannels_noargs(self, shell): """ Test removeconfigchannels command triggers help on no args. """ shell.help_activationkey_removeconfigchannels = MagicMock() shell.client.activationkey.removeConfigChannels = MagicMock() spacecmd.activationkey.do_activationkey_removeconfigchannels(shell, "") assert shell.help_activationkey_removeconfigchannels.called assert not shell.client.activationkey.removeConfigChannels.called def test_do_activationkey_removeconfigchannels_insuff_args(self, shell): """ Test removeconfigchannels command triggers help on insufficient args. """ shell.help_activationkey_removeconfigchannels = MagicMock() shell.client.activationkey.removeConfigChannels = MagicMock() spacecmd.activationkey.do_activationkey_removeconfigchannels(shell, "key") assert shell.help_activationkey_removeconfigchannels.called assert not shell.client.activationkey.removeConfigChannels.called def test_do_activationkey_removeconfigchannels_args(self, shell): """ Test removeconfigchannels command is calling removeConfigChannels API by the activation key passed. """ shell.help_activationkey_removeconfigchannels = MagicMock() shell.client.activationkey.removeConfigChannels = MagicMock() mprint = MagicMock() spacecmd.activationkey.do_activationkey_removeconfigchannels(shell, "key some_patches") assert not shell.help_activationkey_removeconfigchannels.called assert shell.client.activationkey.removeConfigChannels.called session, keys, channels = shell.client.activationkey.removeConfigChannels.call_args_list[0][0] assert shell.session == session assert "key" in keys assert "some_patches" in channels assert len(keys) == len(channels) == 1 @patch("spacecmd.activationkey.config_channel_order", MagicMock(return_value=["lightsaber_patches", "rd2d_upgrade"])) def test_do_activationkey_setconfigchannelorder_noargs(self, shell): """ Test setconfigchannelorder command triggers help on no args. """ for cmd in [""]: shell.help_activationkey_setconfigchannelorder = MagicMock() shell.client.activationkey.listConfigChannels = MagicMock() shell.client.activationkey.setConfigChannels = MagicMock() shell.do_configchannel_list = MagicMock() spacecmd.activationkey.do_activationkey_setconfigchannelorder(shell, cmd) assert shell.help_activationkey_setconfigchannelorder.called assert not shell.client.activationkey.setConfigChannels.called @patch("spacecmd.activationkey.config_channel_order", MagicMock(return_value=["lightsaber_patches", "rd2d_upgrade"])) def test_do_activationkey_setconfigchannelorder_args(self, shell): """ Test setconfigchannelorder command triggers setConfigChannels API call with proper function """ shell.help_activationkey_setconfigchannelorder = MagicMock() shell.client.activationkey.listConfigChannels = MagicMock() shell.client.activationkey.setConfigChannels = MagicMock() shell.do_configchannel_list = MagicMock() mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint): spacecmd.activationkey.do_activationkey_setconfigchannelorder(shell, "key") assert not shell.help_activationkey_setconfigchannelorder.called assert shell.client.activationkey.setConfigChannels.called assert len(mprint.call_args_list) == 4 assert mprint.call_args_list[2][0][0] == "[1] lightsaber_patches" assert mprint.call_args_list[3][0][0] == "[2] rd2d_upgrade" @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) def test_do_activationkey_create_nointeract_argstest(self, shell): """ Test call activation key API "create". """ shell.client.activationkey.create = MagicMock(return_value="superglue") shell.list_base_channels = MagicMock(return_value=["lightsaber_patches_sle42sp8"]) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.do_activationkey_create(shell, "") assert logger.info.called assert shell.client.activationkey.create.called assert logger.info.call_args_list[0][0][0] == "Created activation key superglue" session, name, descr, bch, entl, universal = shell.client.activationkey.create.call_args_list[0][0] assert shell.session == session assert name == descr == bch == "" assert entl == [] assert not universal shell.client.activationkey.create = MagicMock(return_value="woodblock") shell.list_base_channels = MagicMock(return_value=["lightsaber_patches_sle42sp8"]) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.do_activationkey_create( shell, ("--name lightsaber --description 'The signature weapon of the Jedi Order' " "--base-channel lightsaber_patches_sle42sp8 --entitlements expanded,universe " "--universal")) assert logger.info.called assert shell.client.activationkey.create.called assert logger.info.call_args_list[0][0][0] == "Created activation key woodblock" session, name, descr, bch, entl, universal = shell.client.activationkey.create.call_args_list[0][0] assert shell.session == session assert name == "lightsaber" assert "Jedi Order" in descr assert bch == "lightsaber_patches_sle42sp8" assert entl == ["expanded", "universe"] assert universal def test_do_activationkey_activationkey_delete_insuff_args(self, shell): """ Test activationkey_delete command triggers help on insufficient args. """ shell.help_activationkey_delete = MagicMock() shell.client.activationkey.delete = MagicMock() spacecmd.activationkey.do_activationkey_delete(shell, "") assert shell.help_activationkey_delete.called assert not shell.client.activationkey.delete.called @patch("spacecmd.activationkey.filter_results", MagicMock(return_value=["some_patches", "some_stuff"])) def test_do_activationkey_activationkey_delete_args(self, shell): """ Test activationkey_delete command is calling "delete" (key) API. """ shell.help_activationkey_delete = MagicMock() shell.client.activationkey.delete = MagicMock() mprint = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.print", mprint) as mpr, \ patch("spacecmd.activationkey.logging", logger) as lgr: spacecmd.activationkey.do_activationkey_delete(shell, "key some*") assert not shell.help_activationkey_delete.called assert not logger.error.called assert logger.debug.called assert logger.debug.call_args_list[0][0][0] == ("activationkey_delete called with args" " ['key', 'some.*'], keys=['some_patches', 'some_stuff']") assert logger.debug.call_args_list[1][0][0] == "Deleting key some_patches" assert logger.debug.call_args_list[2][0][0] == "Deleting key some_stuff" assert shell.client.activationkey.delete.called keys = ["some_stuff", "some_patches"] for call in shell.client.activationkey.delete.call_args_list: session, keyname = call[0] assert shell.session == session assert keyname in keys keys.pop(keys.index(keyname)) assert not keys @patch("spacecmd.activationkey.filter_results", MagicMock(return_value=["some_patches", "some_stuff"])) def test_do_activationkey_activationkey_list_args(self, shell): """ Test activationkey_list command is calling listActivationKeys API. """ shell.client.activationkey.listActivationKeys = MagicMock( return_value=[ {"key": "some_patches", "description": "Some key"}, {"key": "some_stuff", "description": "Some other key"}, {"key": "some_reactivation", "description": "Kickstart re-activation key"}, ] ) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint) as mpr: ret = sorted(spacecmd.activationkey.do_activationkey_list(shell, "key some*", doreturn=True)) assert len(ret) == 2 assert ret == ['some_patches', 'some_stuff'] def test_do_activationkey_listsystems_noargs(self, shell): """ Test activationkey_listsystems command is invoking help message on insufficient arguments. """ shell.help_activationkey_listsystems = MagicMock() shell.client.activationkey.listActivatedSystems = MagicMock() spacecmd.activationkey.do_activationkey_listsystems(shell, "") assert shell.help_activationkey_listsystems.called assert not shell.client.activationkey.listActivatedSystems.called def test_do_activationkey_listsystems_args(self, shell): """ Test activationkey_listsystems command is calling listActivatedSystems API function. """ shell.help_activationkey_listsystems = MagicMock() shell.client.activationkey.listActivatedSystems = MagicMock( return_value=[ {"hostname": "chair.lan"}, {"hostname": "houseshoe.lan"}, ] ) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint) as mpr: spacecmd.activationkey.do_activationkey_listsystems(shell, "key") assert not shell.help_activationkey_listsystems.called assert shell.client.activationkey.listActivatedSystems.called assert mprint.called assert mprint.call_args_list[0][0][0] == 'chair.lan\nhouseshoe.lan' def test_do_activationkey_details_noargs(self, shell): """ Test activationkey_details shows a help screen if no sufficient arguments has been passed. """ shell.help_activationkey_details = MagicMock() shell.client.activationkey.getDetails = MagicMock() shell.client.activationkey.listConfigChannels = MagicMock() shell.client.activationkey.checkConfigDeployment = MagicMock() shell.client.systemgroup.getDetails = MagicMock() spacecmd.activationkey.do_activationkey_listsystems(shell, "") assert shell.help_activationkey_listsystems.called assert not shell.client.activationkey.getDetails.called assert not shell.client.systemgroup.getDetails.called assert not shell.client.activationkey.listConfigChannels.called assert not shell.client.activationkey.checkConfigDeployment.called def test_do_activationkey_details_args(self, shell): """ Test activationkey_details returns key details if proper arguments passed. """ shell.help_activationkey_details = MagicMock() shell.client.activationkey.getDetails = MagicMock( return_value={ "key": "somekey", "description": "Key description", "universal_default": "yes", "usage_limit": "42", "contact_method": "230V/AC", "server_group_ids": ["a", "b", "c"], "child_channel_labels": ["child_channel_1", "child_channel_2"], "entitlements": ["entitlement_1", "entitlement_2"], "packages": [{"name": "emacs"}], "base_channel_label": "base_channel", } ) shell.client.activationkey.listConfigChannels = MagicMock( return_value=[ {"label": "somekey_config_channel_1"}, {"label": "somekey_config_channel_2"}, ] ) shell.client.activationkey.checkConfigDeployment = MagicMock(return_value=0) shell.client.systemgroup.getDetails = MagicMock( side_effect=[ {"name": "group_a"}, {"name": "group_b"}, {"name": "group_c"}, ] ) mprint = MagicMock() with patch("spacecmd.activationkey.print", mprint) as mpr: out = spacecmd.activationkey.do_activationkey_details(shell, "somekey") assert not mprint.called expectation = ['Key: somekey', 'Description: Key description', 'Universal Default: yes', 'Usage Limit: 42', 'Deploy Config Channels: False', 'Contact Method: 230V/AC', '', 'Software Channels', '-----------------', 'base_channel', ' |-- child_channel_1', ' |-- child_channel_2', '', 'Configuration Channels', '----------------------', 'somekey_config_channel_1', 'somekey_config_channel_2', '', 'Entitlements', '------------', 'entitlement_1\nentitlement_2', '', 'System Groups', '-------------', 'group_a\ngroup_b\ngroup_c', '', 'Packages', '--------', 'emacs'] assert len(out) == len(expectation) for idx, line in enumerate(out): assert line == expectation[idx] def test_do_activationkey_enableconfigdeployment_noargs(self, shell): """ Test activationkey_enableconfigdeployment command is invoking help message on insufficient arguments. """ shell.help_activationkey_enableconfigdeployment = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() spacecmd.activationkey.do_activationkey_enableconfigdeployment(shell, "") assert shell.help_activationkey_enableconfigdeployment.called assert not shell.client.activationkey.enableConfigDeployment.called def test_do_activationkey_enableconfigdeployment_args(self, shell): """ Test activationkey_enableconfigdeployment command is invoking enableConfigDeployment API call. """ shell.help_activationkey_enableconfigdeployment = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.do_activationkey_enableconfigdeployment(shell, "foo bar baz") assert logger.debug.called assert logger.debug.call_args_list[0][0][0] == "Enabling config file deployment for foo" assert logger.debug.call_args_list[1][0][0] == "Enabling config file deployment for bar" assert logger.debug.call_args_list[2][0][0] == "Enabling config file deployment for baz" keynames = ["foo", "bar", "baz"] assert shell.client.activationkey.enableConfigDeployment.called for call in shell.client.activationkey.enableConfigDeployment.call_args_list: session, keyname = call[0] assert shell.session == session assert keyname in keynames keynames.pop(keynames.index(keyname)) assert keynames == [] def test_do_activationkey_disableconfigdeployment_noargs(self, shell): """ Test activationkey_disableconfigdeployment command is invoking help message on insufficient arguments. """ shell.help_activationkey_disableconfigdeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() spacecmd.activationkey.do_activationkey_disableconfigdeployment(shell, "") assert shell.help_activationkey_disableconfigdeployment.called assert not shell.client.activationkey.disableConfigDeployment.called def test_do_activationkey_disableconfigdeployment_args(self, shell): """ Test activationkey_disableconfigdeployment command is invoking disableConfigDeployment API call. """ shell.help_activationkey_disableconfigdeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.do_activationkey_disableconfigdeployment(shell, "foo bar baz") assert logger.debug.called assert logger.debug.call_args_list[0][0][0] == "Disabling config file deployment for foo" assert logger.debug.call_args_list[1][0][0] == "Disabling config file deployment for bar" assert logger.debug.call_args_list[2][0][0] == "Disabling config file deployment for baz" keynames = ["foo", "bar", "baz"] assert shell.client.activationkey.disableConfigDeployment.called for call in shell.client.activationkey.disableConfigDeployment.call_args_list: session, keyname = call[0] assert shell.session == session assert keyname in keynames keynames.pop(keynames.index(keyname)) assert keynames == [] def test_do_activationkey_addconfigchannels_setbasechannel_noargs(self, shell): """ Test do_activationkey_addconfigchannels_setbasechannel involes help message on issuficient arguments. """ shell.help_activationkey_setbasechannel = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.client.activationkey.getDetails = MagicMock() spacecmd.activationkey.do_activationkey_setbasechannel(shell, "") assert shell.help_activationkey_setbasechannel.called assert not shell.client.activationkey.setDetails.called assert not shell.client.activationkey.getDetails.called spacecmd.activationkey.do_activationkey_setbasechannel(shell, "key") assert shell.help_activationkey_setbasechannel.called assert not shell.client.activationkey.setDetails.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_addconfigchannels_setbasechannel_args(self, shell): """ Test do_activationkey_addconfigchannels_setbasechannel calls setDetails API call on proper args. """ key_details = { "base_channel_label": "death_star_channel", "description": "Darth Vader's base channel", "usage_limit": 42, "universal_default": True, } shell.help_activationkey_setbasechannel = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value=key_details) spacecmd.activationkey.do_activationkey_setbasechannel(shell, "red_key death_star_patches_channel") session, keyname, details = shell.client.activationkey.setDetails.call_args_list[0][0] assert shell.session == session assert keyname == "red_key" for dkey in ["description", "usage_limit", "universal_default"]: assert dkey in details assert key_details[dkey] == details[dkey] assert details["base_channel_label"] == "death_star_patches_channel" def test_do_activationkey_addconfigchannels_setbasechannel_usage_limit(self, shell): """ Test do_activationkey_addconfigchannels_setbasechannel resets usage limit from 0 to -1. """ key_details = { "base_channel_label": "death_star_channel", "description": "Darth Vader's base channel", "usage_limit": 0, "universal_default": True, } shell.help_activationkey_setbasechannel = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value=key_details) spacecmd.activationkey.do_activationkey_setbasechannel(shell, "red_key death_star_patches_channel") session, keyname, details = shell.client.activationkey.setDetails.call_args_list[0][0] assert shell.session == session assert keyname == "red_key" for dkey in ["description", "universal_default"]: assert dkey in details assert key_details[dkey] == details[dkey] assert details["base_channel_label"] == "death_star_patches_channel" assert details["usage_limit"] == -1 def test_do_activationkey_addconfigchannels_setusagelimit_noargs(self, shell): """ Test do_activationkey_addconfigchannels_setusagelimit involes help message on issuficient arguments. """ shell.help_activationkey_setusagelimit = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.client.activationkey.getDetails = MagicMock() spacecmd.activationkey.do_activationkey_setusagelimit(shell, "") assert shell.help_activationkey_setusagelimit.called assert not shell.client.activationkey.setDetails.called assert not shell.client.activationkey.getDetails.called spacecmd.activationkey.do_activationkey_setusagelimit(shell, "key") assert shell.help_activationkey_setusagelimit.called assert not shell.client.activationkey.setDetails.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_addconfigchannels_setusagelimit_args(self, shell): """ Test do_activationkey_addconfigchannels_setbasechannel resets usage limit from 0 to -1. """ key_details = { "base_channel_label": "death_star_channel", "description": "Darth Vader's base channel", "usage_limit": 0, "universal_default": True, } shell.help_activationkey_setusagelimit = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value=key_details) spacecmd.activationkey.do_activationkey_setusagelimit(shell, "red_key 42") session, keyname, details = shell.client.activationkey.setDetails.call_args_list[0][0] assert shell.session == session assert keyname == "red_key" for dkey in ["description", "universal_default", "base_channel_label"]: assert dkey in details assert key_details[dkey] == details[dkey] assert type(details["usage_limit"]) == int assert details["usage_limit"] == 42 def test_do_activationkey_addconfigchannels_setuniversaldefault_noargs(self, shell): """ Test do_activationkey_addconfigchannels_setuniversaldefault involes help message on issuficient arguments. """ shell.help_activationkey_setuniversaldefault = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.client.activationkey.getDetails = MagicMock() spacecmd.activationkey.do_activationkey_setuniversaldefault(shell, "") assert shell.help_activationkey_setuniversaldefault.called assert not shell.client.activationkey.setDetails.called assert not shell.client.activationkey.getDetails.called def test_do_activationkey_addconfigchannels_setuniversaldefault_args(self, shell): """ Test do_activationkey_addconfigchannels_setuniversaldefault calls API to set the key settings. """ key_details = { "base_channel_label": "death_star_channel", "description": "Darth Vader's base channel", "usage_limit": 42, "universal_default": False, } shell.help_activationkey_setuniversaldefault = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value=key_details) spacecmd.activationkey.do_activationkey_setuniversaldefault(shell, "red_key 42") session, keyname, details = shell.client.activationkey.setDetails.call_args_list[0][0] assert shell.session == session assert keyname == "red_key" for dkey in ["description", "usage_limit", "base_channel_label"]: assert dkey in details assert key_details[dkey] == details[dkey] assert type(details["universal_default"]) == bool assert details["universal_default"] def test_export_activationkey_getdetails_exception_handling(self, shell): """ Test export_activationkey_getdetails XMLRPC failure. """ key_details = { "server_group_ids": [], } logger = MagicMock() shell.client.activationkey.getDetails = MagicMock() shell.client.activationkey.listConfigChannels = MagicMock(side_effect=xmlrpclib.Fault("boom", "box")) shell.client.activationkey.checkConfigDeployment = MagicMock() shell.client.systemgroup.getDetails = MagicMock(return_value=key_details) with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.export_activationkey_getdetails(shell, "") assert logger.debug.call_args_list[1][0][0] == ("activationkey.listConfigChannel threw an exeception, " "setting config_channels=False") def test_export_activationkey_getdetails(self, shell): """ Test export_activationkey_getdetails. """ key_details = { "server_group_ids": [1, 2, 3], } gr_details = [ {"name": "first"}, {"name": "second"}, {"name": "third"}, ] logger = MagicMock() shell.client.activationkey.getDetails = MagicMock(return_value=key_details) shell.client.activationkey.listConfigChannels = MagicMock( return_value=[ {"label": "rd2d_patches_channel"}, {"label": "k_2so_firmware_channel"}, ] ) shell.client.activationkey.checkConfigDeployment = MagicMock(return_value=True) shell.client.systemgroup.getDetails = MagicMock(side_effect=gr_details) with patch("spacecmd.activationkey.logging", logger): out = spacecmd.activationkey.export_activationkey_getdetails(shell, "red_key") { 'server_group_ids': [1, 2, 3], 'config_channels': ['rd2d_patches_channel', 'k_2so_firmware_channel'], 'config_deploy': True, 'server_groups': ['first', 'second', 'third'] } assert "server_group_ids" in out assert out["server_group_ids"] == [1, 2, 3] assert "config_channels" in out for cfg_channel in ['rd2d_patches_channel', 'k_2so_firmware_channel']: assert cfg_channel in out["config_channels"] assert "config_deploy" in out assert out["config_deploy"] assert "server_groups" in out assert len(out["server_groups"]) == 3 for srv_group in ['first', 'second', 'third']: assert srv_group in out["server_groups"] @patch("spacecmd.activationkey.json_dump_to_file", MagicMock()) @patch("spacecmd.activationkey.os.path.isfile", MagicMock(return_value=False)) def test_do_activationkey_export_noarg(self, shell): """ Test do_activationkey_export exports to 'akey_all.json' if not specified otherwise. """ logger = MagicMock() shell.do_activationkey_list = MagicMock(return_value=["one", "two", "three"]) shell.export_activationkey_getdetails = MagicMock(side_effect=[{}, {}, {}]) shell.client.activationkey.checkConfigDeployment = MagicMock(return_value=True) with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.do_activationkey_export(shell, "") assert logger.debug.called assert logger.error.called assert logger.info.called assert logger.error.call_args_list[0][0][0] == "Failed to save exported keys to file: akey_all.json" assert logger.debug.call_args_list[0][0][0] == "About to dump 3 keys to akey_all.json" expectation = [ 'Exporting ALL activation keys to akey_all.json', 'Exporting key one to akey_all.json', 'Exporting key two to akey_all.json', 'Exporting key three to akey_all.json', ] for idx, call in enumerate(logger.info.call_args_list): assert call[0][0] == expectation[idx] @patch("spacecmd.activationkey.json_dump_to_file", MagicMock()) @patch("spacecmd.activationkey.os.path.isfile", MagicMock(return_value=False)) def test_do_activationkey_export_filename_arg(self, shell): """ Test do_activationkey_export exports to 'akey_all.json' if not specified otherwise. """ logger = MagicMock() shell.do_activationkey_list = MagicMock(return_value=["one", "two", "three"]) shell.export_activationkey_getdetails = MagicMock(side_effect=[{}, {}, {}]) shell.client.activationkey.checkConfigDeployment = MagicMock(return_value=True) with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.do_activationkey_export(shell, "-f somefile.json") assert logger.debug.called assert logger.error.called assert logger.info.called assert logger.error.call_args_list[0][0][0] == "Failed to save exported keys to file: somefile.json" assert logger.debug.call_args_list[0][0][0] == "Passed filename do_activationkey_export somefile.json" expectation = [ 'Exporting ALL activation keys to somefile.json', 'Exporting key one to somefile.json', 'Exporting key two to somefile.json', 'Exporting key three to somefile.json', ] for idx, call in enumerate(logger.info.call_args_list): assert call[0][0] == expectation[idx] def test_do_activationkey_import_noargs(self, shell): """ Test activationkey_import command is invoking help message on insufficient arguments. """ shell.help_activationkey_import = MagicMock() shell.import_activationkey_fromdetails = MagicMock(return_value=False) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): spacecmd.activationkey.do_activationkey_import(shell, "") assert shell.help_activationkey_import.called assert not shell.import_activationkey_fromdetails.called assert logger.error.called assert logger.error.call_args_list[0][0][0] == 'No filename passed' def test_do_activationkey_fromdetails_existingkey(self, shell): """ Test import_activationkey_fromdetails does not import anything if key already exist. """ shell.client.activationkey.create = MagicMock() shell.do_activationkey_list = MagicMock(return_value=["somekey"]) shell.client.activationkey.addChildChannels = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() shell.client.activationkey.addPackages = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.import_activationkey_fromdetails(shell, {"key": "somekey"}) assert not shell.client.activationkey.create.called assert shell.do_activationkey_list.called assert not shell.client.activationkey.addChildChannels.called assert not shell.client.activationkey.enableConfigDeployment.called assert not shell.client.activationkey.disableConfigDeployment.called assert not shell.client.activationkey.addConfigChannels.called assert not shell.client.activationkey.addServerGroups.called assert not shell.client.activationkey.addServerGroups.called assert not shell.client.activationkey.addPackages.called assert logger.warning.called assert logger.warning.call_args_list[0][0][0] == 'somekey already exists! Skipping!' assert type(ret) == bool assert not ret def test_do_activationkey_fromdetails_newkey_no_usage_limit(self, shell): """ Test import_activationkey_fromdetails no usage limit. """ shell.client.activationkey.create = MagicMock(return_value={}) shell.do_activationkey_list = MagicMock(return_value=["some_existing_key"]) shell.client.activationkey.addChildChannels = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() shell.client.activationkey.addPackages = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.import_activationkey_fromdetails( shell, { "key": "somekey", "usage_limit": 0, "base_channel_label": "none", "description": "Key description", "entitlements": ["one", "two", "three"], "universal_default": True, } ) assert shell.do_activationkey_list.called assert shell.client.activationkey.create.called assert not shell.client.activationkey.addChildChannels.called assert not shell.client.activationkey.enableConfigDeployment.called assert not shell.client.activationkey.disableConfigDeployment.called assert not shell.client.activationkey.addConfigChannels.called assert not shell.client.activationkey.addServerGroups.called assert not shell.client.activationkey.addServerGroups.called assert not shell.client.activationkey.addPackages.called assert logger.debug.call_args_list[0][0][0] == "Found key somekey, importing as somekey" assert len(shell.client.activationkey.create.call_args_list[0][0]) == 6 session, keyname, descr, basech, entl, udef = shell.client.activationkey.create.call_args_list[0][0] assert shell.session == session assert keyname == "somekey" assert descr == "Key description" assert basech == "" assert entl == ["one", "two", "three"] assert udef def test_do_activationkey_fromdetails_newkey_fixed_usage_limit(self, shell): """ Test import_activationkey_fromdetails usage limit given. """ shell.client.activationkey.create = MagicMock(return_value={}) shell.do_activationkey_list = MagicMock(return_value=["some_existing_key"]) shell.client.activationkey.addChildChannels = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() shell.client.activationkey.addPackages = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.import_activationkey_fromdetails( shell, { "key": "somekey", "usage_limit": 42, "base_channel_label": "none", "description": "Key description", "entitlements": ["one", "two", "three"], "universal_default": True, } ) assert len(shell.client.activationkey.create.call_args_list[0][0]) == 7 session, keyname, descr, basech, ulim, entl, udef = shell.client.activationkey.create.call_args_list[0][0] assert shell.session == session assert keyname == "somekey" assert descr == "Key description" assert basech == "" assert ulim == 42 assert entl == ["one", "two", "three"] assert udef # This is expected: see mock return on API call "create". assert logger.error.call_args_list[0][0][0] == 'Failed to import key somekey' def test_do_activationkey_fromdetails_no_cfgdeploy(self, shell): """ Test import_activationkey_fromdetails no configuration deployment. """ shell.client.activationkey.create = MagicMock(return_value={"name": "whatever"}) shell.do_activationkey_list = MagicMock(return_value=["some_existing_key"]) shell.client.activationkey.addChildChannels = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() shell.client.activationkey.addPackages = MagicMock() shell.client.systemgroup.getDetails = MagicMock(return_value=None) shell.client.systemgroup.create = MagicMock( side_effect=[ {"id": 1}, {"id": 2}, {"id": 3}, ] ) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.import_activationkey_fromdetails( shell, { "key": "somekey", "usage_limit": 42, "base_channel_label": "none", "description": "Key description", "entitlements": ["one", "two", "three"], "universal_default": True, "child_channel_labels": "child_channel_labels", "config_channels": ["config_channel"], "server_groups": ["one", "two", "three"], "packages": ["emacs"], "config_deploy": 0, } ) assert not shell.client.activationkey.enableConfigDeployment.called assert shell.client.activationkey.disableConfigDeployment.called session, keydata = shell.client.activationkey.disableConfigDeployment.call_args_list[0][0] assert shell.session == session assert keydata == shell.client.activationkey.create() def test_do_activationkey_fromdetails_cfgdeploy(self, shell): """ Test import_activationkey_fromdetails configuration deployment was set. """ shell.client.activationkey.create = MagicMock(return_value={"name": "whatever"}) shell.do_activationkey_list = MagicMock(return_value=["some_existing_key"]) shell.client.activationkey.addChildChannels = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() shell.client.activationkey.addPackages = MagicMock() shell.client.systemgroup.getDetails = MagicMock(return_value=None) shell.client.systemgroup.create = MagicMock( side_effect=[ {"id": 1}, {"id": 2}, {"id": 3}, ] ) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.import_activationkey_fromdetails( shell, { "key": "somekey", "usage_limit": 42, "base_channel_label": "none", "description": "Key description", "entitlements": ["one", "two", "three"], "universal_default": True, "child_channel_labels": "child_channel_labels", "config_channels": ["config_channel"], "server_groups": ["one", "two", "three"], "packages": ["emacs"], "config_deploy": 1, } ) assert shell.client.activationkey.enableConfigDeployment.called assert not shell.client.activationkey.disableConfigDeployment.called session, keydata = shell.client.activationkey.enableConfigDeployment.call_args_list[0][0] assert shell.session == session assert keydata == shell.client.activationkey.create() def test_do_activationkey_fromdetails_groups_pkgs(self, shell): """ Test import_activationkey_fromdetails groups and packages processed. """ shell.client.activationkey.create = MagicMock(return_value={"name": "whatever"}) shell.do_activationkey_list = MagicMock(return_value=["some_existing_key"]) shell.client.activationkey.addChildChannels = MagicMock() shell.client.activationkey.enableConfigDeployment = MagicMock() shell.client.activationkey.disableConfigDeployment = MagicMock() shell.client.activationkey.addConfigChannels = MagicMock() shell.client.activationkey.addServerGroups = MagicMock() shell.client.activationkey.addPackages = MagicMock() shell.client.systemgroup.getDetails = MagicMock(return_value=None) shell.client.systemgroup.create = MagicMock( side_effect=[ {"id": 1}, {"id": 2}, {"id": 3}, ] ) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.import_activationkey_fromdetails( shell, { "key": "somekey", "usage_limit": 42, "base_channel_label": "none", "description": "Key description", "entitlements": ["one", "two", "three"], "universal_default": True, "child_channel_labels": "child_channel_labels", "config_channels": ["config_channel"], "server_groups": ["one", "two", "three"], "packages": ["emacs"], "config_deploy": 1, } ) session, keyprm, gids = shell.client.activationkey.addServerGroups.call_args_list[0][0] assert shell.session == session assert keyprm == {'name': 'whatever'} assert gids == [1, 2, 3] session, keyprm, pkgs = shell.client.activationkey.addPackages.call_args_list[0][0] assert pkgs == ["emacs"] @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) @patch("spacecmd.activationkey.prompt_user", MagicMock(side_effect=["original_key", "cloned_key"])) def test_do_activationkey_clone_noargs(self, shell): """ Test do_activationkey_clone no arguments requires some. """ logger = MagicMock() shell.do_activationkey_list = MagicMock() shell.help_activationkey_clone = MagicMock() shell.export_activationkey_getdetails = MagicMock() shell.list_base_channels = MagicMock() shell.list_child_channels = MagicMock() shell.do_configchannel_list = MagicMock() shell.import_activtionkey_fromdetails = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.do_activationkey_clone(shell, "") assert logger.error.called assert shell.help_activationkey_clone.called assert shell.do_activationkey_list.called assert not shell.export_activationkey_getdetails.called assert not shell.list_base_channels.called assert not shell.list_child_channels.called assert not shell.do_configchannel_list.called assert not shell.import_activtionkey_fromdetails.called assert ret is None assert logger.error.call_args_list[0][0][0] == 'Error - must specify either -c or -x options!' @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) @patch("spacecmd.activationkey.prompt_user", MagicMock(side_effect=["original_key", "cloned_key"])) def test_do_activationkey_clone_wrongargs(self, shell): """ Test do_activationkey_clone wrong arguments prompts for correction. """ shell.do_activationkey_list = MagicMock() shell.help_activationkey_clone = MagicMock() shell.export_activationkey_getdetails = MagicMock() shell.list_base_channels = MagicMock() shell.list_child_channels = MagicMock() shell.do_configchannel_list = MagicMock() shell.import_activtionkey_fromdetails = MagicMock() with pytest.raises(Exception) as exc: spacecmd.activationkey.do_activationkey_clone(shell, "--nonsense=true") assert "Exception: unrecognized arguments: --nonsense=true" in str(exc) @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) @patch("spacecmd.activationkey.prompt_user", MagicMock(side_effect=["original_key", "cloned_key"])) def test_do_activationkey_clone_existing_clone_arg(self, shell): """ Test do_activationkey_clone existing clone name passed to the arguments. """ shell.do_activationkey_list = MagicMock(return_value=["key_clone_name"]) shell.help_activationkey_clone = MagicMock() shell.export_activationkey_getdetails = MagicMock() shell.list_base_channels = MagicMock() shell.list_child_channels = MagicMock() shell.do_configchannel_list = MagicMock() shell.import_activtionkey_fromdetails = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.do_activationkey_clone(shell, "-c key_clone_name") assert logger.error.called assert not shell.help_activationkey_clone.called assert shell.do_activationkey_list.called assert not shell.export_activationkey_getdetails.called assert not shell.list_base_channels.called assert not shell.list_child_channels.called assert not shell.do_configchannel_list.called assert not shell.import_activtionkey_fromdetails.called assert logger.error.call_args_list[0][0][0] == "Key key_clone_name already exists" assert ret is None @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) @patch("spacecmd.activationkey.prompt_user", MagicMock(side_effect=["original_key", "cloned_key"])) def test_do_activationkey_clone_noargs_to_clone(self, shell): """ Test do_activationkey_clone no arguments to a clone name passed to the arguments. """ shell.do_activationkey_list = MagicMock(return_value=["key_some_clone_name"]) shell.help_activationkey_clone = MagicMock() shell.export_activationkey_getdetails = MagicMock() shell.list_base_channels = MagicMock() shell.list_child_channels = MagicMock() shell.do_configchannel_list = MagicMock() shell.import_activtionkey_fromdetails = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.do_activationkey_clone(shell, "-c key_clone_name") assert logger.error.called assert shell.help_activationkey_clone.called assert shell.do_activationkey_list.called assert not shell.export_activationkey_getdetails.called assert not shell.list_base_channels.called assert not shell.list_child_channels.called assert not shell.do_configchannel_list.called assert not shell.import_activtionkey_fromdetails.called assert logger.error.call_args_list[0][0][0] == "Error no activationkey to clone passed!" assert ret is None @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) @patch("spacecmd.activationkey.prompt_user", MagicMock(side_effect=["original_key", "cloned_key"])) def test_do_activationkey_clone_keyargs_to_clone_filtered(self, shell): """ Test do_activationkey_clone filtered out keys. """ shell.do_activationkey_list = MagicMock(return_value=["key_some_clone_name"]) shell.help_activationkey_clone = MagicMock() shell.export_activationkey_getdetails = MagicMock() shell.list_base_channels = MagicMock() shell.list_child_channels = MagicMock() shell.do_configchannel_list = MagicMock() shell.import_activtionkey_fromdetails = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.do_activationkey_clone(shell, "orig_key -c key_clone_name") assert not logger.error.called assert not shell.help_activationkey_clone.called assert shell.do_activationkey_list.called assert not shell.export_activationkey_getdetails.called assert not shell.list_base_channels.called assert not shell.list_child_channels.called assert not shell.do_configchannel_list.called assert not shell.import_activtionkey_fromdetails.called expectations = [ "Got args=['orig_key'] 1", "Filtered akeys []", "all akeys ['key_some_clone_name']", ] for idx, call in enumerate(logger.debug.call_args_list): assert call[0][0] == expectations[idx] @patch("spacecmd.activationkey.is_interactive", MagicMock(return_value=False)) @patch("spacecmd.activationkey.prompt_user", MagicMock(side_effect=["original_key", "cloned_key"])) def test_do_activationkey_clone_keyargs_unfiltered(self, shell): """ Test do_activationkey_clone not filtered out keys. """ shell.do_activationkey_list = MagicMock(return_value=["key_some_clone_name"]) shell.help_activationkey_clone = MagicMock() shell.export_activationkey_getdetails = MagicMock() shell.list_base_channels = MagicMock() shell.list_child_channels = MagicMock() shell.do_configchannel_list = MagicMock() shell.import_activtionkey_fromdetails = MagicMock() logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): ret = spacecmd.activationkey.do_activationkey_clone(shell, "key_some* -c key_clone_name") expectations = [ "Got args=['key_some.*'] 1", "Filtered akeys ['key_some_clone_name']", "all akeys ['key_some_clone_name']", "Cloning key_some_clone_name", ] for idx, call in enumerate(logger.debug.call_args_list): assert call[0][0] == expectations[idx] def test_check_activationkey_nokey(self, shell): """ Test check activation key helper returns False on no key. """ shell.is_activationkey = MagicMock(return_value=False) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): assert not spacecmd.activationkey.check_activationkey(shell, "") assert logger.error.called assert logger.error.call_args_list[0][0][0] == 'no activationkey label given' def test_check_activationkey_not_a_key(self, shell): """ Test check activation key helper returns False on not a key. """ shell.is_activationkey = MagicMock(return_value=False) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): assert not spacecmd.activationkey.check_activationkey(shell, "some_not_a_key") assert logger.error.called assert logger.error.call_args_list[0][0][0] == 'invalid activationkey label some_not_a_key' def test_check_activationkey_correct_key(self, shell): """ Test check activation key helper returns True if a key is correct. """ shell.is_activationkey = MagicMock(return_value=True) logger = MagicMock() with patch("spacecmd.activationkey.logging", logger): assert spacecmd.activationkey.check_activationkey(shell, "some_not_a_key") assert not logger.error.called def test_activationkey_diff_noarg(self, shell): """ Test dump activation key helper invokes help message on insufficient arguments. """ for args in ["", "one two three", "some more args here"]: shell.help_activationkey_diff = MagicMock() shell.dump_activationkey = MagicMock() shell.check_activationkey = MagicMock() shell.do_activationkey_getcorresponding = MagicMock() _diff = MagicMock() with patch("spacecmd.activationkey.diff", _diff): spacecmd.activationkey.do_activationkey_diff(shell, "") assert shell.help_activationkey_diff.called assert not shell.dump_activationkey.called assert not shell.check_activationkey.called assert not shell.do_activationkey_getcorresponding.called assert not _diff.called def test_activationkey_diff_arg(self, shell): """ Test dump activation key helper invokes expected sequence of function calls. """ shell.help_activationkey_diff = MagicMock() shell.dump_activationkey = MagicMock() shell.check_activationkey = MagicMock() shell.do_activationkey_getcorresponding = MagicMock() _diff = MagicMock() with patch("spacecmd.activationkey.diff", _diff): spacecmd.activationkey.do_activationkey_diff(shell, "some_key") assert not shell.help_activationkey_diff.called assert shell.dump_activationkey.called assert shell.check_activationkey.called assert shell.do_activationkey_getcorresponding.called assert _diff.called def test_activationkey_diff(self, shell): """ Test dump activation key helper returns key diffs. """ shell.help_activationkey_diff = MagicMock() shell.dump_activationkey = MagicMock(side_effect=[["some data"], ["some data again"]]) shell.check_activationkey = MagicMock(return_value=True) shell.do_activationkey_getcorresponding = MagicMock(return_value="some_channel") gsdd = MagicMock(return_value=({"one": "two", "three": "three"}, {"one": "two", "three": "four"})) with patch("spacecmd.activationkey.get_string_diff_dicts", gsdd): out = spacecmd.activationkey.do_activationkey_diff(shell, "some_key") assert out == ['--- some_key\n', '+++ some_channel\n', '@@ -1 +1 @@\n', '-some data', '+some data again'] def test_do_activationkey_diable_noargs(self, shell): """ Test do_activationkey_disable command triggers help on no args. """ shell.help_activationkey_disable = MagicMock() shell.client.activationkey.setDetails = MagicMock() spacecmd.activationkey.do_activationkey_disable(shell, "") assert shell.help_activationkey_disable.called assert not shell.client.activationkey.setDetails.called def test_do_activationkey_diable_args(self, shell): """ Test do_activationkey_disable command triggers activationkey.setDetails API call. """ keys = ["key_one", "key_two"] shell.help_activationkey_disable = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.do_activationkey_list = MagicMock(return_value=keys) spacecmd.activationkey.do_activationkey_disable(shell, "key_*") assert not shell.help_activationkey_disable.called assert shell.client.activationkey.setDetails.called for call in shell.client.activationkey.setDetails.call_args_list: session, key, arg = call[0] assert shell.session == session assert key in keys assert "disabled" in arg assert arg["disabled"] def test_do_activationkey_enable_noargs(self, shell): """ Test do_activationkey_enable command triggers help on no args. """ shell.help_activationkey_enable = MagicMock() shell.client.activationkey.setDetails = MagicMock() spacecmd.activationkey.do_activationkey_enable(shell, "") assert shell.help_activationkey_enable.called assert not shell.client.activationkey.setDetails.called def test_do_activationkey_enable_args(self, shell): """ Test do_activationkey_enable command triggers activationkey.setDetails API call. """ keys = ["key_one", "key_two"] shell.help_activationkey_enable = MagicMock() shell.client.activationkey.setDetails = MagicMock() shell.do_activationkey_list = MagicMock(return_value=keys) spacecmd.activationkey.do_activationkey_enable(shell, "key_*") assert not shell.help_activationkey_enable.called assert shell.client.activationkey.setDetails.called for call in shell.client.activationkey.setDetails.call_args_list: session, key, arg = call[0] assert shell.session == session assert key in keys assert "disabled" in arg assert not arg["disabled"] def test_do_activationkey_setdescription_noargs(self, shell): """ Test do_activationkey_setdescription command triggers help on no args. """ shell.help_activationkey_setdescription = MagicMock() shell.client.activationkey.setDetails = MagicMock() spacecmd.activationkey.do_activationkey_setdescription(shell, "") assert shell.help_activationkey_setdescription.called assert not shell.client.activationkey.setDetails.called def test_do_activationkey_setdescription_args(self, shell): """ Test do_activationkey_setdescription command triggers activationkey.setDetails API call. """ shell.help_activationkey_enable = MagicMock() shell.client.activationkey.setDetails = MagicMock() spacecmd.activationkey.do_activationkey_setdescription(shell, "key_one some description of it here") assert not shell.help_activationkey_enable.called assert shell.client.activationkey.setDetails.called for call in shell.client.activationkey.setDetails.call_args_list: session, key, arg = call[0] assert shell.session == session assert "description" in arg assert arg["description"] == "some description of it here" def test_do_activationkey_setcontactmethod_noargs(self, shell): """ Test do_activationkey_setcontactmethod command triggers help on no args. """ shell.help_activationkey_setcontactmethod = MagicMock() shell.client.activationkey.setDetails = MagicMock() spacecmd.activationkey.do_activationkey_setcontactmethod(shell, "") assert shell.help_activationkey_setcontactmethod.called assert not shell.client.activationkey.setDetails.called def test_do_activationkey_setcontactmethod_args(self, shell): """ Test do_activationkey_setdescription command triggers activationkey.setDetails API call. """ shell.help_activationkey_setcontactmethod = MagicMock() shell.client.activationkey.setDetails = MagicMock() spacecmd.activationkey.do_activationkey_setcontactmethod(shell, "key_one 230V") assert not shell.help_activationkey_setcontactmethod.called assert shell.client.activationkey.setDetails.called for call in shell.client.activationkey.setDetails.call_args_list: session, key, arg = call[0] assert shell.session == session assert "contact_method" in arg assert arg["contact_method"] == "230V" 07070100000030000081B40000000000000000000000015D65A5B200000A84000000000000000000000000000000000000001B00000000spacecmd/tests/test_api.py# coding: utf-8 """ Test suite for spacecmd.api """ from mock import MagicMock, patch, mock_open from spacecmd import api import helpers class TestSCAPI: """ Test class for testing spacecmd API. """ def test_no_args(self): """ Test calling API without any arguments. """ shell = MagicMock() shell.help_api = MagicMock() api.do_api(shell, "") assert shell.help_api.called def test_args_output(self): """ Test output option. """ shell = MagicMock() shell.help_api = MagicMock() shell.client = MagicMock() shell.client.call = MagicMock(return_value=["one", "two", "three"]) log = MagicMock() out = helpers.FileHandleMock() with patch("spacecmd.api.open", out, create=True) as mop, \ patch("spacecmd.api.logging", log) as mlog: api.do_api(shell, "call -o /tmp/spacecmd.log") assert not mlog.warning.called assert out.get_content() == '[\n "one",\n "two",\n "three"\n]' assert out.get_init_kwargs() == {} assert out.get_init_args() == ('/tmp/spacecmd.log', 'w') assert out._closed def test_args_format(self): """ Test format option. """ shell = MagicMock() shell.help_api = MagicMock() shell.client = MagicMock() shell.client.call = MagicMock(return_value=["one", "two", "three"]) log = MagicMock() out = helpers.FileHandleMock() with patch("spacecmd.api.open", out, create=True) as mop, \ patch("spacecmd.api.logging", log) as mlog: api.do_api(shell, "call -o /tmp/spacecmd.log -F '>>> %s'") assert not mlog.warning.called assert out.get_content() == '>>> one\n>>> two\n>>> three\n' assert out.get_init_kwargs() == {} assert out.get_init_args() == ('/tmp/spacecmd.log', 'w') assert out._closed def test_args_args(self): """ Test args option. """ shell = MagicMock() shell.help_api = MagicMock() shell.client = MagicMock() shell.client.call = MagicMock(return_value=["one", "two", "three"]) shell.session = "session" log = MagicMock() out = helpers.FileHandleMock() with patch("spacecmd.api.open", out, create=True) as mop, \ patch("spacecmd.api.logging", log) as mlog: api.do_api(shell, "call -A first,second,123 -o /tmp/spacecmd.log") assert shell.client.call.called assert shell.client.call.call_args_list[0][0] == ('session', 'first', 'second', 123) assert out._closed 07070100000031000081B40000000000000000000000015D65A5B2000001FF000000000000000000000000000000000000002600000000spacecmd/tests/test_argumentparser.py# coding: utf-8 """ Test argument parser. """ import pytest import spacecmd.argumentparser class TestSCArgumentParser: """ Test argument parser subclass. """ def test_argparse_raise_exception(self): """ Test argparse raise exception. """ msg = "not enough memory, get system upgrade" argparse = spacecmd.argumentparser.SpacecmdArgumentParser() with pytest.raises(Exception) as exc: argparse.error(msg) assert msg in str(exc) 07070100000032000081B40000000000000000000000015D65A5B20000403B000000000000000000000000000000000000002100000000spacecmd/tests/test_cryptokey.py# coding: utf-8 """ Test suite for cryptokey. """ from mock import MagicMock, patch import pytest from xmlrpc import client as xmlrpclib import spacecmd.cryptokey from helpers import shell, assert_expect class TestSCCryptokey: """ Test cryptokey API. """ def test_cryptokey_create_no_keytype(self, shell): """ Test do_cryptokey_create without correct key type. :param shell: :return: """ shell.help_cryptokey_create = MagicMock() shell.client.kickstart.keys.create = MagicMock() shell.user_confirm = MagicMock(return_value=True) read_file = MagicMock(return_value="contents") prompt_user = MagicMock(side_effect=["", "interactive descr", "/tmp/file.txt"]) editor = MagicMock() logger = MagicMock() with patch("spacecmd.cryptokey.prompt_user", prompt_user) as pmu, \ patch("spacecmd.cryptokey.read_file", read_file) as rfl, \ patch("spacecmd.cryptokey.editor", editor) as edt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_create(shell, "") assert not shell.help_cryptokey_create.called assert not shell.client.kickstart.keys.create.called assert not editor.called assert read_file.called assert prompt_user.called assert logger.error.called assert_expect(logger.error.call_args_list, "Invalid key type") def test_cryptokey_create_interactive_no_contents(self, shell): """ Test do_cryptokey_create without arguments (interactive, no contents given). :param shell: :return: """ shell.help_cryptokey_create = MagicMock() shell.client.kickstart.keys.create = MagicMock() shell.user_confirm = MagicMock(return_value=True) read_file = MagicMock() prompt_user = MagicMock(side_effect=["g", "interactive descr", ""]) editor = MagicMock() logger = MagicMock() with patch("spacecmd.cryptokey.prompt_user", prompt_user) as pmu, \ patch("spacecmd.cryptokey.read_file", read_file) as rfl, \ patch("spacecmd.cryptokey.editor", editor) as edt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_create(shell, "") assert not shell.help_cryptokey_create.called assert not shell.client.kickstart.keys.create.called assert not read_file.called assert not editor.called assert prompt_user.called assert logger.error.called assert_expect(logger.error.call_args_list, "No contents of the file") def test_cryptokey_create_interactive_wrong_key_type(self, shell): """ Test do_cryptokey_create without arguments (interactive, wrong key type). :param shell: :return: """ shell.help_cryptokey_create = MagicMock() shell.client.kickstart.keys.create = MagicMock() shell.user_confirm = MagicMock(return_value=True) read_file = MagicMock(return_value="contents") prompt_user = MagicMock(side_effect=["x", "interactive descr", "/tmp/file.txt"]) editor = MagicMock() logger = MagicMock() with patch("spacecmd.cryptokey.prompt_user", prompt_user) as pmu, \ patch("spacecmd.cryptokey.read_file", read_file) as rfl, \ patch("spacecmd.cryptokey.editor", editor) as edt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_create(shell, "") assert not shell.help_cryptokey_create.called assert not shell.client.kickstart.keys.create.called assert not editor.called assert read_file.called assert prompt_user.called assert logger.error.called assert_expect(logger.error.call_args_list, "Invalid key type") def test_cryptokey_create_GPG_key(self, shell): """ Test do_cryptokey_create with parameters, calling GPG key type. :param shell: :return: """ shell.help_cryptokey_create = MagicMock() shell.client.kickstart.keys.create = MagicMock() shell.user_confirm = MagicMock(return_value=True) read_file = MagicMock(return_value="contents") prompt_user = MagicMock(side_effect=[]) editor = MagicMock() logger = MagicMock() with patch("spacecmd.cryptokey.prompt_user", prompt_user) as pmu, \ patch("spacecmd.cryptokey.read_file", read_file) as rfl, \ patch("spacecmd.cryptokey.editor", editor) as edt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_create(shell, "-t g -d description -f /tmp/file.txt") assert not editor.called assert not shell.help_cryptokey_create.called assert not prompt_user.called assert not logger.error.called assert shell.client.kickstart.keys.create.called assert read_file.called for call in shell.client.kickstart.keys.create.call_args_list: args, kw = call assert args == (shell.session, "description", "GPG", "contents") assert not kw def test_cryptokey_create_SSL_key(self, shell): """ Test do_cryptokey_create with parameters, calling SSL key type. :param shell: :return: """ shell.help_cryptokey_create = MagicMock() shell.client.kickstart.keys.create = MagicMock() shell.user_confirm = MagicMock(return_value=True) read_file = MagicMock(return_value="contents") prompt_user = MagicMock(side_effect=[]) editor = MagicMock() logger = MagicMock() with patch("spacecmd.cryptokey.prompt_user", prompt_user) as pmu, \ patch("spacecmd.cryptokey.read_file", read_file) as rfl, \ patch("spacecmd.cryptokey.editor", editor) as edt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_create(shell, "-t s -d description -f /tmp/file.txt") assert not editor.called assert not shell.help_cryptokey_create.called assert not prompt_user.called assert not logger.error.called assert shell.client.kickstart.keys.create.called assert read_file.called for call in shell.client.kickstart.keys.create.call_args_list: args, kw = call assert args == (shell.session, "description", "SSL", "contents") assert not kw def test_cryptokey_delete_noargs(self, shell): """ Test do_cryptokey_delete without parameters, so help should be displayed. :return: """ shell.help_cryptokey_delete = MagicMock() shell.client.kickstart.keys.delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) shell.do_cryptokey_list = MagicMock() filter_results = MagicMock() logger = MagicMock() with patch("spacecmd.cryptokey.logging", logger) as lgr, \ patch("spacecmd.cryptokey.filter_results", filter_results) as frl: spacecmd.cryptokey.do_cryptokey_delete(shell, "") assert not logger.error.called assert not filter_results.called assert not shell.client.kickstart.keys.delete.called assert not shell.user_confirm.called assert not shell.do_cryptokey_list.called assert shell.help_cryptokey_delete.called def test_cryptokey_delete_no_exist(self, shell): """ Test do_cryptokey_delete with non-existing key. :return: """ shell.help_cryptokey_delete = MagicMock() shell.client.kickstart.keys.delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) shell.do_cryptokey_list = MagicMock(return_value=["one", "two", "three"]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.cryptokey.logging", logger) as lgr, \ patch("spacecmd.cryptokey.print", mprint) as prn: spacecmd.cryptokey.do_cryptokey_delete(shell, "foo*") assert not shell.client.kickstart.keys.delete.called assert not shell.help_cryptokey_delete.called assert not shell.user_confirm.called assert not mprint.called assert logger.error.called assert_expect(logger.error.call_args_list, "No keys matched argument ['foo.*']") def test_cryptokey_delete_not_confirmed(self, shell): """ Test do_cryptokey_delete with non-existing key. :return: """ shell.help_cryptokey_delete = MagicMock() shell.client.kickstart.keys.delete = MagicMock() shell.user_confirm = MagicMock(return_value=False) shell.do_cryptokey_list = MagicMock(return_value=["one", "two", "three"]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.cryptokey.logging", logger) as lgr, \ patch("spacecmd.cryptokey.print", mprint) as prn: spacecmd.cryptokey.do_cryptokey_delete(shell, "t*") assert not shell.client.kickstart.keys.delete.called assert not shell.help_cryptokey_delete.called assert not logger.error.called assert shell.user_confirm.called assert mprint.called assert_expect(mprint.call_args_list, 'three\ntwo') def test_cryptokey_delete_confirmed_deleted(self, shell): """ Test do_cryptokey_delete with non-existing key. :return: """ shell.help_cryptokey_delete = MagicMock() shell.client.kickstart.keys.delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) shell.do_cryptokey_list = MagicMock(return_value=["one", "two", "three"]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.cryptokey.logging", logger) as lgr, \ patch("spacecmd.cryptokey.print", mprint) as prn: spacecmd.cryptokey.do_cryptokey_delete(shell, "t*") assert not logger.error.called assert not shell.help_cryptokey_delete.called assert shell.client.kickstart.keys.delete.called assert shell.user_confirm.called assert mprint.called assert_expect(mprint.call_args_list, 'three\ntwo') exp = [ (shell.session, "two",), (shell.session, "three",), ] for call in shell.client.kickstart.keys.delete.call_args_list: args, kw = call assert not kw assert args == next(iter(exp)) exp.pop(0) assert not exp def test_cryptokey_list_no_stdout(self, shell): """ Test do_cryptokey_list no STDOUT. :param shell: :return: """ shell.client.kickstart.keys.listAllKeys = MagicMock( return_value=[ {"description": "keydescr-1"}, {"description": "keydescr-2"}, ] ) mprint = MagicMock() with patch("spacecmd.cryptokey.print", mprint) as prn: out = spacecmd.cryptokey.do_cryptokey_list(shell, "", doreturn=True) assert not mprint.called assert bool(out) assert shell.client.kickstart.keys.listAllKeys.called assert out == ['keydescr-1', 'keydescr-2'] def test_cryptokey_list_stdout(self, shell): """ Test do_cryptokey_list to STDOUT. :param shell: :return: """ shell.client.kickstart.keys.listAllKeys = MagicMock( return_value=[ {"description": "keydescr-1"}, {"description": "keydescr-2"}, ] ) mprint = MagicMock() with patch("spacecmd.cryptokey.print", mprint) as prn: out = spacecmd.cryptokey.do_cryptokey_list(shell, "", doreturn=False) assert out is None assert mprint.called assert shell.client.kickstart.keys.listAllKeys.called assert_expect(mprint.call_args_list, "keydescr-1\nkeydescr-2") def test_cryptokey_details_noargs(self, shell): """ Test do_cryptokey_details with no parameters. :param shell: :return: """ shell.client.kickstart.keys.getDetails = MagicMock() shell.do_cryptokey_list = MagicMock(return_value=[]) shell.help_cryptokey_details = MagicMock() logger = MagicMock() mprint = MagicMock() with patch("spacecmd.cryptokey.print", mprint) as mpt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_details(shell, "") assert not mprint.called assert not shell.client.kickstart.keys.getDetails.called assert not shell.do_cryptokey_list.called assert shell.help_cryptokey_details.called def test_cryptokey_details_not_found(self, shell): """ Test do_cryptokey_details key not found. :param shell: :return: """ shell.client.kickstart.keys.getDetails = MagicMock() shell.do_cryptokey_list = MagicMock(return_value=[]) shell.help_cryptokey_details = MagicMock() logger = MagicMock() mprint = MagicMock() with patch("spacecmd.cryptokey.print", mprint) as mpt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_details(shell, "somekey") assert not mprint.called assert not shell.client.kickstart.keys.getDetails.called assert not shell.help_cryptokey_details.called assert shell.do_cryptokey_list.called assert logger.error.called assert_expect(logger.error.call_args_list, "No keys matched argument ['somekey']") def test_cryptokey_details_listing(self, shell): """ Test do_cryptokey_details key listing :param shell: :return: """ shell.client.kickstart.keys.getDetails = MagicMock(side_effect=[ {"description": "first descr", "type": "SSL", "content": "one data"}, {"description": "second descr", "type": "GPG", "content": "two data"}, ]) shell.do_cryptokey_list = MagicMock(return_value=["key-one", "key-two", "three"]) shell.SEPARATOR = "---" shell.help_cryptokey_details = MagicMock() logger = MagicMock() mprint = MagicMock() with patch("spacecmd.cryptokey.print", mprint) as mpt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_details(shell, "key*") assert not shell.help_cryptokey_details.called assert not logger.error.called assert logger.debug.called assert mprint.called assert shell.client.kickstart.keys.getDetails.called assert shell.do_cryptokey_list.called exp = [ 'Description: first descr', 'Type: SSL', '', 'one data', '---', 'Description: second descr', 'Type: GPG', '', 'two data', ] for call in mprint.call_args_list: assert_expect([call], next(iter(exp))) exp.pop(0) assert not exp def test_cryptokey_details_rpc_error(self, shell): """ Test do_cryptokey_details captures xmlrpc failure. :param shell: :return: """ shell.client.kickstart.keys.getDetails = MagicMock( side_effect=xmlrpclib.Fault(faultCode=42, faultString="Kaboom") ) shell.do_cryptokey_list = MagicMock(return_value=["somekey"]) shell.help_cryptokey_details = MagicMock() logger = MagicMock() mprint = MagicMock() with patch("spacecmd.cryptokey.print", mprint) as mpt, \ patch("spacecmd.cryptokey.logging", logger) as lgr: spacecmd.cryptokey.do_cryptokey_details(shell, "somekey") assert not mprint.called assert not shell.help_cryptokey_details.called assert not logger.error.called assert shell.client.kickstart.keys.getDetails.called assert shell.do_cryptokey_list.called assert logger.warning.called assert_expect(logger.warning.call_args_list, "somekey is not a valid crypto key") 07070100000033000081B40000000000000000000000015D65A5B20000340F000000000000000000000000000000000000002200000000spacecmd/tests/test_custominfo.py# coding: utf-8 """ Test suite for custominfo source """ from mock import MagicMock, patch, mock_open from spacecmd import custominfo from helpers import shell import pytest class TestSCCusomInfo: """ Test for custominfo API. """ def test_do_custominfo_createkey_no_keyname(self, shell): """ Test do_custominfo_createkey do not break on no key name provided, falling back to interactive mode. """ shell.client.system.custominfo.createKey = MagicMock() prompter = MagicMock(side_effect=["", "", Exception("Empty key")]) with patch("spacecmd.custominfo.prompt_user", prompter): with pytest.raises(Exception) as exc: custominfo.do_custominfo_createkey(shell, "") assert "Empty key" in str(exc) def test_do_custominfo_createkey_no_descr(self, shell): """ Test do_custominfo_createkey description gets the name of the key, if not provided. """ shell.client.system.custominfo.createKey = MagicMock() prompter = MagicMock(side_effect=["keyname", ""]) with patch("spacecmd.custominfo.prompt_user", prompter): custominfo.do_custominfo_createkey(shell, "") assert shell.client.system.custominfo.createKey.called session, keyname, descr = shell.client.system.custominfo.createKey.call_args_list[0][0] assert shell.session == session assert keyname == descr def test_do_custominfo_createkey_descr_interactive(self, shell): """ Test do_custominfo_createkey description gets the name of the key from interactive prompt. """ shell.client.system.custominfo.createKey = MagicMock() prompter = MagicMock(side_effect=["keyname", "keydescr"]) with patch("spacecmd.custominfo.prompt_user", prompter): custominfo.do_custominfo_createkey(shell, "") assert shell.client.system.custominfo.createKey.called session, keyname, descr = shell.client.system.custominfo.createKey.call_args_list[0][0] assert shell.session == session assert keyname != descr assert keyname == "keyname" assert descr == "keydescr" def test_do_custominfo_createkey_descr_args(self, shell): """ Test do_custominfo_createkey description gets the name of the key from the args. """ shell.client.system.custominfo.createKey = MagicMock() prompter = MagicMock(side_effect=Exception("Kaboom")) custominfo.do_custominfo_createkey(shell, "keyname keydescr") assert shell.client.system.custominfo.createKey.called session, keyname, descr = shell.client.system.custominfo.createKey.call_args_list[0][0] assert shell.session == session assert keyname != descr assert keyname == "keyname" assert descr == "keydescr" def test_do_custominfo_deletekey_noargs(self, shell): """ Test do_custominfo_deletekey shows help on no args. """ errmsg = "No arguments passed" shell.client.system.custominfo.deleteKey = MagicMock() shell.do_custominfo_listkeys = MagicMock() shell.help_custominfo_deletekey = MagicMock(side_effect=Exception(errmsg)) shell.user_confirm = MagicMock() logger = MagicMock() with patch("spacecmd.custominfo.logging", logger): with pytest.raises(Exception) as exc: custominfo.do_custominfo_deletekey(shell, "") assert errmsg in str(exc) @patch("spacecmd.custominfo.print", MagicMock()) def test_do_custominfo_deletekey_args(self, shell): """ Test do_custominfo_deletekey calls deleteKey API function. """ keylist = ["some_key", "some_other_key", "this_key_stays"] shell.client.system.custominfo.deleteKey = MagicMock() shell.do_custominfo_listkeys = MagicMock(return_value=keylist) shell.help_custominfo_deletekey = MagicMock(side_effect=Exception("Kaboom")) shell.user_confirm = MagicMock() logger = MagicMock() with patch("spacecmd.custominfo.logging", logger): custominfo.do_custominfo_deletekey(shell, "some*") assert shell.client.system.custominfo.deleteKey.called for call in shell.client.system.custominfo.deleteKey.call_args_list: session, keyname = call[0] assert shell.session == session assert keyname in keylist keylist.pop(keylist.index(keyname)) assert len(keylist) == 1 assert "this_key_stays" in keylist def test_do_custominfo_listkeys_stdout(self, shell): """ Test do_custominfo_listkeys calls lists all keys calling listAllKeys API function to STDOUT. """ keylist=[ {"label": "some_key"}, {"label": "some_other_key"}, {"label": "this_key_stays"}, ] shell.client.system.custominfo.listAllKeys = MagicMock(return_value=keylist) mprint = MagicMock() with patch("spacecmd.custominfo.print", mprint): ret = custominfo.do_custominfo_listkeys(shell, "") assert ret is None assert mprint.called def test_do_custominfo_listkeys_as_data(self, shell): """ Test do_custominfo_listkeys calls lists all keys calling listAllKeys API function as data. """ keylist=[ {"label": "some_key"}, {"label": "some_other_key"}, {"label": "this_key_stays"}, ] shell.client.system.custominfo.listAllKeys = MagicMock(return_value=keylist) mprint = MagicMock() with patch("spacecmd.custominfo.print", mprint): ret = custominfo.do_custominfo_listkeys(shell, "", doreturn=True) assert not mprint.called assert isinstance(ret, list) for key in keylist: assert key["label"] in ret def test_do_custominfo_details_noarg(self, shell): """ Test do_custominfo_details shows help when no arguments has been passed. """ keylist=["some_key", "some_other_key", "this_key_stays"] shell.help_custominfo_details = MagicMock(side_effect=Exception("Help info")) shell.client.system.custominfo.listAllKeys = MagicMock() shell.do_custominfo_listkeys = MagicMock(return_value=keylist) logger = MagicMock() with patch("spacecmd.custominfo.logging", logger): with pytest.raises(Exception) as exc: custominfo.do_custominfo_details(shell, "") assert "Help info" in str(exc) assert not logger.debug.called assert not logger.error.called assert not shell.client.system.custominfo.listAllKeys.called def test_do_custominfo_details_no_key(self, shell): """ Test do_custominfo_details shows error to the log if key name doesn't match. """ shell.client.system.custominfo.listAllKeys = MagicMock() shell.do_custominfo_listkeys = MagicMock(return_value=["key_one", "key_two"]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.custominfo.logging", logger): with patch("spacecmd.custominfo.print", mprint): custominfo.do_custominfo_details(shell, "keyname") assert not shell.client.system.custominfo.listAllKeys.called assert logger.debug.call_args_list[0][0][0] == "customkey_details called with args: 'keyname', keys: ''." assert logger.error.call_args_list[0][0][0] == "No keys matched argument 'keyname'." def test_do_custominfo_details_keydetails_notfound(self, shell): """ Test do_custominfo_details nothing happens if keydetails missing. """ shell.client.system.custominfo.listAllKeys = MagicMock() shell.do_custominfo_listkeys = MagicMock(return_value=["key_one", "key_two"]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.custominfo.logging", logger): with patch("spacecmd.custominfo.print", mprint): custominfo.do_custominfo_details(shell, "key*") assert shell.client.system.custominfo.listAllKeys.called assert not mprint.called def test_do_custominfo_details_keydetails_na(self, shell): """ Test do_custominfo_details prints key details not available in format. """ shell.SEPARATOR = "***" shell.client.system.custominfo.listAllKeys = MagicMock( return_value=[ {"label": "key_one"}, {"label": "key_two"}, ] ) shell.do_custominfo_listkeys = MagicMock(return_value=["key_one", "key_two"]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.custominfo.logging", logger): with patch("spacecmd.custominfo.print", mprint): custominfo.do_custominfo_details(shell, "key*") expectations = [ 'Label: key_one', 'Description: N/A', 'Modified: N/A', 'System Count: 0', '***', 'Label: key_two', 'Description: N/A', 'Modified: N/A', 'System Count: 0' ] assert shell.client.system.custominfo.listAllKeys.called assert mprint.called for idx, call in enumerate(mprint.call_args_list): assert call[0][0] == expectations[idx] def test_do_custominfo_details_keydetails(self, shell): """ Test do_custominfo_details prints key details not available in format. """ shell.SEPARATOR = "***" shell.client.system.custominfo.listAllKeys = MagicMock( return_value=[ {"label": "key_one", "description": "descr one", "last_modified": "123", "system_count": 1}, {"label": "key_two", "description": "descr two", "last_modified": "234", "system_count": 2}, ] ) shell.do_custominfo_listkeys = MagicMock(return_value=["key_one", "key_two"]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.custominfo.logging", logger): with patch("spacecmd.custominfo.print", mprint): custominfo.do_custominfo_details(shell, "key*") expectations = [ 'Label: key_one', 'Description: descr one', 'Modified: 123', 'System Count: 1', '***', 'Label: key_two', 'Description: descr two', 'Modified: 234', 'System Count: 2' ] assert shell.client.system.custominfo.listAllKeys.called assert mprint.called for idx, call in enumerate(mprint.call_args_list): assert call[0][0] == expectations[idx] def test_custominfo_updatekey_noarg_name(self, shell): """ Test do_custominfo_updatekey with no arguments falls to the interactive prompt. """ shell.client.system.custominfo.updateKey = MagicMock() prompt = MagicMock(side_effect=Exception("interactive mode")) with patch("spacecmd.custominfo.prompt_user", prompt): with pytest.raises(Exception) as exc: custominfo.do_custominfo_updatekey(shell, "") assert "interactive mode" in str(exc) def test_custominfo_updatekey_noarg_descr(self, shell): """ Test do_custominfo_updatekey with no arguments falls to the interactive prompt. """ shell.client.system.custominfo.updateKey = MagicMock() prompt = MagicMock(side_effect=["keyname", Exception("interactive mode for descr")]) with patch("spacecmd.custominfo.prompt_user", prompt): with pytest.raises(Exception) as exc: custominfo.do_custominfo_updatekey(shell, "") assert "interactive mode for descr" in str(exc) def test_custominfo_updatekey_keyonly_arg(self, shell): """ Test do_custominfo_updatekey description is taken interactively. """ shell.client.system.custominfo.updateKey = MagicMock() prompt = MagicMock(side_effect=[Exception("interactive mode for descr")]) with patch("spacecmd.custominfo.prompt_user", prompt): with pytest.raises(Exception) as exc: custominfo.do_custominfo_updatekey(shell, "keyname") assert "interactive mode for descr" in str(exc) def test_custominfo_updatekey_all_args(self, shell): """ Test do_custominfo_updatekey description is taken by arguments, interactive mode is not initiated. """ shell.client.system.custominfo.updateKey = MagicMock() prompt = MagicMock(side_effect=[Exception("interactive mode for descr")]) with patch("spacecmd.custominfo.prompt_user", prompt): custominfo.do_custominfo_updatekey(shell, "keyname 'some key description here'") assert shell.client.system.custominfo.updateKey.called session, keyname, description = shell.client.system.custominfo.updateKey.call_args_list[0][0] assert shell.session == session assert keyname == "keyname" assert description == "some key description here" 07070100000034000081B40000000000000000000000015D65A5B2000054E7000000000000000000000000000000000000002400000000spacecmd/tests/test_distribution.py# coding: utf-8 """ Test distribution """ from unittest.mock import MagicMock, patch from helpers import shell, assert_expect import pytest import spacecmd.distribution class TestSCDistribution: """ Test suite for distribution commands of the spacecmd. """ def test_distribution_create_no_args(self, shell): """ Test do_distribution_create with no args. :param shell: :return: """ shell.client.kickstart.tree.listInstallTypes = MagicMock(return_value=[ {"label": "image"}, ]) shell.client.kickstart.tree.update = MagicMock() shell.client.kickstart.tree.create = MagicMock() shell.list_base_channels = MagicMock(return_value=["base-channel"]) mprint = MagicMock() prompt = MagicMock(side_effect=[ "name", "/path/tree", "base-channel", "image" ]) logger = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.prompt_user", prompt) as prmt, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_create(shell, "") assert mprint.called assert prompt.called assert shell.client.kickstart.tree.listInstallTypes.called assert shell.client.kickstart.tree.create.called assert not shell.client.kickstart.tree.update.called # Check STDOUT consistency exp = [ '', 'Base Channels', '-------------', 'base-channel', '', '', 'Install Types', '-------------', 'image', '' ] for call in mprint.call_args_list: assert_expect([call], next(iter(exp))) exp.pop(0) assert not exp for call in shell.client.kickstart.tree.create.call_args_list: args, kw = call assert args == (shell.session, "name", "/path/tree", "base-channel", "image") assert not kw assert_expect(shell.client.kickstart.tree.listInstallTypes.call_args_list, shell.session) def test_distribution_create_no_args_update_mode(self, shell): """ Test do_distribution_create with no args with update mode. :param shell: :return: """ shell.client.kickstart.tree.listInstallTypes = MagicMock(return_value=[ {"label": "image"}, ]) shell.client.kickstart.tree.update = MagicMock() shell.client.kickstart.tree.create = MagicMock() shell.list_base_channels = MagicMock(return_value=["base-channel"]) mprint = MagicMock() prompt = MagicMock(side_effect=[ "name", "/path/tree", "base-channel", "image" ]) logger = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.prompt_user", prompt) as prmt, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_create(shell, "", update=True) assert not mprint.called assert not prompt.called assert not shell.client.kickstart.tree.listInstallTypes.called assert not shell.client.kickstart.tree.create.called assert not shell.client.kickstart.tree.update.called assert logger.error.called assert_expect(logger.error.call_args_list, "The name of the distribution is required") def test_distribution_create_args_ds_update_mode(self, shell): """ Test do_distribution_create with distribution name in update mode. :param shell: :return: """ shell.client.kickstart.tree.listInstallTypes = MagicMock(return_value=[ {"label": "image"}, ]) shell.client.kickstart.tree.update = MagicMock() shell.client.kickstart.tree.create = MagicMock() shell.list_base_channels = MagicMock(return_value=["base-channel"]) mprint = MagicMock() prompt = MagicMock() logger = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.prompt_user", prompt) as prmt, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_create(shell, "-n myname", update=True) assert not mprint.called assert not prompt.called assert not shell.client.kickstart.tree.listInstallTypes.called assert not shell.client.kickstart.tree.create.called assert not shell.client.kickstart.tree.update.called assert logger.error.called assert_expect(logger.error.call_args_list, "A path is required") def test_distribution_create_args_dspt_update_mode(self, shell): """ Test do_distribution_create with distribution name and path in update mode. :param shell: :return: """ shell.client.kickstart.tree.listInstallTypes = MagicMock(return_value=[ {"label": "image"}, ]) shell.client.kickstart.tree.update = MagicMock() shell.client.kickstart.tree.create = MagicMock() shell.list_base_channels = MagicMock(return_value=["base-channel"]) mprint = MagicMock() prompt = MagicMock() logger = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.prompt_user", prompt) as prmt, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_create( shell, "-n myname -p /path/tree", update=True) assert not mprint.called assert not prompt.called assert not shell.client.kickstart.tree.listInstallTypes.called assert not shell.client.kickstart.tree.create.called assert not shell.client.kickstart.tree.update.called assert logger.error.called assert_expect(logger.error.call_args_list, "A base channel is required") def test_distribution_create_args_dsptbc_update_mode(self, shell): """ Test do_distribution_create with distribution name, path and base channel in update mode. :param shell: :return: """ shell.client.kickstart.tree.listInstallTypes = MagicMock(return_value=[ {"label": "image"}, ]) shell.client.kickstart.tree.update = MagicMock() shell.client.kickstart.tree.create = MagicMock() shell.list_base_channels = MagicMock(return_value=["base-channel"]) mprint = MagicMock() prompt = MagicMock() logger = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.prompt_user", prompt) as prmt, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_create( shell, "-n myname -p /path/tree -b base-channel", update=True) assert not mprint.called assert not prompt.called assert not shell.client.kickstart.tree.listInstallTypes.called assert not shell.client.kickstart.tree.create.called assert not shell.client.kickstart.tree.update.called assert logger.error.called assert_expect(logger.error.call_args_list, "An install type is required") def test_distribution_create_args_dsptbcit_update_mode(self, shell): """ Test do_distribution_create with distribution name, path, base channel and install type in update mode. :param shell: :return: """ shell.client.kickstart.tree.listInstallTypes = MagicMock(return_value=[ {"label": "image"}, ]) shell.client.kickstart.tree.update = MagicMock() shell.client.kickstart.tree.create = MagicMock() shell.list_base_channels = MagicMock(return_value=["base-channel"]) mprint = MagicMock() prompt = MagicMock() logger = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.prompt_user", prompt) as prmt, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_create( shell, "-n myname -p /path/tree -b base-channel -t image", update=True) assert not mprint.called assert not prompt.called assert not shell.client.kickstart.tree.create.called assert not logger.error.called assert not shell.client.kickstart.tree.listInstallTypes.called assert shell.client.kickstart.tree.update.called for call in shell.client.kickstart.tree.update.call_args_list: args, kw = call assert args == (shell.session, "myname", "/path/tree", "base-channel", "image") assert not kw def test_distribution_list_noarg_noret(self, shell): """ Test do_distribution_list without argumnets, no return option. :param shell: :return: """ shell.client.kickstart.listAutoinstallableChannels = MagicMock(return_value=[ {"label": "channel-name"}, ]) shell.client.kickstart.tree.list = MagicMock(return_value=[ {"label": "some-channel"}, {"label": "some-other-channel"}, ]) mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn: out = spacecmd.distribution.do_distribution_list(shell, "") assert out is None assert mprint.called assert_expect(mprint.call_args_list, "some-channel\nsome-other-channel") def test_distribution_list_noarg_ret(self, shell): """ Test do_distribution_list without argumnets, return data mode. :param shell: :return: """ shell.client.kickstart.listAutoinstallableChannels = MagicMock(return_value=[ {"label": "channel-name"}, ]) shell.client.kickstart.tree.list = MagicMock(return_value=[ {"label": "some-channel"}, {"label": "some-other-channel"}, ]) mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn: out = spacecmd.distribution.do_distribution_list(shell, "", doreturn=True) assert out is not None assert type(out) == list assert not mprint.called assert out == ['some-channel', 'some-other-channel'] def test_distribution_delete_noargs(self, shell): """ Test do_distribution_delete with no arguments. :param shell: :return: """ shell.do_distribution_list = MagicMock() shell.help_distribution_delete = MagicMock() shell.client.kickstart.tree.delete = MagicMock() shell.user_confirm = MagicMock() logger = MagicMock() mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_delete(shell, "") assert not logger.debug.called assert not logger.error.called assert not mprint.called assert not shell.do_distribution_list.called assert not shell.client.kickstart.tree.delete.called assert not shell.user_confirm.called assert shell.help_distribution_delete.called def test_distribution_delete_args_no_match(self, shell): """ Test do_distribution_delete with wrong arguments. :param shell: :return: """ shell.do_distribution_list = MagicMock(return_value=["bar"]) shell.help_distribution_delete = MagicMock() shell.client.kickstart.tree.delete = MagicMock() shell.user_confirm = MagicMock() logger = MagicMock() mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_delete(shell, "foo*") assert logger.debug.called assert logger.error.called assert not mprint.called assert not shell.client.kickstart.tree.delete.called assert not shell.user_confirm.called assert not shell.help_distribution_delete.called assert_expect(logger.debug.call_args_list, "distribution_delete called with args ['foo.*'], dists=[]") assert_expect(logger.error.call_args_list, "No distributions matched argument ['foo.*']") def test_distribution_delete_args_match_no_confirm(self, shell): """ Test do_distribution_delete with correct arguments, not confirmed to delete. :param shell: :return: """ shell.do_distribution_list = MagicMock(return_value=["bar"]) shell.help_distribution_delete = MagicMock() shell.client.kickstart.tree.delete = MagicMock() shell.user_confirm = MagicMock(return_value=False) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_delete(shell, "b*") assert not logger.error.called assert not shell.client.kickstart.tree.delete.called assert not shell.help_distribution_delete.called assert logger.debug.called assert mprint.called assert shell.user_confirm.called assert_expect(logger.debug.call_args_list, "distribution_delete called with args ['b.*'], dists=['bar']") assert_expect(shell.user_confirm.call_args_list, "Delete distribution tree(s) [y/N]:") assert_expect(mprint.call_args_list, "bar") def test_distribution_delete_args_match_confirm(self, shell): """ Test do_distribution_delete with correct arguments, confirmed to delete. :param shell: :return: """ shell.do_distribution_list = MagicMock(return_value=["bar"]) shell.help_distribution_delete = MagicMock() shell.client.kickstart.tree.delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_delete(shell, "b*") assert not logger.error.called assert not shell.help_distribution_delete.called assert shell.client.kickstart.tree.delete.called assert logger.debug.called assert mprint.called assert shell.user_confirm.called assert_expect(logger.debug.call_args_list, "distribution_delete called with args ['b.*'], dists=['bar']") assert_expect(shell.user_confirm.call_args_list, "Delete distribution tree(s) [y/N]:") assert_expect(mprint.call_args_list, "bar") for call in shell.client.kickstart.tree.delete.call_args_list: args, kw = call assert args == (shell.session, "bar",) def test_distribution_details_noargs(self, shell): """ Test do_distribution_details with no arguments. :param shell: :return: """ shell.help_distribution_details = MagicMock() shell.client.kickstart.tree.getDetails = MagicMock() shell.client.channel.software.getDetails = MagicMock() shell.do_distribution_list = MagicMock() logger = MagicMock() mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_details(shell, "") assert not logger.error.called assert not logger.debug.called assert not shell.client.kickstart.tree.getDetails.called assert not shell.client.channel.software.getDetails.called assert not shell.do_distribution_list.called assert shell.help_distribution_details.called def test_distribution_details_no_dists(self, shell): """ Test do_distribution_details with no distributions found. :param shell: :return: """ shell.help_distribution_details = MagicMock() shell.client.kickstart.tree.getDetails = MagicMock() shell.client.channel.software.getDetails = MagicMock() shell.do_distribution_list = MagicMock(return_value=[]) logger = MagicMock() mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_details(shell, "test*") assert not shell.client.kickstart.tree.getDetails.called assert not shell.client.channel.software.getDetails.called assert not shell.help_distribution_details.called assert not mprint.called assert logger.debug.called assert logger.error.called assert shell.do_distribution_list.called assert_expect(logger.debug.call_args_list, "distribution_details called with args ['test.*'], dists=[]") assert_expect(logger.error.call_args_list, "No distributions matched argument ['test.*']") def test_distribution_details_list(self, shell): """ Test do_distribution_details lister. :param shell: :return: """ shell.help_distribution_details = MagicMock() shell.client.kickstart.tree.getDetails = MagicMock(side_effect=[ {"channel_id": "ch-id-1", "label": "dist-1", "abs_path": "/tmp/d1"}, {"channel_id": "ch-id-2", "label": "dist-2", "abs_path": "/tmp/d2"}, ]) shell.client.channel.software.getDetails = MagicMock(side_effect=[ {"label": "channel-one"}, {"label": "channel-two"}, ]) shell.do_distribution_list = MagicMock(return_value=["dist-1", "dist-2"]) shell.SEPARATOR = "---" logger = MagicMock() mprint = MagicMock() with patch("spacecmd.distribution.print", mprint) as prn, \ patch("spacecmd.distribution.logging", logger) as lgr: spacecmd.distribution.do_distribution_details(shell, "dist*") assert not shell.help_distribution_details.called assert not logger.error.called assert shell.client.kickstart.tree.getDetails.called assert shell.client.channel.software.getDetails.called assert logger.debug.called assert shell.do_distribution_list.called assert mprint.called exp = [ 'Name: dist-1', 'Path: /tmp/d1', 'Channel: channel-one', '---', 'Name: dist-2', 'Path: /tmp/d2', 'Channel: channel-two' ] for call in mprint.call_args_list: assert_expect([call], next(iter(exp))) exp.pop(0) assert not exp def test_distribution_rename_noargs(self, shell): """ Test do_distribution_rename without arguments. :param shell: :return: """ for args in ["", "foo"]: shell.help_distribution_rename = MagicMock() shell.client.kickstart.tree.rename = MagicMock() spacecmd.distribution.do_distribution_rename(shell, "") assert not shell.client.kickstart.tree.rename.called assert shell.help_distribution_rename.called def test_distribution_rename(self, shell): """ Test do_distribution_rename. :param shell: :return: """ shell.help_distribution_rename = MagicMock() shell.client.kickstart.tree.rename = MagicMock() spacecmd.distribution.do_distribution_rename(shell, "source destination") assert not shell.help_distribution_rename.called assert shell.client.kickstart.tree.rename.called for call in shell.client.kickstart.tree.rename.call_args_list: args, kw = call assert args == (shell.session, "source", "destination") def test_distribution_update_noargs(self, shell): """ Test do_distribution_update without arguments. :param shell: :return: """ shell.help_distribution_update = MagicMock() shell.do_distribution_create = MagicMock() spacecmd.distribution.do_distribution_update(shell, "") assert not shell.do_distribution_create.called assert shell.help_distribution_update.called def test_distribution_update(self, shell): """ Test do_distribution_update. :param shell: :return: """ shell.help_distribution_update = MagicMock() shell.do_distribution_create = MagicMock() spacecmd.distribution.do_distribution_update(shell, "my-distro") assert not shell.help_distribution_update.called assert shell.do_distribution_create.called for call in shell.do_distribution_create.call_args_list: args, kw = call assert args == ("my-distro", ) assert kw == {"update": True} 07070100000035000081B40000000000000000000000015D65A5B2000013F9000000000000000000000000000000000000002800000000spacecmd/tests/test_filepreservation.py# coding: utf-8 """ Test suite for spacecmd.filepreservation """ from unittest.mock import MagicMock, patch, mock_open import spacecmd.filepreservation from helpers import shell class TestSCFilePreservation: """ Test class for testing spacecmd file preservation. """ def test_do_filepreservation_list_noreturn(self, shell): """ Test do_filepreservation_list return to the STDOUT """ shell.client.kickstart.filepreservation.listAllFilePreservations = MagicMock( return_value=[ {"name": "list_one"}, {"name": "list_two"}, {"name": "list_three"}, ] ) mprint = MagicMock() with patch("spacecmd.filepreservation.print", mprint): ret = spacecmd.filepreservation.do_filepreservation_list(shell, "", doreturn=False) assert ret is None assert mprint.call_args_list[0][0][0] == "list_one\nlist_three\nlist_two" def test_do_filepreservation_list_return(self, shell): """ Test do_filepreservation_list return data. """ shell.client.kickstart.filepreservation.listAllFilePreservations = MagicMock( return_value=[ {"name": "list_one"}, {"name": "list_two"}, {"name": "list_three"}, ] ) mprint = MagicMock() with patch("spacecmd.filepreservation.print", mprint): ret = spacecmd.filepreservation.do_filepreservation_list(shell, "", doreturn=True) assert not mprint.called assert ret == ['list_one', 'list_two', 'list_three'] def test_do_filepreservation_create_noargs_prompted_name(self, shell): """ Test do_filepreservation_create no args passed so prompt appears. """ shell.client.kickstart.filepreservation.create = MagicMock() mprint = MagicMock() prmt = MagicMock(side_effect=["prompted_name", "file_one", "file_two", ""]) with patch("spacecmd.filepreservation.prompt_user", prmt) as pmt, \ patch("spacecmd.filepreservation.print", mprint) as mpt: spacecmd.filepreservation.do_filepreservation_create(shell, "") expectations = [ 'File List', '---------', '', '', 'File List', '---------', 'file_one', '', 'File List', '---------', 'file_one\nfile_two', '', '', 'File List', '---------', 'file_one\nfile_two' ] for idx, call in enumerate(mprint.call_args_list): assert call[0][0] == expectations[idx] def test_do_filepreservation_delete_noargs(self, shell): """ Test do_filepreservation_delete no args. """ shell.help_filepreservation_delete = MagicMock() shell.client.kickstart.filepreservation.delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) spacecmd.filepreservation.do_filepreservation_delete(shell, "") assert shell.help_filepreservation_delete.called assert not shell.client.kickstart.filepreservation.delete.called assert not shell.user_confirm.called def test_do_filepreservation_delete_args(self, shell): """ Test do_filepreservation_delete key name passed. """ shell.help_filepreservation_delete = MagicMock() shell.client.kickstart.filepreservation.delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) spacecmd.filepreservation.do_filepreservation_delete(shell, "some_key") assert not shell.help_filepreservation_delete.called assert shell.client.kickstart.filepreservation.delete.called assert shell.user_confirm.called session, keyname = shell.client.kickstart.filepreservation.delete.call_args_list[0][0] assert shell.session == session assert keyname == "some_key" def test_do_filepreservation_details_noargs(self, shell): """ Test do_filepreservation_details no args. """ shell.help_filepreservation_details = MagicMock() shell.client.kickstart.filepreservation.getDetails = MagicMock() spacecmd.filepreservation.do_filepreservation_details(shell, "") assert shell.help_filepreservation_details.called assert not shell.client.kickstart.filepreservation.details.called @patch("spacecmd.filepreservation.print", MagicMock()) def test_do_filepreservation_details_args(self, shell): """ Test do_filepreservation_details key passed. """ shell.help_filepreservation_details = MagicMock() shell.client.kickstart.filepreservation.getDetails = MagicMock() spacecmd.filepreservation.do_filepreservation_details(shell, "somekey") assert not shell.help_filepreservation_details.called assert shell.client.kickstart.filepreservation.getDetails.called session, keyname = shell.client.kickstart.filepreservation.getDetails.call_args_list[0][0] assert shell.session == session assert keyname == "somekey" 07070100000036000081B40000000000000000000000015D65A5B2000038F0000000000000000000000000000000000000001C00000000spacecmd/tests/test_scap.py# coding: utf-8 """ Test suite for Scap commands at spacecmd. """ from unittest.mock import MagicMock, patch from helpers import shell, assert_expect import pytest import spacecmd.scap class TestScap: """ Test suite for scap. """ def test_scap_listxccdfscans_noarg(self, shell): """ Test calling scap listxccdfscans without arguments. :param shell: :return: """ shell.help_scap_listxccdfscans = MagicMock() shell.ssm = MagicMock() shell.expand_systems = MagicMock() shell.get_system_id = MagicMock() shell.client.system.scap.listXccdfScans = MagicMock() mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_listxccdfscans(shell, "") assert shell.help_scap_listxccdfscans.called assert not shell.ssm.keys.called assert not shell.expand_systems.called assert not shell.get_system_id.called assert not shell.client.system.scap.listXccdfScans.called assert not mprint.called def test_scap_listxccdfscans_ssm_arg(self, shell): """ Test calling scap listxccdfscans with ssm argument. No systems has been scanned. :param shell: :return: """ shell.help_scap_listxccdfscans = MagicMock() shell.ssm = MagicMock() shell.ssm.keys = MagicMock(return_value=[]) shell.expand_systems = MagicMock() shell.get_system_id = MagicMock() shell.client.system.scap.listXccdfScans = MagicMock() mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_listxccdfscans(shell, "ssm") assert not shell.help_scap_listxccdfscans.called assert shell.ssm.keys.called assert not shell.expand_systems.called assert not shell.get_system_id.called assert not shell.client.system.scap.listXccdfScans.called assert not mprint.called def test_scap_listxccdfscans_system_arg(self, shell): """ Test calling scap listxccdfscans with a system name argument. :param shell: :return: """ shell.help_scap_listxccdfscans = MagicMock() shell.SEPARATOR = "---" shell.ssm = MagicMock() shell.ssm.keys = MagicMock(return_value=[]) shell.expand_systems = MagicMock(return_value=["chair-1", "table-2"]) shell.get_system_id = MagicMock(side_effect=["001", "002"]) shell.client.system.scap.listXccdfScans = MagicMock(side_effect=[ [ {"xid": 1, "profile": "001", "path": "/etc/first", "completed": "true"}, {"xid": 2, "profile": "001", "path": "/etc/second", "completed": "false"}, ], [ {"xid": 3, "profile": "002", "path": "/opt/etc/third", "completed": "false"}, {"xid": 4, "profile": "002", "path": "/opt/etc/fourth", "completed": "true"}, ], ]) mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_listxccdfscans(shell, "chair table") assert not shell.help_scap_listxccdfscans.called assert not shell.ssm.keys.called assert shell.expand_systems.called assert shell.get_system_id.called assert shell.client.system.scap.listXccdfScans.called assert mprint.called expectations = [ 'System: chair-1', '', 'XID: 1 Profile: 001 Path: (/etc/first) Completed: true', 'XID: 2 Profile: 001 Path: (/etc/second) Completed: false', shell.SEPARATOR, 'System: table-2', '', 'XID: 3 Profile: 002 Path: (/opt/etc/third) Completed: false', 'XID: 4 Profile: 002 Path: (/opt/etc/fourth) Completed: true', ] assert_expect(mprint.call_args_list, *expectations) def test_scap_getxccdfscanruleresults_noargs(self, shell): """ Test getxccdfscanruleresults without args :param shell: :return: """ shell.help_scap_getxccdfscanruleresults = MagicMock() shell.client.system.scap.getXccdfScanRuleResults = MagicMock() mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_getxccdfscanruleresults(shell, "") assert shell.help_scap_getxccdfscanruleresults.called assert not shell.client.system.scap.getXccdfScanRuleResults.called assert not mprint.called def test_scap_getxccdfscanruleresults_xids_no_rules(self, shell): """ Test getxccdfscanruleresults with XIDs but no rules :param shell: :return: """ shell.help_scap_getxccdfscanruleresults = MagicMock() shell.SEPARATOR = "---" shell.client.system.scap.getXccdfScanRuleResults = MagicMock() mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_getxccdfscanruleresults(shell, "1 2 3") assert not shell.help_scap_getxccdfscanruleresults.called assert shell.client.system.scap.getXccdfScanRuleResults.called assert mprint.called expectations = [ 'XID: 1', '', '---', 'XID: 2', '', '---', 'XID: 3', '', ] assert_expect(mprint.call_args_list, *expectations) def test_scap_getxccdfscanruleresults_xids_with_rules(self, shell): """ Test getxccdfscanruleresults with XIDs with rules :param shell: :return: """ shell.help_scap_getxccdfscanruleresults = MagicMock() shell.SEPARATOR = "---" shell.client.system.scap.getXccdfScanRuleResults = MagicMock(side_effect=[ [ {"idref": "001A", "result": "result placeholder - 1", "idents": "idents placeholder - 1"}, {"idref": "001B", "result": "result placeholder - 2", "idents": "idents placeholder - 2"}, ], [ {"idref": "002A", "result": "result placeholder - 1", "idents": "idents placeholder - 1"}, {"idref": "002B", "result": "result placeholder - 2", "idents": "idents placeholder - 2"}, ], [ {"idref": "003A", "result": "result placeholder - 1", "idents": "idents placeholder - 1"}, {"idref": "003B", "result": "result placeholder - 2", "idents": "idents placeholder - 2"}, ] ]) mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_getxccdfscanruleresults(shell, "1 2 3") assert not shell.help_scap_getxccdfscanruleresults.called assert shell.client.system.scap.getXccdfScanRuleResults.called assert mprint.called expectations = [ 'XID: 1', '', 'IDref: 001A Result: result placeholder - 1 Idents: (idents placeholder - 1)', 'IDref: 001B Result: result placeholder - 2 Idents: (idents placeholder - 2)', '---', 'XID: 2', '', 'IDref: 002A Result: result placeholder - 1 Idents: (idents placeholder - 1)', 'IDref: 002B Result: result placeholder - 2 Idents: (idents placeholder - 2)', '---', 'XID: 3', '', 'IDref: 003A Result: result placeholder - 1 Idents: (idents placeholder - 1)', 'IDref: 003B Result: result placeholder - 2 Idents: (idents placeholder - 2)', ] assert_expect(mprint.call_args_list, *expectations) def test_scap_getxccdfscandetails_no_args(self, shell): """ Test getxccdfscandetails with no args. :param shell: :return: """ shell.help_scap_getxccdfscandetails = MagicMock() shell.SEPARATOR = "---" shell.client.system.scap.getXccdfScanDetails = MagicMock() mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_getxccdfscandetails(shell, "") assert shell.help_scap_getxccdfscandetails.called assert not shell.client.system.scap.getXccdfScanDetails.called assert not mprint.called def test_scap_getxccdfscandetails_xids(self, shell): """ Test getxccdfscandetails with XID args. :param shell: :return: """ shell.help_scap_getxccdfscandetails = MagicMock() shell.SEPARATOR = "---" shell.client.system.scap.getXccdfScanDetails = MagicMock(side_effect=[ { "xid": 1, "sid": "0001", "action_id": "a1", "path": "p1", "oscap_parameters": "op1", "test_result": "tr1", "benchmark": "b1", "benchmark_version": "bv1", "profile": "p1", "profile_title": "pt1", "start_time": "st1", "end_time": "et1", "errors": "e1", }, { "xid": 2, "sid": "0002", "action_id": "a2", "path": "p2", "oscap_parameters": "op2", "test_result": "tr2", "benchmark": "b2", "benchmark_version": "bv2", "profile": "p2", "profile_title": "pt2", "start_time": "st2", "end_time": "et2", "errors": "e2", }, ]) mprint = MagicMock() with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_getxccdfscandetails(shell, "1 2") assert not shell.help_scap_getxccdfscandetails.called assert shell.client.system.scap.getXccdfScanDetails.called assert mprint.called expectations = [ (shell.session, 1), (shell.session, 2) ] for call in shell.client.system.scap.getXccdfScanDetails.call_args_list: arg, kw = call assert kw == {} assert arg == next(iter(expectations)) expectations.pop(0) expectations = [ ('XID: 1',), ('',), ('XID:', 1, 'SID:', '0001', 'Action_ID:', 'a1', 'Path:', 'p1', 'OSCAP_Parameters:', 'op1', 'Test_Result:', 'tr1', 'Benchmark:', 'b1', 'Benchmark_Version:', 'bv1', 'Profile:', 'p1', 'Profile_Title:', 'pt1', 'Start_Time:', 'st1', 'End_Time:', 'et1', 'Errors:', 'e1'), ('---',), ('XID: 2',), ('',), ('XID:', 2, 'SID:', '0002', 'Action_ID:', 'a2', 'Path:', 'p2', 'OSCAP_Parameters:', 'op2', 'Test_Result:', 'tr2', 'Benchmark:', 'b2', 'Benchmark_Version:', 'bv2', 'Profile:', 'p2', 'Profile_Title:', 'pt2', 'Start_Time:', 'st2', 'End_Time:', 'et2', 'Errors:', 'e2'), ] for call in mprint.call_args_list: arg, kw = call assert kw == {} assert arg == next(iter(expectations)) expectations.pop(0) def test_scap_schedulexccdfscan_no_args(self, shell): """ Test for do_scap_schedulexccdfscan with no args. :param shell: :return: """ shell.help_scap_schedulexccdfscan = MagicMock() shell.ssm.keys = MagicMock() shell.expand_systems = MagicMock() shell.get_system_id = MagicMock() shell.client.system.scap.scheduleXccdfScan = MagicMock() mprint = MagicMock() for insufficient_param in ["", "ssm", "/tmp foo"]: with patch("spacecmd.scap.print", mprint): spacecmd.scap.do_scap_schedulexccdfscan(shell, insufficient_param) assert shell.help_scap_schedulexccdfscan.called assert not shell.ssm.keys.called assert not shell.expand_systems.called assert not shell.get_system_id.called assert not shell.client.system.scap.scheduleXccdfScan.called assert not mprint.called def test_scap_schedulexccdfscan_ssm_arg(self, shell): """ Test for do_scap_schedulexccdfscan with SSM arg :param shell: :return: """ shell.help_scap_schedulexccdfscan = MagicMock() shell.ssm.keys = MagicMock(return_value=[]) shell.expand_systems = MagicMock() shell.get_system_id = MagicMock() shell.client.system.scap.scheduleXccdfScan = MagicMock() spacecmd.scap.do_scap_schedulexccdfscan(shell, "ssm /opt/xccdf.xml xccdf-option") assert not shell.help_scap_schedulexccdfscan.called assert shell.ssm.keys.called assert not shell.expand_systems.called assert not shell.get_system_id.called assert not shell.client.system.scap.scheduleXccdfScan.called def test_scap_schedulexccdfscan_systems_arg(self, shell): """ Test for do_scap_schedulexccdfscan with systems arg :param shell: :return: """ shell.help_scap_schedulexccdfscan = MagicMock() shell.ssm.keys = MagicMock() shell.expand_systems = MagicMock() shell.get_system_id = MagicMock() shell.client.system.scap.scheduleXccdfScan = MagicMock() spacecmd.scap.do_scap_schedulexccdfscan(shell, "/opt/xccdf.xml xccdf-option some.example.com") assert not shell.help_scap_schedulexccdfscan.called assert not shell.ssm.keys.called assert shell.expand_systems.called assert not shell.get_system_id.called assert not shell.client.system.scap.scheduleXccdfScan.called assert_expect(shell.expand_systems.call_args_list, ["some.example.com"]) def test_scap_schedulexccdfscan_systems_arg_with_data(self, shell): """ Test for do_scap_schedulexccdfscan with systems arg with data :param shell: :return: """ shell.help_scap_schedulexccdfscan = MagicMock() shell.ssm.keys = MagicMock() shell.expand_systems = MagicMock(return_value=["some.example.com"]) shell.get_system_id = MagicMock(return_value="1000010000") shell.client.system.scap.scheduleXccdfScan = MagicMock() spacecmd.scap.do_scap_schedulexccdfscan(shell, "/opt/xccdf.xml xccdf-option some.example.com") assert not shell.help_scap_schedulexccdfscan.called assert not shell.ssm.keys.called assert shell.expand_systems.called assert shell.get_system_id.called assert shell.client.system.scap.scheduleXccdfScan.called for call in shell.client.system.scap.scheduleXccdfScan.call_args_list: args, kw = call assert args == (shell.session, '1000010000', '/opt/xccdf.xml', '--xccdf-option') 07070100000037000081B40000000000000000000000015D65A5B200002287000000000000000000000000000000000000001D00000000spacecmd/tests/test_shell.py# coding: utf-8 """ Unit test for the spacecmd.shell module. """ from mock import MagicMock, patch import os import time import readline import pytest from spacecmd.shell import SpacewalkShell, UnknownCallException class TestSCShell: """ Test shell in spacecmd. """ @patch("spacecmd.shell.atexit", MagicMock()) def test_shell_history(self): """ Test history length. """ assert SpacewalkShell.HISTORY_LENGTH == 1024 @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) @patch("spacecmd.shell.sys.exit", MagicMock()) def test_shell_delimeters(self): """ Test shell delimieters are set without hyphens or colons during the tab completion. """ cfg_dir = "/tmp/shell/{}/conf".format(int(time.time())) m_logger = MagicMock() cpl_setter = MagicMock() with patch("spacecmd.shell.logging", m_logger) as lgr, \ patch("spacecmd.shell.readline.set_completer_delims", cpl_setter): options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, cfg_dir, None) assert shell.history_file == "{}/history".format(cfg_dir) assert not m_logger.error.called assert cpl_setter.call_args[0][0] != readline.get_completer_delims() assert cpl_setter.call_args[0][0] == ' \t\n`~!@#$%^&*()=+[{]}\\|;\'",<>?' @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) @patch("spacecmd.shell.sys.exit", MagicMock()) @patch("spacecmd.shell.os.path.isfile", MagicMock(side_effect=IOError("No such file"))) def test_shell_no_history_file(self): """ Test shell no history file should capture IOError and log it. """ cfg_dir = "/tmp/shell/{}/conf".format(int(time.time())) m_logger = MagicMock() cpl_setter = MagicMock() with patch("spacecmd.shell.logging", m_logger): options = MagicMock() options.nohistory = False shell = SpacewalkShell(options, cfg_dir, None) assert shell.history_file == "{}/history".format(cfg_dir) assert not os.path.exists(shell.history_file) assert m_logger.error.call_args[0][0] == "Could not read history file" @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.print", MagicMock()) @patch("spacecmd.shell.sys.exit", MagicMock(side_effect=Exception("Exit attempt"))) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_precmd_exit_keywords(self): """ Test 'precmd' method of the shell on exit keywords. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" for cmd in ["exit", "quit", "eof"]: with pytest.raises(Exception) as exc: shell.precmd(cmd) assert "Exit attempt" in str(exc) @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_precmd_common_keywords(self): """ Test 'precmd' method of the shell on common keywords, e.g. login, logout, clear etc. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" for cmd in ["help", "login", "logout", "whoami", "history", "clear"]: assert shell.precmd(cmd) == cmd @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_precmd_empty_line(self): """ Test 'precmd' method of the shell on empty line. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" assert shell.precmd("") == "" @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_precmd_session_login(self): """ Test 'precmd' method of the shell on session login. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" shell.do_login = MagicMock(side_effect=Exception("login attempt")) with pytest.raises(Exception) as exc: shell.precmd("system_list") assert "login attempt" in str(exc) @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_precmd_help_keyword(self): """ Test 'precmd' method of the shell on --help/-h arguments. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" shell.session = True assert shell.precmd("system_list --help") == "help system_list" assert shell.precmd("system_list -h") == "help system_list" @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_precmd_one_char_cmd(self): """ Test 'precmd' method one char. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" shell.session = True assert shell.precmd("x") == "x" @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.print", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_history_item", MagicMock(return_value="repeated item")) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_precmd_history(self): """ Test 'precmd' method getting history item. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" shell.session = True assert shell.precmd("!!") == "repeated item" @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.SpacewalkShell.print_result", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_postcmd(self): """ Test 'postcmd' method of the shell. """ options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" shell.session = True shell.ssm = {1: "one"} shell.postcmd("result", "command") assert shell.prompt == "spacecmd {SSM:1}> " @patch("spacecmd.shell.atexit", MagicMock()) @patch("spacecmd.shell.readline.set_completer_delims", MagicMock()) @patch("spacecmd.shell.readline.get_completer_delims", MagicMock(return_value=readline.get_completer_delims())) def test_shell_default(self): """ Test 'default' method of the shell. """ cmd = MagicMock() with patch("spacecmd.shell.Cmd.default", cmd): options = MagicMock() options.nohistory = True shell = SpacewalkShell(options, "", None) shell.config["server"] = "" shell.session = True with pytest.raises(UnknownCallException): shell.default("test") assert cmd.call_args[0][1] == "test" 07070100000038000081B40000000000000000000000015D65A5B200003A72000000000000000000000000000000000000001F00000000spacecmd/tests/test_snippet.py# coding: utf-8 """ Test suite for snippet source """ from mock import MagicMock, patch, mock_open from spacecmd import snippet from helpers import shell, assert_expect import pytest class TestSCSnippets: """ Test for snippet API. """ def test_snippet_list_noarg(self, shell): """ Test snippet list noargs """ snippets = [ {"name": "snippet - 3"}, {"name": "snippet - 1"}, {"name": "snippet - 2"}, ] mprint = MagicMock() shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) with patch("spacecmd.snippet.print", mprint): out = snippet.do_snippet_list(shell, "") assert out is None assert mprint.call_args_list[0][0] == ('snippet - 1\nsnippet - 2\nsnippet - 3',) # Sorted def test_snippet_list_args(self, shell): """ Test snippet list with the args """ snippets = [ {"name": "snippet - 3"}, {"name": "snippet - 1"}, {"name": "snippet - 2"}, ] shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) mprint = MagicMock() with patch("spacecmd.snippet.print", mprint): out = snippet.do_snippet_list(shell, "", doreturn=True) assert out is not None assert out == ['snippet - 1', 'snippet - 2', 'snippet - 3'] # Sorted def test_snippet_details_noarg(self, shell): """ Test snippet details no args """ snippets = [ {"name": "snippet - 3"}, {"name": "snippet - 1"}, {"name": "snippet - 2"}, ] shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) shell.help_snippet_details = MagicMock() mprint = MagicMock() logger = MagicMock() with patch("spacecmd.snippet.print", mprint) as mpr, patch("spacecmd.snippet.logging", logger) as lgr: snippet.do_snippet_details(shell, "") assert not mprint.called assert not logger.warning.called assert shell.help_snippet_details.called def test_snippet_details_args(self, shell): """ Test snippet details with the args """ snippets = [ {"name": "snippet3", "contents": "three", "fragment": "3rd fragment", "file": "/tmp/3"}, {"name": "snippet1", "contents": "one", "fragment": "1st fragment", "file": "/tmp/1"}, {"name": "snippet2", "contents": "two", "fragment": "2nd fragment", "file": "/tmp/2"}, ] shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) shell.SEPARATOR = "---" shell.help_snippet_details = MagicMock() mprint = MagicMock() logger = MagicMock() with patch("spacecmd.snippet.print", mprint) as mpr, patch("spacecmd.snippet.logging", logger) as lgr: snippet.do_snippet_details(shell, "snippet4 snippet5 snippet3 snippet1") assert not shell.help_snippet_details.called assert logger.warning.called assert mprint.called calls = [ "snippet4 is not a valid snippet", "snippet5 is not a valid snippet", ] assert_expect(logger.warning.call_args_list, *calls) stdout_data = [ 'Name: snippet3', 'Macro: 3rd fragment', 'File: /tmp/3', '', 'three', '---', 'Name: snippet1', 'Macro: 1st fragment', 'File: /tmp/1', '', 'one', ] assert_expect(mprint.call_args_list, *stdout_data) def test_snippet_create_no_args(self, shell): """ Test create snippet with no arguments and no name update. """ snippets = [ {"name": "snippet3", "contents": "three", "fragment": "3rd fragment", "file": "/tmp/3"}, {"name": "snippet1", "contents": "one", "fragment": "1st fragment", "file": "/tmp/1"}, {"name": "snippet2", "contents": "two", "fragment": "2nd fragment", "file": "/tmp/2"}, ] prompt_user = MagicMock(side_effect=["custom-snippet", "/tmp/cs.snip"]) logger = MagicMock() editor = MagicMock(return_value=("editor content", False)) read_file = MagicMock(return_value="file content") mprint = MagicMock() shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) shell.client.kickstart.snippet.createOrUpdate = MagicMock() shell.user_confirm = MagicMock(return_value=True) with patch("spacecmd.snippet.logging", logger) as lgr, \ patch("spacecmd.snippet.prompt_user", prompt_user) as prmt, \ patch("spacecmd.snippet.editor", editor) as edtr, \ patch("spacecmd.snippet.read_file", read_file) as rfl, \ patch("spacecmd.snippet.print", mprint) as prn: snippet.do_snippet_create(shell, "") assert not logger.error.called assert not shell.client.kickstart.snippet.listCustom.called assert shell.user_confirm.called assert shell.client.kickstart.snippet.createOrUpdate.called assert_expect(prompt_user.call_args_list, "Name:", "File:") assert_expect(mprint.call_args_list, "", "Snippet: custom-snippet", "Contents", "--------", "file content") def test_snippet_create_name_arg(self, shell): """ Test create snippet with only name argument """ snippets = [ {"name": "snippet3", "contents": "three", "fragment": "3rd fragment", "file": "/tmp/3"}, {"name": "snippet1", "contents": "one", "fragment": "1st fragment", "file": "/tmp/1"}, {"name": "snippet2", "contents": "two", "fragment": "2nd fragment", "file": "/tmp/2"}, ] prompt_user = MagicMock(side_effect=["custom-snippet", "/tmp/cs.snip"]) logger = MagicMock() editor = MagicMock(return_value=("editor content", False)) read_file = MagicMock(return_value="file content") mprint = MagicMock() shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) shell.client.kickstart.snippet.createOrUpdate = MagicMock() shell.user_confirm = MagicMock(return_value=True) with patch("spacecmd.snippet.logging", logger) as lgr, \ patch("spacecmd.snippet.prompt_user", prompt_user) as prmt, \ patch("spacecmd.snippet.editor", editor) as edtr, \ patch("spacecmd.snippet.read_file", read_file) as rfl, \ patch("spacecmd.snippet.print", mprint) as prn: snippet.do_snippet_create(shell, "--name something") assert not shell.client.kickstart.snippet.listCustom.called assert not shell.client.kickstart.snippet.createOrUpdate.called assert not shell.user_confirm.called assert not mprint.called assert not prompt_user.called assert logger.error.called assert logger.error.call_args_list[0][0][0] == "A file is required" def test_snippet_create_file_arg(self, shell): """ Test create snippet with only file argument """ snippets = [ {"name": "snippet3", "contents": "three", "fragment": "3rd fragment", "file": "/tmp/3"}, {"name": "snippet1", "contents": "one", "fragment": "1st fragment", "file": "/tmp/1"}, {"name": "snippet2", "contents": "two", "fragment": "2nd fragment", "file": "/tmp/2"}, ] prompt_user = MagicMock(side_effect=["custom-snippet", "/tmp/cs.snip"]) logger = MagicMock() editor = MagicMock(return_value=("editor content", False)) read_file = MagicMock(return_value="file content") mprint = MagicMock() shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) shell.client.kickstart.snippet.createOrUpdate = MagicMock() shell.user_confirm = MagicMock(return_value=True) with patch("spacecmd.snippet.logging", logger) as lgr, \ patch("spacecmd.snippet.prompt_user", prompt_user) as prmt, \ patch("spacecmd.snippet.editor", editor) as edtr, \ patch("spacecmd.snippet.read_file", read_file) as rfl, \ patch("spacecmd.snippet.print", mprint) as prn: snippet.do_snippet_create(shell, "--file /path/to/somewhere.snip") assert not shell.client.kickstart.snippet.listCustom.called assert not shell.client.kickstart.snippet.createOrUpdate.called assert not shell.user_confirm.called assert not mprint.called assert not prompt_user.called assert logger.error.called assert logger.error.call_args_list[0][0][0] == "A name is required for the snippet" def test_snippet_create_args(self, shell): """ Test create snippet with arguments. """ snippets = [ {"name": "snippet3", "contents": "three", "fragment": "3rd fragment", "file": "/tmp/3"}, {"name": "snippet1", "contents": "one", "fragment": "1st fragment", "file": "/tmp/1"}, {"name": "snippet2", "contents": "two", "fragment": "2nd fragment", "file": "/tmp/2"}, ] prompt_user = MagicMock(side_effect=["custom-snippet", "/tmp/cs.snip"]) logger = MagicMock() editor = MagicMock(return_value=("editor content", False)) read_file = MagicMock(return_value="file content") mprint = MagicMock() shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) shell.client.kickstart.snippet.createOrUpdate = MagicMock() shell.user_confirm = MagicMock(return_value=True) with patch("spacecmd.snippet.logging", logger) as lgr, \ patch("spacecmd.snippet.prompt_user", prompt_user) as prmt, \ patch("spacecmd.snippet.editor", editor) as edtr, \ patch("spacecmd.snippet.read_file", read_file) as rfl, \ patch("spacecmd.snippet.print", mprint) as prn: snippet.do_snippet_create(shell, "--name foobar --file /path/to/somewhere.snip") assert not logger.error.called assert not shell.client.kickstart.snippet.listCustom.called assert not prompt_user.called assert shell.client.kickstart.snippet.createOrUpdate.called assert shell.user_confirm.called assert mprint.called assert_expect(mprint.call_args_list, "", "Snippet: foobar", "Contents", "--------", "file content") def test_snippet_create_editor(self, shell): """ Test create snippet using the editor. """ snippets = [ {"name": "snippet3", "contents": "three", "fragment": "3rd fragment", "file": "/tmp/3"}, {"name": "snippet1", "contents": "one", "fragment": "1st fragment", "file": "/tmp/1"}, {"name": "snippet2", "contents": "two", "fragment": "2nd fragment", "file": "/tmp/2"}, ] prompt_user = MagicMock(side_effect=["custom-snippet", "/tmp/cs.snip"]) logger = MagicMock() editor = MagicMock(return_value=("editor content", False)) read_file = MagicMock(return_value="file content") mprint = MagicMock() shell.client.kickstart.snippet.listCustom = MagicMock(return_value=snippets) shell.client.kickstart.snippet.createOrUpdate = MagicMock() shell.user_confirm = MagicMock(side_effect=[False, True]) with patch("spacecmd.snippet.logging", logger) as lgr, \ patch("spacecmd.snippet.prompt_user", prompt_user) as prmt, \ patch("spacecmd.snippet.editor", editor) as edtr, \ patch("spacecmd.snippet.read_file", read_file) as rfl, \ patch("spacecmd.snippet.print", mprint) as prn: snippet.do_snippet_create(shell, "") assert not logger.error.called assert not shell.client.kickstart.snippet.listCustom.called assert prompt_user.called assert shell.client.kickstart.snippet.createOrUpdate.called assert shell.user_confirm.called assert mprint.called assert editor.called assert_expect(mprint.call_args_list, "", "Snippet: custom-snippet", "Contents", "--------", "editor content") def test_snippet_update_no_args(self, shell): """ Test update snippet with no args """ shell.do_snippet_create = MagicMock() shell.help_snippet_update = MagicMock() out = snippet.do_snippet_update(shell, "") assert shell.help_snippet_update.called assert out is None def test_snippet_update_args(self, shell): """ Test update snippet with args """ shell.do_snippet_create = MagicMock() shell.help_snippet_update = MagicMock() out = snippet.do_snippet_update(shell, "custom_name") assert not shell.help_snippet_update.called assert out is not None assert shell.do_snippet_create.called assert not shell.do_snippet_create.call_args_list[0][0][0] assert "update_name" in shell.do_snippet_create.call_args_list[0][1] assert shell.do_snippet_create.call_args_list[0][1]["update_name"] == "custom_name" def test_snippet_delete_no_args(self, shell): """ Test delete snippet with no args """ shell.client.kickstart.snippet.delete = MagicMock() shell.help_snippet_delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) out = snippet.do_snippet_delete(shell, "") assert not shell.client.kickstart.snippet.delete.called assert shell.help_snippet_delete.called assert out is None def test_snippet_delete_args(self, shell): """ Test delete snippet with args (snippet name) """ shell.client.kickstart.snippet.delete = MagicMock() shell.help_snippet_delete = MagicMock() shell.user_confirm = MagicMock(return_value=True) out = snippet.do_snippet_delete(shell, "some-snippet") assert shell.client.kickstart.snippet.delete.called assert not shell.help_snippet_delete.called assert out is None assert shell.client.kickstart.snippet.delete.call_args_list[0][0][0] == shell.session assert shell.client.kickstart.snippet.delete.call_args_list[0][0][1] == "some-snippet" def test_snippet_delete_args_no_confirm(self, shell): """ Test delete snippet with args (snippet name), unconfirmed. """ shell.client.kickstart.snippet.delete = MagicMock() shell.help_snippet_delete = MagicMock() shell.user_confirm = MagicMock(return_value=False) out = snippet.do_snippet_delete(shell, "some-snippet") assert not shell.client.kickstart.snippet.delete.called assert not shell.help_snippet_delete.called assert out is None 07070100000039000081B40000000000000000000000015D65A5B200002D1A000000000000000000000000000000000000001B00000000spacecmd/tests/test_ssm.py# coding: utf-8 """ Test suite for the SSM module commands. """ from mock import MagicMock, patch, mock_open from spacecmd import ssm from helpers import shell, assert_expect import pytest class TestSCSSM: """ Test for SSM module API. """ def test_ssm_add_noarg(self, shell): """ Test do_ssm_add no args. :param shell: :return: """ shell.help_ssm_add = MagicMock() shell.expand_systems = MagicMock(return_value=[]) shell.ssm = {} shell.get_system_id = MagicMock(return_value=None) logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_add(shell, "") assert not logger.warning.called assert not logger.debug.called assert shell.help_ssm_add.called def test_ssm_add_system_not_found(self, shell): """ Test do_ssm_add a system that does not exists. :param shell: :return: """ shell.help_ssm_add = MagicMock() shell.expand_systems = MagicMock(return_value=[]) shell.ssm = {} shell.get_system_id = MagicMock(return_value=None) logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_add(shell, "example.com") assert logger.warning.called assert not logger.debug.called assert not shell.help_ssm_add.called assert_expect(logger.warning.call_args_list, "No systems found") def test_ssm_add_system_already_in_list(self, shell): """ Test do_ssm_add a system that already in the list. :param shell: :return: """ shell.help_ssm_add = MagicMock() shell.expand_systems = MagicMock(return_value=["example.com"]) shell.ssm = {"example.com": {}} shell.ssm_cache_file = "/tmp/ssm_cache_file" shell.get_system_id = MagicMock(return_value=None) logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_add(shell, "example.com") assert logger.warning.called assert logger.debug.called assert not shell.help_ssm_add.called assert save_cache.called assert_expect(logger.warning.call_args_list, "example.com is already in the list") assert_expect(logger.debug.call_args_list, "Systems Selected: 1") for call in save_cache.call_args_list: args, kw = call assert not kw assert args == (shell.ssm_cache_file, shell.ssm) def test_ssm_add_system_new(self, shell): """ Test do_ssm_add a new system. :param shell: :return: """ shell.help_ssm_add = MagicMock() shell.expand_systems = MagicMock(return_value=["new.com"]) shell.ssm = {"example.com": {}} shell.ssm_cache_file = "/tmp/ssm_cache_file" shell.get_system_id = MagicMock(return_value={"name": "new.com"}) logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_add(shell, "new.com") assert not logger.warning.called assert not shell.help_ssm_add.called assert logger.debug.called assert save_cache.called exp = ["Added new.com", "Systems Selected: 2"] for call in logger.debug.call_args_list: assert_expect([call], next(iter(exp))) exp.pop(0) for call in save_cache.call_args_list: args, kw = call assert not kw assert args == (shell.ssm_cache_file, shell.ssm) def test_ssm_intersect_noarg(self, shell): """ Test do_ssm_intersect without arguments. :param shell: :return: """ shell.help_ssm_intersect = MagicMock() shell.expand_systems = MagicMock(return_value=["new.com"]) shell.ssm = {"example.com": {}} shell.ssm_cache_file = "/tmp/ssm_cache_file" logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_intersect(shell, "") assert shell.help_ssm_intersect.called assert not logger.warning.called assert not logger.debug.called assert not save_cache.called def test_ssm_intersect_no_systems_found(self, shell): """ Test do_ssm_intersect when no given systems found. :param shell: :return: """ shell.help_ssm_intersect = MagicMock() shell.expand_systems = MagicMock(return_value=[]) shell.ssm = {} shell.ssm_cache_file = "/tmp/ssm_cache_file" logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_intersect(shell, "unknown") assert not shell.help_ssm_intersect.called assert logger.warning.called assert not logger.debug.called assert not save_cache.called assert_expect(logger.warning.call_args_list, "No systems found") def test_ssm_intersect(self, shell): """ Test do_ssm_intersect when no given systems found. :param shell: :return: """ shell.help_ssm_intersect = MagicMock() shell.expand_systems = MagicMock(return_value=["existing.com"]) shell.ssm = { "brexit.co.uk": {"name": "finished"}, "existing.com": {"name": "keptalive"}, } shell.ssm_cache_file = "/tmp/ssm_cache_file" logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_intersect(shell, "existing.com") assert not shell.help_ssm_intersect.called assert not logger.warning.called assert logger.debug.called assert save_cache.called exp = ["existing.com is in both groups: leaving in SSM", "Systems Selected: 1"] for call in logger.debug.call_args_list: assert_expect([call], next(iter(exp))) exp.pop(0) assert shell.ssm == {'existing.com': {'name': 'keptalive'}} for call in save_cache.call_args_list: args, kw = call assert not kw assert args == (shell.ssm_cache_file, shell.ssm) def test_ssm_remove_noarg(self, shell): """ Test do_ssm_remove without arguments. :param shell: :return: """ shell.help_ssm_remove = MagicMock() shell.expand_systems = MagicMock(return_value=["new.com"]) shell.ssm = {"example.com": {}} shell.ssm_cache_file = "/tmp/ssm_cache_file" logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_remove(shell, "") assert shell.help_ssm_remove.called assert not logger.warning.called assert not logger.debug.called assert not save_cache.called def test_ssm_remove_no_systems_found(self, shell): """ Test do_ssm_remove without arguments. :param shell: :return: """ shell.help_ssm_remove = MagicMock() shell.expand_systems = MagicMock(return_value=[]) shell.ssm = {"example.com": {}} shell.ssm_cache_file = "/tmp/ssm_cache_file" logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_remove(shell, "unknown") assert not shell.help_ssm_remove.called assert logger.warning.called assert not logger.debug.called assert not save_cache.called assert_expect(logger.warning.call_args_list, "No systems found") def test_ssm_remove(self, shell): """ Test do_ssm_remove without arguments. :param shell: :return: """ shell.help_ssm_remove = MagicMock() shell.expand_systems = MagicMock(return_value=["remove.me"]) shell.ssm = {"remove.me": {}, "keepalive.io": {}} shell.ssm_cache_file = "/tmp/ssm_cache_file" logger = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc: ssm.do_ssm_remove(shell, "unknown") assert not shell.help_ssm_remove.called assert not logger.warning.called assert logger.debug.called assert save_cache.called assert shell.ssm == {'keepalive.io': {}} exp = ["Removed remove.me", "Systems Selected: 1"] for call in logger.debug.call_args_list: assert_expect([call], next(iter(exp))) exp.pop(0) for call in save_cache.call_args_list: args, kw = call assert not kw assert args == (shell.ssm_cache_file, shell.ssm) def test_ssm_list(self): """ Test do_ssm_list for listing of the systems in the SSM group. :return: """ shell.help_ssm_list = MagicMock() shell.ssm = {"remove.me": {}, "keepalive.io": {}} for args in ["unknown", ""]: logger = MagicMock() mprint = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc, \ patch("spacecmd.ssm.print", mprint) as prn: ssm.do_ssm_list(shell, args=args) assert len(shell.ssm) == 2 assert mprint.called assert not save_cache.called assert_expect(mprint.call_args_list, "keepalive.io\nremove.me") def test_ssm_clear(self): """ Test do_ssm_list for listing of the systems in the SSM group. :return: """ shell.ssm_cache_file = "/tmp/ssm_cache_file" for args in ["unknown", ""]: shell.ssm = {"remove.me": {}, "keepalive.io": {}} logger = MagicMock() mprint = MagicMock() save_cache = MagicMock() with patch("spacecmd.ssm.logging", logger) as lgr, \ patch("spacecmd.ssm.save_cache", save_cache) as svc, \ patch("spacecmd.ssm.print", mprint) as prn: ssm.do_ssm_clear(shell, args=args) assert not shell.ssm assert not mprint.called assert not logger.warning.called assert not logger.debug.called assert save_cache.called for call in save_cache.call_args_list: args, kw = call assert not kw assert args == (shell.ssm_cache_file, shell.ssm) 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