spacecmd/src/lib/configchannel.py | 204 +++++++++++++++----------------
spacecmd/src/lib/system.py | 250 ++++++++++++++++++++++++++++++++++++++
spacecmd/src/lib/utils.py | 16 ++
3 files changed, 365 insertions(+), 105 deletions(-)
New commits:
commit 37dfdbd89c0af5d2583b181e6a00c57871c89716
Author: Steven Hardy <shardy(a)redhat.com>
Date: Wed Apr 25 11:41:00 2012 +0100
spacecmd : enhancement add system_addconfigfile
Adds system_addconfigfile which can add system-sandbox and locally-managed
config files
diff --git a/spacecmd/src/lib/configchannel.py b/spacecmd/src/lib/configchannel.py
index e8994ca..7410b95 100644
--- a/spacecmd/src/lib/configchannel.py
+++ b/spacecmd/src/lib/configchannel.py
@@ -376,91 +376,22 @@ def do_configchannel_delete(self, args):
####################
-def help_configchannel_addfile(self):
- print 'configchannel_addfile: Create a configuration file'
- print '''usage: configchannel_addfile [CHANNEL] [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
-
- 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=''):
- options = [ Option('-c', '--channel', action='store'),
- Option('-p', '--path', action='store'),
- Option('-o', '--owner', action='store'),
- Option('-g', '--group', action='store'),
- Option('-m', '--mode', action='store'),
- Option('-x', '--selinux-ctx', action='store'),
- Option('-t', '--target-path', action='store'),
- Option('-f', '--file', action='store'),
- Option('-r', '--revision', action='store'),
- Option('-s', '--symlink', action='store_true'),
- Option('-b', '--binary', action='store_true'),
- Option('-d', '--directory', action='store_true')
]
-
- (args, options) = parse_arguments(args, options)
+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 is_interactive(options):
- # the channel name can be passed in
- if len(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:
- fileinfo = \
- self.client.configchannel.lookupFileInfo(self.session,
- options.channel,
- [ options.path ])
- except:
- fileinfo = None
-
+ if interactive:
# use existing values if available
- if fileinfo:
- for info in fileinfo:
+ if file_info:
+ for info in file_info:
if info.get('path') == options.path:
logging.debug('Found existing file in channel')
@@ -540,16 +471,6 @@ def do_configchannel_addfile(self, args, update_path=''):
contents = editor(template = template, delete = True)
else:
- # the channel name can be passed in without -c
- if len(args):
- options.channel = args[0]
-
- if not options.channel:
- logging.error("The channel name is required")
- return
-
- logging.debug("Using channel %s" % options.channel)
-
if not options.path:
logging.error('The path is required')
return
@@ -629,6 +550,94 @@ def do_configchannel_addfile(self, args, update_path=''):
print '--------'
print file_info['contents']
+ return file_info
+
+def help_configchannel_addfile(self):
+ print 'configchannel_addfile: Create a configuration file'
+ print '''usage: configchannel_addfile [CHANNEL] [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
+
+ 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=''):
+ options = [ Option('-c', '--channel', action='store'),
+ Option('-p', '--path', action='store'),
+ Option('-o', '--owner', action='store'),
+ Option('-g', '--group', action='store'),
+ Option('-m', '--mode', action='store'),
+ Option('-x', '--selinux-ctx', action='store'),
+ Option('-t', '--target-path', action='store'),
+ Option('-f', '--file', action='store'),
+ Option('-r', '--revision', action='store'),
+ Option('-s', '--symlink', action='store_true'),
+ Option('-b', '--binary', action='store_true'),
+ Option('-d', '--directory', action='store_true')
]
+
+ (args, options) = parse_arguments(args, options)
+
+ file_info = None
+
+ interactive = is_interactive(options)
+ if interactive:
+ # the channel name can be passed in
+ if len(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:
+ 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 self.user_confirm():
if options.symlink:
self.client.configchannel.createOrUpdateSymlink(self.session,
diff --git a/spacecmd/src/lib/system.py b/spacecmd/src/lib/system.py
index 7f14346..f8267b1 100644
--- a/spacecmd/src/lib/system.py
+++ b/spacecmd/src/lib/system.py
@@ -1117,6 +1117,143 @@ def do_system_listconfigfiles(self, args):
####################
+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=''):
+ options = [ Option('-S', '--sandbox', action='store_true'),
+ Option('-L', '--local', action='store_true'),
+ Option('-p', '--path', action='store'),
+ Option('-o', '--owner', action='store'),
+ Option('-g', '--group', action='store'),
+ Option('-m', '--mode', action='store'),
+ Option('-x', '--selinux-ctx', action='store'),
+ Option('-t', '--target-path', action='store'),
+ Option('-f', '--file', action='store'),
+ Option('-r', '--revision', action='store'),
+ Option('-s', '--symlink', action='store_true'),
+ Option('-b', '--binary', action='store_true'),
+ Option('-d', '--directory', action='store_true')
]
+
+ (args, options) = parse_arguments(args, options)
+
+ file_info = None
+
+ # the system name can be passed in
+ if len(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:
+ 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:
+# # compatibility for Satellite 5.3
+# if not self.check_api_version('10.11'):
+# del file_info['selinux_ctx']
+#
+# if file_info.has_key('revision'):
+# del file_info['revision']
+#
+ 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]
commit 63cd27ce358e0a9e6298437741ac686f427cff3c
Author: Steven Hardy <shardy(a)redhat.com>
Date: Wed Apr 11 08:03:21 2012 +0100
spacecmd : move file_needs_b64_enc into utils
Move file_needs_b64_enc as it will be used in both congfigchannel.py and system.py
diff --git a/spacecmd/src/lib/configchannel.py b/spacecmd/src/lib/configchannel.py
index e0364c2..e8994ca 100644
--- a/spacecmd/src/lib/configchannel.py
+++ b/spacecmd/src/lib/configchannel.py
@@ -376,22 +376,6 @@ def do_configchannel_delete(self, args):
####################
-def file_needs_b64_enc(self, contents):
-
- # we try to catch files automatically which will not work
- # correctly via the API so that they get uploaded correctly
-
- # Files with trailing newlines, which the API strips from files
- # uploaded as text, to avoid this we upload them as base64 encoded
- if contents != contents.rstrip():
- logging.info("trailing newlines detected, uploading as binary")
- return True
-
- # TODO : Add other exceptions here, e.g those containing characters which
- # are valid ascii but not valid XML (e.g the escape character)
-
- return False
-
def help_configchannel_addfile(self):
print 'configchannel_addfile: Create a configuration file'
print '''usage: configchannel_addfile [CHANNEL] [options]
diff --git a/spacecmd/src/lib/utils.py b/spacecmd/src/lib/utils.py
index 628d9f2..6292f5a 100644
--- a/spacecmd/src/lib/utils.py
+++ b/spacecmd/src/lib/utils.py
@@ -633,4 +633,20 @@ def json_read_from_file(filename):
print "could not open file %s for reading, check permissions?" %
filename
return None
+def file_needs_b64_enc(self, contents):
+
+ # Used to check if files (config files primarily) need base64 encoding
+ # in order to work properly via the API
+
+ # Files with trailing newlines, which the API strips from files
+ # uploaded as text, to avoid this we upload them as base64 encoded
+ if contents != contents.rstrip():
+ logging.info("trailing newlines detected, uploading as binary")
+ return True
+
+ # TODO : Add other exceptions here, e.g those containing characters which
+ # are valid ascii but not valid XML (e.g the escape character)
+
+ return False
+
# vim:ts=4:expandtab:
commit 20f7f144807bb6eda5570a745d31d84c1576ab5e
Author: Steven Hardy <shardy(a)redhat.com>
Date: Tue Apr 10 08:17:07 2012 +0100
spacecmd : Fix usage for configchannel_addfile
Fix usage for configchannel_addfile, which contained some slightly inaccurate
information re binary/base64 mode.
diff --git a/spacecmd/src/lib/configchannel.py b/spacecmd/src/lib/configchannel.py
index 5b4701b..e0364c2 100644
--- a/spacecmd/src/lib/configchannel.py
+++ b/spacecmd/src/lib/configchannel.py
@@ -410,9 +410,10 @@ options:
-t SYMLINK_TARGET
-f local path to file contents
- Note re binary/base64: Some text files, notably those containing blank lines
- and those containing ASCII escape characters (or other charaters not allowed
- or processed correctly as text via the XMLRPC API) need to be sent as binary
+ 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):
commit f1e2340000956be40446f7dc37e6d68ae4ab6963
Author: Steven Hardy <shardy(a)redhat.com>
Date: Mon Apr 9 22:02:58 2012 +0100
spacecmd : enhancement Add system_listconfigfiles
Adds new option which lists configfiles for a given system, including centrally
managed, system sandbox and locally managed files
diff --git a/spacecmd/src/lib/system.py b/spacecmd/src/lib/system.py
index 08c3deb..7f14346 100644
--- a/spacecmd/src/lib/system.py
+++ b/spacecmd/src/lib/system.py
@@ -1004,6 +1004,119 @@ def do_system_listconfigchannels(self, args):
####################
+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], min=10)
+ max_type = max_length(["file", "directory",
"symlink"], min=10)
+ max_label = max_length([f['channel_label'] for f in filelist], min=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):
+ options = [ Option('-s', '--sandbox', action='store_true'),
+ Option('-l', '--local', action='store_true'),
+ Option('-c', '--central', action='store_true'),
+ Option('-q', '--quiet', action='store_true') ]
+
+ (args, options) = parse_arguments(args, options)
+
+ 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 len(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: return
+
+ 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:
+ 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_addconfigchannels(self):
print 'system_addconfigchannels: Add config channels to a system'
print '''usage: system_addconfigchannels <SYSTEMS> <CHANNEL
...> [options]