File spacecmd-git-0.51b4b12.obscpio of Package spacecmd.11261
07070100000000000041FD0000000000000000000000015CDC25DC00000000000000000000000000000000000000000000000900000000spacecmd07070100000001000081B40000000000000000000000015CDC25DC00000010000000000000000000000000000000000000001400000000spacecmd/.gitignore*~
*.pyc
*.swp
07070100000002000081B40000000000000000000000015CDC25DC0000048A000000000000000000000000000000000000001900000000spacecmd/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
07070100000003000081B40000000000000000000000015CDC25DC00000359000000000000000000000000000000000000001B00000000spacecmd/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
07070100000004000081B40000000000000000000000015CDC25DC00001396000000000000000000000000000000000000001200000000spacecmd/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=
07070100000005000081B40000000000000000000000015CDC25DC0000032B000000000000000000000000000000000000001200000000spacecmd/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.11"
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",
}
)
07070100000006000081B40000000000000000000000015CDC25DC0000138F000000000000000000000000000000000000001B00000000spacecmd/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=
07070100000007000081B40000000000000000000000015CDC25DC00004E79000000000000000000000000000000000000001A00000000spacecmd/spacecmd.changes-------------------------------------------------------------------
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
-------------------------------------------------------------------
07070100000008000081B40000000000000000000000015CDC25DC000010AA000000000000000000000000000000000000001700000000spacecmd/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.11
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
BuildRequires: python3-rpm
BuildRequires: python3-simplejson
Requires: python3
%else
BuildRequires: python
BuildRequires: python-devel
BuildRequires: python-simplejson
BuildRequires: 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
07070100000009000041FD0000000000000000000000015CDC25DC00000000000000000000000000000000000000000000000D00000000spacecmd/src0707010000000A000041FD0000000000000000000000015CDC25DC00000000000000000000000000000000000000000000001100000000spacecmd/src/bin0707010000000B000081FD0000000000000000000000015CDC25DC00001EBC000000000000000000000000000000000000001A00000000spacecmd/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)
0707010000000C000041FD0000000000000000000000015CDC25DC00000000000000000000000000000000000000000000001100000000spacecmd/src/doc0707010000000D000081B40000000000000000000000015CDC25DC0000894B000000000000000000000000000000000000001900000000spacecmd/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>.
0707010000000E000081B40000000000000000000000015CDC25DC00000D04000000000000000000000000000000000000001800000000spacecmd/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
0707010000000F000081B40000000000000000000000015CDC25DC000017BC000000000000000000000000000000000000001C00000000spacecmd/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>
07070100000010000041FD0000000000000000000000015CDC25DC00000000000000000000000000000000000000000000001200000000spacecmd/src/misc07070100000011000081B40000000000000000000000015CDC25DC000001E9000000000000000000000000000000000000002B00000000spacecmd/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
07070100000012000041FD0000000000000000000000015CDC25DC00000000000000000000000000000000000000000000001600000000spacecmd/src/spacecmd07070100000013000081B40000000000000000000000015CDC25DC00000029000000000000000000000000000000000000002200000000spacecmd/src/spacecmd/__init__.py# coding: utf-8
"""
Package spacecmd
"""
07070100000014000081B40000000000000000000000015CDC25DC0000DF3A000000000000000000000000000000000000002700000000spacecmd/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)
07070100000015000081B40000000000000000000000015CDC25DC00000B67000000000000000000000000000000000000001D00000000spacecmd/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()
07070100000016000081B40000000000000000000000015CDC25DC0000054C000000000000000000000000000000000000002800000000spacecmd/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)
07070100000017000081B40000000000000000000000015CDC25DC0000E780000000000000000000000000000000000000002700000000spacecmd/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))
07070100000018000081B40000000000000000000000015CDC25DC00001712000000000000000000000000000000000000002300000000spacecmd/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)
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)
# 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'))
07070100000019000081B40000000000000000000000015CDC25DC00001563000000000000000000000000000000000000002400000000spacecmd/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 %s, keys=%s" %
(args, keys))
if not keys:
logging.error("No keys matched argument %s" % args)
return
add_separator = False
all_keys = self.client.system.custominfo.listAllKeys(self.session)
for key in keys:
for k in all_keys:
if k.get('label') == key:
details = k
if add_separator:
print(self.SEPARATOR)
add_separator = True
print('Label: %s' % details.get('label'))
print('Description: %s' % details.get('description'))
print('Modified: %s' % details.get('last_modified'))
print('System Count: %i' % details.get('system_count'))
####################
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)
0707010000001A000081B40000000000000000000000015CDC25DC000022B8000000000000000000000000000000000000002600000000spacecmd/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):
return self.do_distribution_create(args, update=True)
0707010000001B000081B40000000000000000000000015CDC25DC000045BB000000000000000000000000000000000000002000000000spacecmd/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 []
0707010000001C000081B40000000000000000000000015CDC25DC00001070000000000000000000000000000000000000002A00000000spacecmd/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'))))
0707010000001D000081B40000000000000000000000015CDC25DC00003290000000000000000000000000000000000000001F00000000spacecmd/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)))
0707010000001E000081B40000000000000000000000015CDC25DC0001577B000000000000000000000000000000000000002300000000spacecmd/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)
0707010000001F000081B40000000000000000000000015CDC25DC000070C8000000000000000000000000000000000000001E00000000spacecmd/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.iteritems():
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)
07070100000020000081B40000000000000000000000015CDC25DC00002F41000000000000000000000000000000000000001D00000000spacecmd/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)
07070100000021000081B40000000000000000000000015CDC25DC00002C73000000000000000000000000000000000000002100000000spacecmd/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)
07070100000022000081B40000000000000000000000015CDC25DC00003216000000000000000000000000000000000000001E00000000spacecmd/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)
07070100000023000081B40000000000000000000000015CDC25DC00002A21000000000000000000000000000000000000002000000000spacecmd/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('')
07070100000024000081B40000000000000000000000015CDC25DC00001698000000000000000000000000000000000000001E00000000spacecmd/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('')
print('Example:')
print('> scap_schedulexccdfscan \'/usr/share/openscap/scap-security-xccdf.xml\'' +
' \'profile Web-Default\' system-scap.example.com')
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)
07070100000025000081B40000000000000000000000015CDC25DC000034B3000000000000000000000000000000000000002200000000spacecmd/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)
07070100000026000081B40000000000000000000000015CDC25DC00001E11000000000000000000000000000000000000001F00000000spacecmd/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
07070100000027000081B40000000000000000000000015CDC25DC00001661000000000000000000000000000000000000002100000000spacecmd/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 = [s.get('name') for s in snippets]
if doreturn:
return snippets
else:
if snippets:
print('\n'.join(sorted(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)
07070100000028000081B40000000000000000000000015CDC25DC00018090000000000000000000000000000000000000002900000000spacecmd/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 args:
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))
07070100000029000081B40000000000000000000000015CDC25DC000016D6000000000000000000000000000000000000001D00000000spacecmd/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)
0707010000002A000081B40000000000000000000000015CDC25DC0001D9D1000000000000000000000000000000000000002000000000spacecmd/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)
####################
0707010000002B000081B40000000000000000000000015CDC25DC00004AA8000000000000000000000000000000000000001E00000000spacecmd/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)
0707010000002C000081B40000000000000000000000015CDC25DC000061E2000000000000000000000000000000000000001F00000000spacecmd/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
0707010000002D000041FD0000000000000000000000015CDC25DC00000000000000000000000000000000000000000000000F00000000spacecmd/tests0707010000002E000081B40000000000000000000000015CDC25DC000004D3000000000000000000000000000000000000001A00000000spacecmd/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
0707010000002F000081B40000000000000000000000015CDC25DC00015B49000000000000000000000000000000000000002500000000spacecmd/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.
"""
print()
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
print()
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, "")
print()
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")
print()
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"
07070100000030000081B40000000000000000000000015CDC25DC00000A84000000000000000000000000000000000000001B00000000spacecmd/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
07070100000031000081B40000000000000000000000015CDC25DC000001FF000000000000000000000000000000000000002600000000spacecmd/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)
07070100000032000081B40000000000000000000000015CDC25DC000013B5000000000000000000000000000000000000002800000000spacecmd/tests/test_filepreservation.py# coding: utf-8
"""
Test suite for spacecmd.filepreservation
"""
from 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
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"
07070100000033000081B40000000000000000000000015CDC25DC00002227000000000000000000000000000000000000001D00000000spacecmd/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.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.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"
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!