summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/server/plugins/connectors/properties.txt28
-rw-r--r--doc/server/plugins/structures/bundler/index.txt44
-rw-r--r--man/bcfg2-repo-validate.833
-rw-r--r--man/bcfg2-reports.86
-rw-r--r--man/bcfg2-server.810
-rw-r--r--man/bcfg2.142
-rw-r--r--man/bcfg2.conf.510
-rw-r--r--schemas/bundle.xsd16
-rw-r--r--schemas/info.xsd3
-rw-r--r--src/lib/Options.py2
-rw-r--r--src/lib/Server/Admin/Init.py4
-rw-r--r--src/lib/Server/Plugin.py34
-rw-r--r--src/lib/Server/Plugins/Properties.py10
-rwxr-xr-xsrc/sbin/bcfg236
-rwxr-xr-xsrc/sbin/bcfg2-admin8
-rwxr-xr-xsrc/sbin/bcfg2-build-reports142
-rwxr-xr-xsrc/sbin/bcfg2-info115
-rwxr-xr-xsrc/sbin/bcfg2-ping-sweep2
-rwxr-xr-xsrc/sbin/bcfg2-repo-validate188
-rwxr-xr-xsrc/sbin/bcfg2-reports38
20 files changed, 416 insertions, 355 deletions
diff --git a/doc/server/plugins/connectors/properties.txt b/doc/server/plugins/connectors/properties.txt
index ae8bf0caa..9fb0dba87 100644
--- a/doc/server/plugins/connectors/properties.txt
+++ b/doc/server/plugins/connectors/properties.txt
@@ -24,17 +24,37 @@ Properties adds a new dictionary to client metadata instances that maps
property file names to PropertyFile instances. PropertyFile instances
contain parsed XML data as the "data" attribute.
+The XML data in a property file is arbitrary, but a matching ``.xsd``
+file can be created to assign a schema to a property file, which will
+be checked when running ``bcfg2-repo-validate``. For instance, given::
+
+ Properties/dns-config.xml
+ Properties/dns-config.xsd
+
+``dns-config.xml`` will be validated against ``dns-config.xsd``.
+
Usage
=====
Specific property files can be referred to in
-templates as metadata.Properties[<filename>]. The
+templates as ``metadata.Properties[<filename>]``. The
data attribute is an LXML element object. (Documented
`here <http://codespeak.net/lxml/tutorial.html#the-element-class>`_)
-Currently, no access methods are defined for this data, but as we
-formulate common use cases, we will add them to the !PropertyFile class
-as methods. This will simplify templates.
+Currently, only one access method is defined for this data, ``Match``.
+``Match`` parses the Group and Client tags in the file and returns a
+list of elements that apply to the client described by a set of
+metadata. (See :ref:`server-plugins-structures-bundler-index` for
+more details on how Group and Client tags are parsed.) For instance::
+
+ {% python
+ ntp_servers = [el.text
+ for el in metadata.Properties['ntp.xml'].Match(metadata):
+ if el.tag == "Server"]
+ %}
+
+As we formulate more common use cases, we will add them to the
+!PropertyFile class as methods. This will simplify templates.
Accessing Properties contents from TGenshi
==========================================
diff --git a/doc/server/plugins/structures/bundler/index.txt b/doc/server/plugins/structures/bundler/index.txt
index 0d0054a2c..c84d6cdbe 100644
--- a/doc/server/plugins/structures/bundler/index.txt
+++ b/doc/server/plugins/structures/bundler/index.txt
@@ -16,14 +16,15 @@ entries. For example, a bundle could say that the configuration file
describe the particular version of ``/etc/passwd`` that a given client
will receive.
-Groups can be used inside of bundles to differentiate which entries
-particular clients will recieve; this is useful for the case where
-entries are named differently across systems; for example, one linux
-distro may have a package called openssh while another uses the name
-ssh. Configuration entries nested inside of Group elements only apply
-to clients who are a member of those groups; multiple nested groups must
-all apply. Also, groups may be negated; entries included in such groups
-will only apply to clients who are not a member of said group.
+Group and Client tags can be used inside of bundles to differentiate
+which entries particular clients will recieve; this is useful for the
+case where entries are named differently across systems; for example,
+one linux distro may have a package called openssh while another uses
+the name ssh. Configuration entries nested inside of Group elements
+only apply to clients who are a member of those groups; multiple
+nested groups must all apply. Also, groups may be negated; entries
+included in such groups will only apply to clients who are not a
+member of said group. The same applies to Client elements.
The following is an annotated copy of a bundle:
@@ -54,20 +55,25 @@ The following is an annotated copy of a bundle:
<Package name='ssh'/>
<Service name='ssh'/>
</Group>
+ <Client name='trust.example.com'>
+ <Path name='/etc/ssh/shosts.equiv'/>
+ </Client>
</Bundle>
-In this bundle, most of the entries are common to all systems. Clients in
-group **deb** get one extra package and service, while clients in group
-**rpm** get two extra packages and an extra service. In addition, clients
-in group **fedora** *and* group **rpm** get one extra package entries,
-unless they are not in the **fc14** group, in which case, they get an
-extra package. Notice that this file doesn't describe which versions
-of these entries that clients should get, only that they should get
-them. (Admittedly, this example is slightly contrived, but demonstrates
-how group entries can be used in bundles)
+In this bundle, most of the entries are common to all systems. Clients
+in group **deb** get one extra package and service, while clients in
+group **rpm** get two extra packages and an extra service. In
+addition, clients in group **fedora** *and* group **rpm** get one
+extra package entries, unless they are not in the **fc14** group, in
+which case, they get an extra package. The client
+**trust.example.com** gets one extra file that is not distributed to
+any other clients. Notice that this file doesn't describe which
+versions of these entries that clients should get, only that they
+should get them. (Admittedly, this example is slightly contrived, but
+demonstrates how group entries can be used in bundles)
+----------------------------+-------------------------------+
-| Group | Entry |
+| Group/Hostname | Entry |
+============================+===============================+
| all | /etc/ssh/ssh_host_dsa_key |
+----------------------------+-------------------------------+
@@ -101,6 +107,8 @@ how group entries can be used in bundles)
+----------------------------+-------------------------------+
| deb | Service ssh |
+----------------------------+-------------------------------+
+| trust.example.com | /etc/ssh/shosts.equiv |
++----------------------------+-------------------------------+
Genshi templates
================
diff --git a/man/bcfg2-repo-validate.8 b/man/bcfg2-repo-validate.8
index 81ba61099..1bf74a206 100644
--- a/man/bcfg2-repo-validate.8
+++ b/man/bcfg2-repo-validate.8
@@ -3,7 +3,7 @@
bcfg2-repo-validate \- Check Bcfg2 repository data against data schemas
.SH SYNOPSIS
.B bcfg2-repo-validate
-.I [-v]
+.I [OPTIONS]
.SH DESCRIPTION
.PP
.B bcfg2-repo-validate
@@ -12,24 +12,37 @@ finding typos or malformed data.
.SH OPTIONS
.TP
-.BR "-Q <repository path>"
-Server repository path
+.BR "-v"
+Be verbose about checks that have succeeded. This also enables
+checking for missing bundles.
.TP
-.BR "-C <conffile>"
+.BR "-C"
Specify path to bcfg2.conf (default /etc/bcfg2.conf)
.TP
-.BR "--stdin"
-Operate on a list of files supplied on stdin
+.BR "-Q"
+Specify path to Bcfg2 repository (default /var/lib/bcfg2)
.TP
-.BR "-v"
-Enable verbose output
+.BR "--schema"
+Specify path to Bcfg2 XML Schemas (default /usr/share/bcfg2/schema)
.TP
-.BR "--schema=<schema path>"
-Path to XML Schema files
+.BR "--stdin"
+Rather than validating all XML files in the Bcfg2 specification, only
+validate a list of files supplied on stdin. This makes a few
+assumptions:
+
+Files included using XInclude will only be validated if they are
+included on stdin; XIncludes will not be followed.
+
+Property files will only be validated if both the property file itself
+and its matching schema are included on stdin.
+
+.TP
+.BR "--require-schema"
+Require property files to have matching schema files
.RE
.SH "SEE ALSO"
diff --git a/man/bcfg2-reports.8 b/man/bcfg2-reports.8
index 7597818cd..bc4c9344b 100644
--- a/man/bcfg2-reports.8
+++ b/man/bcfg2-reports.8
@@ -3,7 +3,7 @@
bcfg2-reports \- Query reporting system for client status
.SH SYNOPSIS
.B bcfg2-reports
-.I [-v]
+.I [-v]
.SH DESCRIPTION
.PP
\fBbcfg2-reports\fR allows you to retrieve data from the database about
@@ -53,14 +53,14 @@ Toggles expired/unexpired state of NAME. NAME is the name of the entry.
Shows only hosts whose current interaction has bad entries in of KIND
kind and NAME name; if a single argument ARG1 is given, then KIND,NAME
pairs will be read from a file of name ARG1. KIND is the type of entry
-(Package, Path, Service, etc). NAME is the name of the entry.
+(Package, Path, Service, etc). NAME is the name of the entry.
.RE
.B "\-\-extraentry=KIND,NAME"
.RS
Shows only hosts whose current interaction has extra entries in of KIND
kind and NAME name; if a single argument ARG1 is given, then KIND,NAME
pairs will be read from a file of name ARG1. KIND is the type of entry
-(Package, Path, Service, etc). NAME is the name of the entry.
+(Package, Path, Service, etc). NAME is the name of the entry.
.RE
.B "\-\-fields=ARG1,ARG2,..."
.RS
diff --git a/man/bcfg2-server.8 b/man/bcfg2-server.8
index 9f99ee8b7..a6bffc1fa 100644
--- a/man/bcfg2-server.8
+++ b/man/bcfg2-server.8
@@ -7,11 +7,11 @@ bcfg2-server \- Server for client configuration specifications
.SH DESCRIPTION
.PP
.B bcfg2-server
-This daemon serves configurations to clients based on the data in its
-repository.
+This daemon serves configurations to clients based on the data in its
+repository.
.SH OPTIONS
.PP
-.B \-d
+.B \-d
.RS
Run bcfg2 in debug mode.
.RE
@@ -19,11 +19,11 @@ Run bcfg2 in debug mode.
.RS
Run bcfg2 in verbose mode.
.RE
-.B "\-C <ConfigFile Path>"
+.B "\-C <ConfigFile Path>"
.RS
Use an alternative path for bcfg2.conf. The default is /etc/bcfg2.conf
.RE
-.B \-D
+.B \-D
.RS
Daemonize, placing the program pid in the specified pidfile.
.RE
diff --git a/man/bcfg2.1 b/man/bcfg2.1
index 14398fdab..938d41dfe 100644
--- a/man/bcfg2.1
+++ b/man/bcfg2.1
@@ -31,15 +31,15 @@ Specify the encoding of Cfg files.
.TP
.BR "\-I"
-Run bcfg2 in interactive mode. The user will be prompted before each
+Run bcfg2 in interactive mode. The user will be prompted before each
change.
.TP
-.BR "\-O"
+.BR "\-O"
Omit lock check.
-.TP
-.BR "\-P"
+.TP
+.BR "\-P"
Run bcfg2 in paranoid mode. Diffs will be logged for
configuration files marked as paranoid by the Bcfg2 server.
@@ -62,24 +62,24 @@ Run bcfg2 against one or multiple bundles in the configuration.
Cache a copy of the configuration in cachefile.
.TP
-.BR "\-d"
+.BR "\-d"
Run bcfg2 in debug mode.
-.TP
-.BR "\-e"
+.TP
+.BR "\-e"
When in verbose mode, display extra entry information (temporary until
verbosity rework).
.TP
-.BR "\-f <specification path>"
+.BR "\-f <specification path>"
Configure from a file rather than querying the server.
-.TP
-.BR "\-h"
+.TP
+.BR "\-h"
Print Usage information.
-.TP
-.BR "\-k"
+.TP
+.BR "\-k"
Run in bulletproof mode. This currently only affects behavior in the
debian toolset; it calls apt\-get update and clean and
dpkg \-\-configure \-\-pending.
@@ -97,14 +97,14 @@ administrators and much configuration variety.
.TP
.BR "\-n"
Run bcfg2 in dry\-run mode. No changes will be made to the
-system.
+system.
.TP
.BR "\-o <LogFile Path>"
Writes a log to the specified path.
.TP
-.BR "\-p <profile>"
+.BR "\-p <profile>"
Assert a profile for the current client.
.TP
@@ -112,16 +112,16 @@ Assert a profile for the current client.
Run bcfg2 in quick mode. Package checksum verification won't be
performed. This mode relaxes the constraints of correctness, and thus
should only be used in safe conditions.
-
+
.TP
.BR "\-Q"
-Run bcfg2 in "bundle quick" mode, where only entries in a bundle are
-or installed. This runs much faster than -q, but doesn't provide
+Run bcfg2 in "bundle quick" mode, where only entries in a bundle are
+or installed. This runs much faster than -q, but doesn't provide
statistics to the server at all. In order for this option to work, the
--b option must also be provided. This option is incompatible with -r.
+-b option must also be provided. This option is incompatible with -r.
.TP
-.BR "\-r <mode>"
+.BR "\-r <mode>"
Cause bcfg2 to remove extra configuration elements it detects. Mode is
one of all, Services, or Packages. All removes all entries. Likewise,
Services and Packages remove only the extra configuration elements of
@@ -139,11 +139,11 @@ modify services.
Specifiy the path to the SSL key.
.TP
-.BR "\-u <user>"
+.BR "\-u <user>"
Attempt to authenticate as 'user'.
.TP
-.BR "\-x <password>"
+.BR "\-x <password>"
Use 'password' for client communication.
.TP
diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5
index c32ccde16..f2e47b7ac 100644
--- a/man/bcfg2.conf.5
+++ b/man/bcfg2.conf.5
@@ -64,7 +64,7 @@ reporting system, so they can be centrally observed.
\(bu
.B BB
-The BB plugin maps users to machines and metadata to machines.
+The BB plugin maps users to machines and metadata to machines.
(experimental)
\(bu
@@ -339,20 +339,20 @@ settings used for client-server communication.
.B ca
The path to a file containing the CA certificate. This file is
required on the server, and optional on clients. However, if the
-cacert is not present on clients, the server cannot be verified.
+cacert is not present on clients, the server cannot be verified.
.TP
.B certificate
The path to a file containing a PEM formatted certificate which
signs the key with the ca certificate. This setting is required on
the server in all cases, and required on clients if using client
-certificates.
+certificates.
.TP
.B key
Specifies the path to a file containing the SSL Key. This is required
on the server in all cases, and required on clients if using client
-certificates.
+certificates.
.TP
.B password
@@ -379,7 +379,7 @@ of the configuration file.
.TP
.B path
-Custom path for backups created in paranoid mode. The default is in
+Custom path for backups created in paranoid mode. The default is in
/var/cache/bcfg2.
.TP
diff --git a/schemas/bundle.xsd b/schemas/bundle.xsd
index 1ea44c991..b226e1078 100644
--- a/schemas/bundle.xsd
+++ b/schemas/bundle.xsd
@@ -93,6 +93,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
+ <xsd:element name='Client' type='GroupType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Elements within Client tags only apply to the named client
+ (or vice-versa; see #element_negate below)
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
<xsd:element ref="py:def"/>
<xsd:element ref="py:match"/>
<xsd:element ref="py:choose"/>
@@ -207,6 +215,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
+ <xsd:element name='Client' type='GroupType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Elements within Client tags only apply to the named client
+ (or vice-versa; see #element_negate below)
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
<xsd:element ref="py:def"/>
<xsd:element ref="py:match"/>
<xsd:element ref="py:choose"/>
diff --git a/schemas/info.xsd b/schemas/info.xsd
index 983513d66..96ccbe56c 100644
--- a/schemas/info.xsd
+++ b/schemas/info.xsd
@@ -22,6 +22,8 @@
<xsd:element name='Info' type='InfoType'/>
<xsd:element name='Group' type='GroupType' minOccurs='0'
maxOccurs='unbounded'/>
+ <xsd:element name='Client' type='GroupType' minOccurs='0'
+ maxOccurs='unbounded'/>
</xsd:choice>
<xsd:attribute type='xsd:string' name='name' use='required'/>
<xsd:attribute type='xsd:boolean' name='negate' />
@@ -31,6 +33,7 @@
<xsd:complexType>
<xsd:choice minOccurs='0' maxOccurs='unbounded'>
<xsd:element name='Group' type='GroupType'/>
+ <xsd:element name='Client' type='GroupType'/>
<xsd:element name='Info' type='InfoType'/>
</xsd:choice>
</xsd:complexType>
diff --git a/src/lib/Options.py b/src/lib/Options.py
index f64b491d5..1973e7091 100644
--- a/src/lib/Options.py
+++ b/src/lib/Options.py
@@ -207,6 +207,8 @@ SCHEMA_PATH = Option('Path to XML Schema files', cmd='--schema',
odesc='<schema path>',
default="%s/share/bcfg2/schemas" % DEFAULT_INSTALL_PREFIX,
long_arg=True)
+REQUIRE_SCHEMA = Option("Require property files to have matching schema files",
+ cmd="--require-schema", default=False, long_arg=True)
# Metadata options
MDATA_OWNER = Option('Default Path owner',
diff --git a/src/lib/Server/Admin/Init.py b/src/lib/Server/Admin/Init.py
index 1c12aee24..9771fd10b 100644
--- a/src/lib/Server/Admin/Init.py
+++ b/src/lib/Server/Admin/Init.py
@@ -138,7 +138,7 @@ def create_key(hostname, keypath, certpath, country, state, location):
keypath,
certpath))
subprocess.call((ccstr), shell=True)
- os.chmod(keypath, stat.S_IRUSR | stat.S_IWUSR) # 0600
+ os.chmod(keypath, stat.S_IRUSR|stat.S_IWUSR) # 0600
def create_conf(confpath, confdata):
@@ -156,7 +156,7 @@ def create_conf(confpath, confdata):
return
try:
open(confpath, "w").write(confdata)
- os.chmod(keypath, stat.S_IRUSR | stat.S_IWUSR) # 0600
+ os.chmod(keypath, stat.S_IRUSR|stat.S_IWUSR) # 0600
except Exception, e:
print("Error %s occured while trying to write configuration "
"file to '%s'.\n" %
diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py
index ac9479d44..69b38c4ff 100644
--- a/src/lib/Server/Plugin.py
+++ b/src/lib/Server/Plugin.py
@@ -457,17 +457,29 @@ class StructFile(XMLFileBacked):
work = {lambda x: True: xdata.getchildren()}
while work:
(predicate, worklist) = work.popitem()
- self.fragments[predicate] = [item for item in worklist if item.tag != 'Group'
- and not isinstance(item, lxml.etree._Comment)]
- for group in [item for item in worklist if item.tag == 'Group']:
- # if only python had forceable early-binding
- if group.get('negate', 'false') in ['true', 'True']:
- cmd = "lambda x:'%s' not in x.groups and predicate(x)"
- else:
- cmd = "lambda x:'%s' in x.groups and predicate(x)"
-
- newpred = eval(cmd % (group.get('name')), {'predicate': predicate})
- work[newpred] = group.getchildren()
+ self.fragments[predicate] = \
+ [item for item in worklist
+ if (item.tag != 'Group' and
+ item.tag != 'Client' and
+ not isinstance(item,
+ lxml.etree._Comment))]
+ for item in worklist:
+ cmd = None
+ if item.tag == 'Group':
+ if item.get('negate', 'false').lower() == 'true':
+ cmd = "lambda x:'%s' not in x.groups and predicate(x)"
+ else:
+ cmd = "lambda x:'%s' in x.groups and predicate(x)"
+ elif item.tag == 'Client':
+ if item.get('negate', 'false').lower() == 'true':
+ cmd = "lambda x:x.hostname != '%s' and predicate(x)"
+ else:
+ cmd = "lambda x:x.hostname == '%s' and predicate(x)"
+ # else, ignore item
+ if cmd is not None:
+ newpred = eval(cmd % item.get('name'),
+ {'predicate':predicate})
+ work[newpred] = item.getchildren()
def Match(self, metadata):
"""Return matching fragments of independent."""
diff --git a/src/lib/Server/Plugins/Properties.py b/src/lib/Server/Plugins/Properties.py
index 2888ef1d1..c5d2acc44 100644
--- a/src/lib/Server/Plugins/Properties.py
+++ b/src/lib/Server/Plugins/Properties.py
@@ -4,15 +4,9 @@ import lxml.etree
import Bcfg2.Server.Plugin
-class PropertyFile(Bcfg2.Server.Plugin.XMLFileBacked):
+class PropertyFile(Bcfg2.Server.Plugin.StructFile):
"""Class for properties files."""
-
- def Index(self):
- """Build data into an xml object."""
- try:
- self.data = lxml.etree.XML(self.data)
- except lxml.etree.XMLSyntaxError:
- Bcfg2.Server.Plugin.logger.error("Failed to parse %s" % self.name)
+ pass
class PropDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2
index fe962211c..9bc50fe65 100755
--- a/src/sbin/bcfg2
+++ b/src/sbin/bcfg2
@@ -3,26 +3,24 @@
"""Bcfg2 Client"""
__revision__ = '$Revision$'
-import fcntl
import logging
import os
import signal
-import stat
import sys
import tempfile
import time
import xmlrpclib
-
+import fcntl
+import Bcfg2.Options
import Bcfg2.Client.XML
import Bcfg2.Client.Frame
import Bcfg2.Client.Tools
-import Bcfg2.Options
-import Bcfg2.Logger
+
import Bcfg2.Proxy
+import Bcfg2.Logger
logger = logging.getLogger('bcfg2')
-
def cb_sigint_handler(signum, frame):
"""Exit upon CTRL-C."""
os._exit(1)
@@ -107,11 +105,9 @@ class Client:
self.logger.info(Bcfg2.Client.Tools.drivers)
raise SystemExit(0)
if self.setup['remove'] and 'services' in self.setup['remove']:
- self.logger.error("Service removal is nonsensical, "
- "disable services to get former behavior")
+ self.logger.error("Service removal is nonsensical, disable services to get former behavior")
if self.setup['remove'] not in [False, 'all', 'services', 'packages']:
- self.logger.error("Got unknown argument %s for -r" %
- (self.setup['remove']))
+ self.logger.error("Got unknown argument %s for -r" % (self.setup['remove']))
if (self.setup["file"] != False) and (self.setup["cache"] != False):
print("cannot use -f and -c together")
raise SystemExit(1)
@@ -134,17 +130,13 @@ class Client:
script.write(probe.text)
script.close()
os.close(scripthandle)
- os.chmod(script.name, stat.S_IRUSR | stat.S_IWUSR |
- stat.S_IXUSR | stat.S_IRGRP |
- stat.S_IXGRP | stat.S_IROTH |
- stat.S_IXOTH) # 0755
+ os.chmod(script.name, 0755)
ret.text = os.popen(script.name).read().strip()
self.logger.info("Probe %s has result:\n%s" % (name, ret.text))
finally:
os.unlink(script.name)
except:
- self.logger.error("Failed to execute probe: %s" % (name),
- exc_info=1)
+ self.logger.error("Failed to execute probe: %s" % (name), exc_info=1)
raise SystemExit(1)
return ret
@@ -177,10 +169,10 @@ class Client:
proxy = Bcfg2.Proxy.ComponentProxy(self.setup['server'],
self.setup['user'],
self.setup['password'],
- key=self.setup['key'],
- cert=self.setup['certificate'],
- ca=self.setup['ca'],
- allowedServerCNs=self.setup['serverCN'])
+ key = self.setup['key'],
+ cert = self.setup['certificate'],
+ ca = self.setup['ca'],
+ allowedServerCNs = self.setup['serverCN'])
if self.setup['profile']:
try:
@@ -218,9 +210,7 @@ class Client:
if len(probes.findall(".//probe")) > 0:
try:
# upload probe responses
- proxy.RecvProbeData(Bcfg2.Client.XML.tostring(probedata,
- encoding='UTF-8',
- xml_declaration=True))
+ proxy.RecvProbeData(Bcfg2.Client.XML.tostring(probedata, encoding='UTF-8', xml_declaration=True))
except:
self.logger.error("Failed to upload probe data", exc_info=1)
raise SystemExit(1)
diff --git a/src/sbin/bcfg2-admin b/src/sbin/bcfg2-admin
index f8b82d201..2c9a43859 100755
--- a/src/sbin/bcfg2-admin
+++ b/src/sbin/bcfg2-admin
@@ -12,7 +12,6 @@ log = logging.getLogger('bcfg2-admin')
import Bcfg2.Server.Admin
-
def mode_import(modename):
"""Load Bcfg2.Server.Admin.<mode>."""
modname = modename.capitalize()
@@ -20,12 +19,10 @@ def mode_import(modename):
(modname)).Server.Admin, modname)
return getattr(mod, modname)
-
def get_modes():
"""Get all available modes, except for the base mode."""
return [x.lower() for x in Bcfg2.Server.Admin.__all__ if x != 'mode']
-
def create_description():
"""Create the description string from the list of modes."""
modes = get_modes()
@@ -39,7 +36,6 @@ def create_description():
continue
return description.getvalue()
-
def main():
Bcfg2.Logger.setup_logging('bcfg2-admin', to_console=True, level=40)
usage = "Usage: %prog [options] MODE [args]"
@@ -60,7 +56,7 @@ def main():
else:
# Print short help for all modes
parser.print_help()
- print(create_description())
+ print create_description()
raise SystemExit(0)
if args[0] in get_modes():
@@ -77,7 +73,7 @@ def main():
else:
log.error("Unknown mode %s" % args[0])
parser.print_help()
- print(create_description())
+ print create_description()
raise SystemExit(1)
if __name__ == '__main__':
diff --git a/src/sbin/bcfg2-build-reports b/src/sbin/bcfg2-build-reports
index 6b3e24e84..231f52105 100755
--- a/src/sbin/bcfg2-build-reports
+++ b/src/sbin/bcfg2-build-reports
@@ -14,9 +14,7 @@ import socket
import sys
from time import asctime, strptime
from ConfigParser import ConfigParser, NoSectionError, NoOptionError
-from lxml.etree import XML, XSLT, parse, Element, ElementTree, \
- SubElement, tostring, XMLSyntaxError
-
+from lxml.etree import XML, XSLT, parse, Element, ElementTree, SubElement, tostring, XMLSyntaxError
def generatereport(rspec, nrpt):
"""
@@ -26,12 +24,12 @@ def generatereport(rspec, nrpt):
reportspec = copy.deepcopy(rspec)
nodereprt = copy.deepcopy(nrpt)
- reportgood = reportspec.get("good", default='Y')
- reportmodified = reportspec.get("modified", default='Y')
+ reportgood = reportspec.get("good", default = 'Y')
+ reportmodified = reportspec.get("modified", default = 'Y')
current_date = asctime()[:10]
"""Build regex of all the nodes we are reporting about."""
- pattern = re.compile('|'.join([item.get("name") for item in reportspec.findall('Machine')]))
+ pattern = re.compile( '|'.join([item.get("name") for item in reportspec.findall('Machine')]))
for node in nodereprt.findall('Node'):
if not (node.findall("Statistics") and pattern.match(node.get('name'))):
@@ -42,27 +40,25 @@ def generatereport(rspec, nrpt):
# Reduce to most recent Statistics entry.
statisticslist = node.findall('Statistics')
# This line actually sorts from most recent to oldest.
- statisticslist.sort(lambda y, x: cmp(strptime(x.get("time")),
- strptime(y.get("time"))))
+ statisticslist.sort(lambda y, x: cmp(strptime(x.get("time")), strptime(y.get("time"))))
stats = statisticslist[0]
-
+
[node.remove(item) for item in node.findall('Statistics')]
-
+
# Add a good tag if node is good and we wnat to report such.
if reportgood == 'Y' and stats.get('state') == 'clean':
- SubElement(stats, "Good")
+ SubElement(stats,"Good")
[stats.remove(item) for item in stats.findall("Bad") + stats.findall("Modified") if \
item.getchildren() == []]
[stats.remove(item) for item in stats.findall("Modified") if reportmodified == 'N']
-
+
# Test for staleness -if stale add Stale tag.
if stats.get("time").find(current_date) == -1:
- SubElement(stats, "Stale")
+ SubElement(stats,"Stale")
node.append(stats)
return nodereprt
-
def mail(mailbody, confi):
"""mail mails a previously generated report."""
@@ -76,8 +72,7 @@ def mail(mailbody, confi):
pipe.write(mailbody)
exitcode = pipe.close()
if exitcode:
- print("Exit code: %s" % exitcode)
-
+ print "Exit code: %s" % exitcode
def rss(reportxml, delivery, report):
"""rss appends a new report to the specified rss file
@@ -103,7 +98,7 @@ def rss(reportxml, delivery, report):
chantitle = SubElement(channel, "title")
chantitle.text = report.attrib['name']
chanlink = SubElement(channel, "link")
-
+
# This can later link to WWW report if one gets published
# simultaneously?
chanlink.text = "http://www.mcs.anl.gov/cobalt/bcfg2"
@@ -120,19 +115,17 @@ def rss(reportxml, delivery, report):
fil.write(tree)
fil.close()
-
def www(reportxml, delivery):
"""www outputs report to."""
# This can later link to WWW report if one gets published
- # simultaneously?
+ # simultaneously?
for destination in delivery.findall('Destination'):
fil = open(destination.attrib['address'], 'w')
fil.write(reportxml)
fil.close()
-
def fileout(reportxml, delivery):
"""Outputs to plain text file."""
for destination in delivery.findall('Destination'):
@@ -141,29 +134,25 @@ def fileout(reportxml, delivery):
fil.write(reportxml)
fil.close()
-
def pretty_print(element, level=0):
"""Produce a pretty-printed text representation of element."""
if element.text:
- fmt = "%s<%%s %%s>%%s</%%s>" % (level * " ")
- data = (element.tag, (" ".join(["%s='%s'" % keyval for keyval in
- list(element.attrib.items())])),
+ fmt = "%s<%%s %%s>%%s</%%s>" % (level*" ")
+ data = (element.tag, (" ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()])),
element.text, element.tag)
if element._children:
- fmt = "%s<%%s %%s>\n" % (level * " ",) + (len(element._children) * "%s") + "%s</%%s>\n" % (level * " ")
- data = (element.tag, ) + (" ".join(["%s='%s'" % keyval for keyval in
- list(element.attrib.items())]),)
- data += tuple([pretty_print(entry, level + 2) for entry in element._children]) + (element.tag, )
+ fmt = "%s<%%s %%s>\n" % (level*" ",) + (len(element._children) * "%s") + "%s</%%s>\n" % (level*" ")
+ data = (element.tag, ) + (" ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()]),)
+ data += tuple([pretty_print(entry, level+2) for entry in element._children]) + (element.tag, )
else:
fmt = "%s<%%s %%s/>\n" % (level * " ")
- data = (element.tag, " ".join(["%s='%s'" % keyval for keyval in
- list(element.attrib.items())]))
+ data = (element.tag, " ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()]))
return fmt % data
if __name__ == '__main__':
- ping = True
- all = False
+ ping=True
+ all=False
if '-C' in sys.argv:
cfpath = sys.argv[sys.argv.index('-C') + 1]
else:
@@ -182,24 +171,17 @@ if __name__ == '__main__':
#websrcspath = "/usr/share/bcfg2/web-rprt-srcs/"
try:
- opts, args = getopt.getopt(sys.argv[1:],
- "C:hAc:Ns:",
- ["help", "all", "config=", "no-ping",
- "stats="])
+ opts, args = getopt.getopt(sys.argv[1:], "C:hAc:Ns:", ["help", "all", "config=","no-ping", "stats="])
except getopt.GetoptError, mesg:
# Print help information and exit:
- print("%s\nUsage:\n"
- "bcfg2-build-reports [-h] [-A (include ALL clients)] "
- "[-c <configuration-file>] [-s <statistics-file>] "
- "[-N (do not ping clients)]" % (mesg))
- raise SystemExit(2)
+ print "%s\nUsage:\nbcfg2-build-reports [-h][-A (include ALL clients)] [-c <configuration-file>] [-s <statistics-file>][-N (do not ping clients)]" % (mesg)
+ raise SystemExit, 2
for o, a in opts:
if o in ("-h", "--help"):
- print("Usage:\nbcfg2-build-reports [-h] [-c <configuration-file>] "
- "[-s <statistics-file>]")
+ print "Usage:\nbcfg2-build-reports [-h] [-c <configuration-file>] [-s <statistics-file>]"
raise SystemExit
if o in ("-A", "--all"):
- all = True
+ all=True
if o in ("-c", "--config"):
configpath = a
if o in ("-N", "--no-ping"):
@@ -207,96 +189,94 @@ if __name__ == '__main__':
if o in ("-s", "--stats"):
statpath = a
+
# See if hostinfo.xml exists, and is less than 23.5 hours old
#try:
#hostinstat = os.stat(hostinfopath)
#if (time() - hostinstat[9])/(60*60) > 23.5:
if ping:
- os.system('bcfg2-ping-sweep -C %s' % cfpath) # bcfg2-ping-sweep needs to be in path
+ os.system('bcfg2-ping-sweep -C %s' % cfpath) # bcfg2-ping-sweep needs to be in path
#except OSError:
# os.system('GenerateHostInfo')#Generate HostInfo needs to be in path
+
"""Reads data & config files."""
try:
statsdata = XML(open(statpath).read())
except (IOError, XMLSyntaxError):
- print("bcfg2-build-reports: Failed to parse %s" % (statpath))
- raise SystemExit(1)
+ print("bcfg2-build-reports: Failed to parse %s"%(statpath))
+ raise SystemExit, 1
try:
configdata = XML(open(configpath).read())
except (IOError, XMLSyntaxError):
- print("bcfg2-build-reports: Failed to parse %s" % (configpath))
- raise SystemExit(1)
+ print("bcfg2-build-reports: Failed to parse %s"%(configpath))
+ raise SystemExit, 1
try:
clientsdata = XML(open(clientsdatapath).read())
except (IOError, XMLSyntaxError):
- print("bcfg2-build-reports: Failed to parse %s" % (clientsdatapath))
- raise SystemExit(1)
+ print("bcfg2-build-reports: Failed to parse %s"%(clientsdatapath))
+ raise SystemExit, 1
# Merge data from three sources.
- nodereport = Element("Report", attrib={"time": asctime()})
+ nodereport = Element("Report", attrib={"time" : asctime()})
# Should all of the other info in Metadata be appended?
# What about all of the package stuff for other types of reports?
for client in clientsdata.findall("Client"):
- nodel = Element("Node", attrib={"name": client.get("name")})
+ nodel = Element("Node", attrib={"name" : client.get("name")})
nodel.append(client)
for nod in statsdata.findall("Node"):
if client.get('name').find(nod.get('name')) == 0:
for statel in nod.findall("Statistics"):
nodel.append(statel)
nodereport.append(nodel)
-
+
if all:
for nod in statsdata.findall("Node"):
for client in clientsdata.findall("Client"):
if client.get('name').find(nod.get('name')) == 0:
break
else:
- nodel = Element("Node", attrib={"name": nod.get("name")})
- client = Element("Client", attrib={"name": nod.get("name"),
- "profile": "default"})
+ nodel = Element("Node", attrib={"name" : nod.get("name")})
+ client = Element("Client", attrib={"name" : nod.get("name"), "profile" : "default"})
nodel.append(client)
for statel in nod.findall("Statistics"):
nodel.append(statel)
nodereport.append(nodel)
-
+
+
for reprt in configdata.findall('Report'):
nodereport.set("name", reprt.get("name", default="BCFG Report"))
if reprt.get('refresh-time') != None:
- nodereport.set("refresh-time", reprt.get("refresh-time",
- default="600"))
+ nodereport.set("refresh-time", reprt.get("refresh-time", default="600"))
procnodereport = generatereport(reprt, nodereport)
for deliv in reprt.findall('Delivery'):
# Is a deepcopy of procnodereport necessary?
-
+
delivtype = deliv.get('type', default='nodes-digest')
deliverymechanism = deliv.get('mechanism', default='www')
# Apply XSLT, different ones based on report type, and options
- if deliverymechanism == 'null-operator': # Special Cases
- fileout(tostring(ElementTree(procnodereport).getroot(),
- encoding='UTF-8',
- xml_declaration=True),
- deliv)
+ if deliverymechanism == 'null-operator': # Special Cases
+ fileout(tostring(ElementTree(procnodereport).getroot(), encoding='UTF-8', xml_declaration=True), deliv)
break
transform = delivtype + '-' + deliverymechanism + '.xsl'
- try: # Make sure valid stylesheet is selected.
+ try: # Make sure valid stylesheet is selected.
os.stat(transformpath + transform)
except:
print("bcfg2-build-reports: Invalid report type or delivery mechanism.\n Can't find: "\
+ transformpath + transform)
- raise SystemExit(1)
+ raise SystemExit, 1
- try: # Try to parse stylesheet.
+ try: # Try to parse stylesheet.
stylesheet = XSLT(parse(transformpath + transform))
except:
print("bcfg2-build-reports: invalid XSLT transform file.")
- raise SystemExit(1)
-
+ raise SystemExit, 1
+
if deliverymechanism == 'mail':
if delivtype == 'nodes-individual':
reportdata = copy.deepcopy(procnodereport)
@@ -305,37 +285,35 @@ if __name__ == '__main__':
reportdata.append(noden)
result = stylesheet.apply(ElementTree(reportdata))
outputstring = stylesheet.tostring(result)
-
+
if not outputstring == None:
toastring = ''
for desti in deliv.findall("Destination"):
toastring = "%s%s " % \
(toastring, desti.get('address'))
# Prepend To: and From:
- outputstring = "To: %s\nFrom: root@%s\n%s" % \
+ outputstring = "To: %s\nFrom: root@%s\n%s"% \
(toastring, socket.getfqdn(), outputstring)
- mail(outputstring, c) # call function to send
-
+ mail(outputstring, c) #call function to send
+
else:
reportdata = copy.deepcopy(procnodereport)
result = stylesheet.apply(ElementTree(reportdata))
outputstring = stylesheet.tostring(result)
-
+
if not outputstring == None:
toastring = ''
for desti in deliv.findall("Destination"):
toastring = "%s%s " % \
(toastring, desti.get('address'))
# Prepend To: and From:
- outputstring = "To: %s\nFrom: root@%s\n%s" % \
+ outputstring = "To: %s\nFrom: root@%s\n%s"% \
(toastring, socket.getfqdn(), outputstring)
- mail(outputstring, c) # call function to send
+ mail(outputstring, c) #call function to send
else:
- outputstring = tostring(stylesheet.apply(ElementTree(procnodereport)).getroot(),
- encoding='UTF-8',
- xml_declaration=True)
+ outputstring = tostring(stylesheet.apply(ElementTree(procnodereport)).getroot(), encoding='UTF-8', xml_declaration=True)
if deliverymechanism == 'rss':
rss(outputstring, deliv, reprt)
- else: # Must be deliverymechanism == 'www':
+ else: # Must be deliverymechanism == 'www':
www(outputstring, deliv)
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index f78b3a7f4..a6d236bc8 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -5,6 +5,7 @@ __revision__ = '$Revision$'
from code import InteractiveConsole
import cmd
+import errno
import getopt
import logging
import lxml.etree
@@ -27,42 +28,6 @@ import Bcfg2.Server.Plugin
logger = logging.getLogger('bcfg2-info')
-USAGE = """Commands
-
-build <hostname> <filename> - Build config for hostname, writing to filename
-builddir <hostname> <dirname> - Build config for hostname, writing separate files to dirname
-buildall <directory> - Build configs for all clients in directory
-buildfile <filename> <hostname> - Build config file for hostname (not written to disk)
-bundles - Print out group/bundle information
-clients - Print out client/profile information
-config - Print out the configuration of the Bcfg2 server
-debug - Shell out to native python interpreter
-event_debug - Display filesystem events as they are processed
-generators - List current versions of generators
-groups - List groups
-help - Print this list of available commands
-mappings <type*> <name*> - Print generator mappings for optional type and name
-profile <command> <args> - Profile a single bcfg2-info command
-quit - Exit the bcfg2-info command line
-showentries <hostname> <type> - Show abstract configuration entries for a given host
-showclient <client1> <client2> - Show metadata for given hosts
-update - Process pending file events
-version - Print version of this tool"""
-
-BUILDDIR_USAGE = """Usage: builddir [-f] <hostname> <output dir>
-
-Generates a config for client <hostname> and writes the
-individual configuration files out separately in a tree
-under <output dir>. The <output dir> directory must be
-rooted under /tmp unless the -f argument is provided, in
-which case it can be located anywhere.
-
-NOTE: Currently only handles file entries and writes
-all content with the default owner and permissions. These
-could be much more permissive than would be created by the
-Bcfg2 client itself."""
-
-
class mockLog(object):
def error(self, *args, **kwargs):
pass
@@ -73,22 +38,18 @@ class mockLog(object):
def debug(self, *args, **kwargs):
pass
-
class dummyError(Exception):
"""This is just a dummy."""
pass
-
class FileNotBuilt(Exception):
"""Thrown when File entry contains no content."""
def __init__(self, value):
Exception.__init__(self)
self.value = value
-
def __str__(self):
return repr(self.value)
-
def printTabular(rows):
"""Print data in tabular format."""
cmax = tuple([max([len(str(row[index])) for row in rows]) + 1 \
@@ -100,13 +61,11 @@ def printTabular(rows):
for row in rows[1:]:
print(fstring % row)
-
def displayTrace(trace, num=80, sort=('time', 'calls')):
stats = pstats.Stats(trace)
stats.sort_stats('cumulative', 'calls', 'time')
stats.print_stats(200)
-
class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
"""Main class for bcfg2-info."""
def __init__(self, repo, plgs, passwd, encoding, event_debug):
@@ -147,7 +106,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
try:
opts, _ = getopt.getopt(args.split(), 'nf:')
except:
- print("Usage: debug [-n] [-f <command list>]")
+ print "Usage: debug [-n] [-f <command list>]"
return
self.cont = False
scriptmode = False
@@ -177,7 +136,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
Exit program.
Usage: [quit|exit]
"""
- for plugin in list(self.plugins.values()):
+ for plugin in self.plugins.values():
plugin.shutdown()
os._exit(0)
@@ -186,7 +145,27 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
def do_help(self, _):
"""Print out usage info."""
- print(USAGE)
+ print 'Commands:'
+ print 'build <hostname> <filename> - Build config for hostname, writing to filename'
+ print 'builddir <hostname> <dirname> - Build config for hostname, writing separate files to dirname'
+ print 'buildall <directory> - Build configs for all clients in directory'
+ print 'buildfile <filename> <hostname> - Build config file for hostname (not written to disk)'
+ print 'bundles - Print out group/bundle information'
+ print 'clients - Print out client/profile information'
+ print 'config - Print out the configuration of the Bcfg2 server'
+ print 'debug - Shell out to native python interpreter'
+ print 'event_debug - Display filesystem events as they are processed'
+ print 'generators - List current versions of generators'
+ print 'groups - List groups'
+ print 'help - Print this list of available commands'
+ print 'mappings <type*> <name*> - Print generator mappings for optional type and name'
+ print 'profile <command> <args> - Profile a single bcfg2-info command'
+ print 'quit - Exit the bcfg2-info command line'
+ print 'showentries <hostname> <type> - Show abstract configuration entries for a given host'
+ print 'showclient <client1> <client2> - Show metadata for given hosts'
+ print 'update - Process pending file events'
+ print 'version - Print version of this tool'
+
def do_update(self, _):
"""Process pending filesystem events."""
@@ -219,7 +198,18 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
def help_builddir(self):
"""Display help for builddir command."""
- print(BUILDDIR_USAGE)
+ print('Usage: builddir [-f] <hostname> <output dir>')
+ print('')
+ print('Generates a config for client <hostname> and writes the')
+ print('individual configuration files out separately in a tree')
+ print('under <output dir>. The <output dir> directory must be')
+ print('rooted under /tmp unless the -f argument is provided, in')
+ print('which case it can be located anywhere.')
+ print('')
+ print('NOTE: Currently only handles file entries and writes')
+ print('all content with the default owner and permissions. These')
+ print('could be much more permissive than would be created by the')
+ print('Bcfg2 client itself.')
def do_builddir(self, args):
"""Build client configuration as separate files within a dir."""
@@ -248,9 +238,9 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
p = Bcfg2.Client.Tools.POSIX.POSIX(log, setup, client_config)
states = dict()
p.Inventory(states)
- p.Install(list(states.keys()), states)
+ p.Install(states.keys(), states)
else:
- print("Error: Incorrect number of parameters.")
+ print('Error: Incorrect number of parameters.')
self.help_builddir()
def do_buildall(self, args):
@@ -272,7 +262,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
try:
metadata = self.build_metadata(client)
self.Bind(entry, metadata)
- print(lxml.etree.tostring(entry, encoding="UTF-8",
+ print(lxml.etree.tostring(entry, encoding="UTF-8",
xml_declaration=True))
except:
print("Failed to build entry %s for host %s" % (fname, client))
@@ -317,6 +307,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
]
printTabular(output)
+
def do_generators(self, _):
"""Print out generator info."""
for generator in self.generators:
@@ -380,22 +371,22 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
except:
print("Client %s not defined" % client)
continue
- print("Hostname:\t", client_meta.hostname)
- print("Profile:\t", client_meta.profile)
- print("Groups:\t\t", list(client_meta.groups)[0])
+ print "Hostname:\t", client_meta.hostname
+ print "Profile:\t", client_meta.profile
+ print "Groups:\t\t", list(client_meta.groups)[0]
for grp in list(client_meta.groups)[1:]:
- print("\t\t%s" % grp)
+ print '\t\t%s' % grp
if client_meta.bundles:
- print("Bundles:\t", list(client_meta.bundles)[0])
+ print "Bundles:\t", list(client_meta.bundles)[0]
for bnd in list(client_meta.bundles)[1:]:
- print("\t\t%s" % bnd)
+ print '\t\t%s' % bnd
if client_meta.connectors:
- print("Connector data")
- print("=" * 80)
+ print "Connector data"
+ print "=" * 80
for conn in client_meta.connectors:
if getattr(client_meta, conn):
- print("%s:\t" % (conn), getattr(client_meta, conn))
- print("=" * 80)
+ print "%s:\t" % (conn), getattr(client_meta, conn)
+ print "=" * 80
def do_mappings(self, args):
"""Print out mapping info."""
@@ -411,11 +402,11 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
interested = [(etype, [args.split()[1]])
for etype in etypes]
else:
- interested = [(etype, generator.Entries[etype])
- for etype in etypes
+ interested = [(etype, generator.Entries[etype])
+ for etype in etypes
if etype in generator.Entries]
for etype, names in interested:
- for name in [name for name in names if name in
+ for name in [name for name in names if name in
generator.Entries.get(etype, {})]:
data.append((generator.name, etype, name))
printTabular(data)
diff --git a/src/sbin/bcfg2-ping-sweep b/src/sbin/bcfg2-ping-sweep
index 70f718690..718ad69d0 100755
--- a/src/sbin/bcfg2-ping-sweep
+++ b/src/sbin/bcfg2-ping-sweep
@@ -33,7 +33,7 @@ if __name__ == '__main__':
osname = uname()[0]
while hostlist or pids:
- if hostlist and len(list(pids.keys())) < 15:
+ if hostlist and len(pids.keys()) < 15:
host = hostlist.pop()
pid = fork()
if pid == 0:
diff --git a/src/sbin/bcfg2-repo-validate b/src/sbin/bcfg2-repo-validate
index 595613d8a..4d8dd6bed 100755
--- a/src/sbin/bcfg2-repo-validate
+++ b/src/sbin/bcfg2-repo-validate
@@ -11,12 +11,56 @@ import glob
import lxml.etree
import os
import sys
+import fnmatch
+import logging
import Bcfg2.Options
+from subprocess import Popen, PIPE, STDOUT
+
+def validate(filename, schemafile, schema=None, xinclude=True):
+ """validate a fail against the given lxml.etree.Schema. return
+ True on success, False on failure"""
+ if schema is None:
+ # if no schema object was provided, instantiate one
+ try:
+ schema = lxml.etree.XMLSchema(lxml.etree.parse(schemafile))
+ except:
+ logging.warn("Failed to process schema %s", schemafile)
+ return False
+
+ try:
+ datafile = lxml.etree.parse(filename)
+ except SyntaxError:
+ logging.warn("%s ***FAILS*** to parse \t\t<----", filename)
+ lint = Popen(["xmllint", filename], stdout=PIPE, stderr=STDOUT)
+ logging.warn(lint.communicate()[0])
+ lint.wait()
+ return False
+ except IOError:
+ logging.warn("Failed to open file %s \t\t<---", filename)
+ return False
+
+ if schema.validate(datafile):
+ logging.info("%s checks out", filename)
+ else:
+ cmd = ["xmllint"]
+ if xinclude:
+ cmd.append("--xinclude")
+ cmd.extend(["--noout", "--schema", schemafile, filename])
+ lint = Popen(cmd, stdout=PIPE, stderr=STDOUT)
+ output = lint.communicate()[0]
+ if lint.wait():
+ logging.warn("%s ***FAILS*** to verify \t\t<----", filename)
+ logging.warn(output)
+ return False
+ else:
+ logging.info("%s checks out", filename)
+ return True
if __name__ == '__main__':
opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY,
'verbose': Bcfg2.Options.VERBOSE,
'configfile': Bcfg2.Options.CFILE,
+ 'require-schema': Bcfg2.Options.REQUIRE_SCHEMA}
'schema': Bcfg2.Options.SCHEMA_PATH,
'stdin': Bcfg2.Options.FILES_ON_STDIN}
setup = Bcfg2.Options.OptionParser(opts)
@@ -27,6 +71,12 @@ if __name__ == '__main__':
os.chdir(schemadir)
repo = setup['repo']
+ # set up logging
+ level = logging.WARNING
+ if verbose:
+ level = logging.INFO
+ logging.basicConfig(level=level, format="%(message)s")
+
if setup['stdin']:
file_list = [s.strip() for s in sys.stdin.readlines()]
info_list = [f for f in file_list if os.path.basename(f) == 'info.xml']
@@ -44,6 +94,9 @@ if __name__ == '__main__':
dec_list = fnmatch.filter(file_list, "*/Decisions/*")
pkgcfg_list = fnmatch.filter(file_list, "*/Packages/config.xml")
gp_list = fnmatch.filter(file_list, "*/GroupPatterns/config.xml")
+ props_list = [f
+ for f in fnmatch.filter(file_list, "*/Properties/*.xml")
+ if "%s.xsd" % os.path.splitext(f)[0] in file_list]
else:
# not reading files from stdin
@@ -70,6 +123,7 @@ if __name__ == '__main__':
dec_list = glob.glob("%s/Decisions/*" % repo)
pkgcfg_list = glob.glob("%s/Packages/config.xml" % repo)
gp_list = glob.glob('%s/GroupPatterns/config.xml' % repo)
+ props_list = glob.glob("%s/Properties/*.xml" % repo)
# include files in metadata_list
ref_bundles = set()
@@ -81,8 +135,7 @@ if __name__ == '__main__':
filename = included.pop()
except KeyError:
continue
- if not setup['stdin'] or filepath in file_list:
- metadata_list.append("%s/Metadata/%s" % (repo, filename))
+ metadata_list.append("%s/Metadata/%s" % (repo, filename))
groupdata = lxml.etree.parse("%s/Metadata/%s" % (repo, filename))
group_ents = [ent.get('href') for ent in \
groupdata.
@@ -97,15 +150,14 @@ if __name__ == '__main__':
ref_bundles.add("%s/Bundler/%s" % (repo, bundle.get('name')))
# check for multiple default group definitions
- default_groups = []
- for grp in lxml.etree.parse("%s/Metadata/groups.xml" \
- % repo).findall('.//Group'):
- if grp.get('default') == 'true':
- default_groups.append(grp)
- if len(default_groups) > 1:
- print("*** Warning: Multiple default groups defined")
- for grp in default_groups:
- print(" %s" % grp.get('name'))
+ if "%s/Metadata/groups.xml" % repo in metadata_list:
+ default_groups = [g for g in lxml.etree.parse("%s/Metadata/groups.xml" %
+ repo).findall('.//Group')
+ if g.get('default') == 'true']
+ if len(default_groups) > 1:
+ logging.warn("*** Warning: Multiple default groups defined")
+ for grp in default_groups:
+ logging.warn(" %s", grp.get('name'))
# verify attributes for configuration entries
# (as defined in doc/server/configurationentries)
@@ -123,7 +175,7 @@ if __name__ == '__main__':
try:
xdata = lxml.etree.parse(rfile)
except lxml.etree.XMLSyntaxError, e:
- print("Failed to parse %s: %s" % (rfile, e))
+ logging.warn("Failed to parse %s: %s", rfile, e)
for posixpath in xdata.findall("//Path"):
pathname = posixpath.get('name')
pathtype = posixpath.get('type')
@@ -141,9 +193,11 @@ if __name__ == '__main__':
if pathset.issuperset(required_attrs):
continue
else:
- print("The following required attributes are missing for"
- " Path %s in %s: %s" % (pathname, rfile,
- [attr for attr in required_attrs.difference(pathset)]))
+ logging.warn("The following required attributes are missing for"
+ " Path %s in %s: %s",
+ pathname, rfile,
+ [attr
+ for attr in required_attrs.difference(pathset)])
# warn on duplicate Pkgmgr entries with the same priority
pset = set()
@@ -151,7 +205,7 @@ if __name__ == '__main__':
try:
xdata = lxml.etree.parse(plist)
except lxml.etree.XMLSyntaxError, e:
- print("Failed to parse %s: %s" % (plist, e))
+ logging.warn("Failed to parse %s: %s", plist, e)
# get priority, type, group
priority = xdata.getroot().get('priority')
ptype = xdata.getroot().get('type')
@@ -169,8 +223,8 @@ if __name__ == '__main__':
# check if package is already listed with same priority,
# type, grp
if ptuple in pset:
- print("Duplicate Package %s, priority:%s, type:%s"\
- % (pkg.get('name'), priority, ptype))
+ logging.warn("Duplicate Package %s, priority:%s, type:%s",
+ pkg.get('name'), priority, ptype)
else:
pset.add(ptuple)
@@ -190,65 +244,55 @@ if __name__ == '__main__':
failures = 0
for schemaname, filelist in list(filesets.items()):
- try:
- schema = lxml.etree.XMLSchema(lxml.etree.parse(open(schemaname %
- schemadir)))
- except:
- print("Failed to process schema %s" % (schemaname % schemadir))
- failures = 1
- continue
- for filename in filelist:
+ if filelist:
+ # avoid loading schemas for empty file lists
try:
- datafile = lxml.etree.parse(open(filename))
- except SyntaxError:
- print("%s ***FAILS*** to parse \t\t<----" % (filename))
- os.system("xmllint %s" % filename)
- failures = 1
- continue
- except IOError:
- print("Failed to open file %s \t\t<---" % (filename))
+ schema = lxml.etree.XMLSchema(lxml.etree.parse(schemaname %
+ schemadir))
+ except:
+ logging.warn("Failed to process schema %s",
+ schemaname % schemadir)
failures = 1
continue
- if schema.validate(datafile):
- if verbose:
- print("%s checks out" % (filename))
- else:
- rc = os.system("xmllint --noout --xinclude --schema \
- %s %s > /dev/null 2>/dev/null" % \
- (schemaname % schemadir, filename))
- if rc:
+ for filename in filelist:
+ if not validate(filename, schemaname % schemadir,
+ schema=schema, xinclude=not setup['stdin']):
failures = 1
- print("%s ***FAILS*** to verify \t\t<----" % (filename))
- os.system("xmllint --noout --xinclude --schema %s %s" % \
- (schemaname % schemadir, filename))
- elif verbose:
- print("%s checks out" % (filename))
+ # check Properties files against their schemas
+ for filename in props_list:
+ logging.info("checking %s" % filename)
+ schemafile = "%s.xsd" % os.path.splitext(filename)[0]
+ if os.path.exists(schemafile):
+ if not validate(filename, schemafile, xinclude=not setup['stdin']):
+ failures = 1
+ elif setup['require-schema']:
+ logging.warn("No schema found for %s", filename)
+ failures = 1
+
# print out missing bundle information
- if verbose:
- print("")
- if not setup['stdin']:
- # if we've taken a list of files on stdin, there's an
- # excellent chance that referenced bundles do not exist,
- # so skip this check
- for bundle in ref_bundles:
- # check for both regular and genshi bundles
- xmlbundle = "%s.xml" % bundle
- genshibundle = "%s.genshi" % bundle
- allbundles = bundle_list + genshibundle_list
- if (xmlbundle not in allbundles and
- genshibundle not in allbundles):
- print("*** Warning: Bundle %s referenced, but does not "
- "exist." % bundle)
-
- # verify bundle name attribute matches filename
- for bundle in (bundle_list + genshibundle_list):
- fname = bundle.split('Bundler/')[1].split('.')[0]
- xdata = lxml.etree.parse(bundle)
- bname = xdata.getroot().get('name')
- if fname != bname:
- print("The following names are inconsistent:")
- print(" Filename is %s" % fname)
- print(" Bundle name found in %s is %s" % (fname, bname))
+ logging.info("")
+ if not setup['stdin']:
+ # if we've taken a list of files on stdin, there's an
+ # excellent chance that referenced bundles do not exist, so
+ # skip this check
+ for bundle in ref_bundles:
+ # check for both regular and genshi bundles
+ xmlbundle = "%s.xml" % bundle
+ genshibundle = "%s.genshi" % bundle
+ allbundles = bundle_list + genshibundle_list
+ if xmlbundle not in allbundles and genshibundle not in allbundles:
+ logging.info("*** Warning: Bundle %s referenced, but does not "
+ "exist.", bundle)
+
+ # verify bundle name attribute matches filename
+ for bundle in (bundle_list + genshibundle_list):
+ fname = bundle.split('Bundler/')[1].split('.')[0]
+ xdata = lxml.etree.parse(bundle)
+ bname = xdata.getroot().get('name')
+ if fname != bname:
+ logging.warn("The following names are inconsistent:")
+ logging.warn(" Filename is %s", fname)
+ logging.warn(" Bundle name found in %s is %s", fname, bname)
raise SystemExit(failures)
diff --git a/src/sbin/bcfg2-reports b/src/sbin/bcfg2-reports
index c6cc766c6..559e9fb43 100755
--- a/src/sbin/bcfg2-reports
+++ b/src/sbin/bcfg2-reports
@@ -26,18 +26,15 @@ from getopt import getopt
import datetime
import fileinput
-
def timecompare(client1, client2):
"""Compares two clients by their timestamps."""
return cmp(client1.current_interaction.timestamp, \
client2.current_interaction.timestamp)
-
def namecompare(client1, client2):
"""Compares two clients by their names."""
return cmp(client1.name, client2.name)
-
def statecompare(client1, client2):
"""Compares two clients by their states."""
clean1 = client1.current_interaction.isclean()
@@ -50,7 +47,6 @@ def statecompare(client1, client2):
else:
return 0
-
def crit_compare(criterion, client1, client2):
"""Compares two clients by the criteria provided in criterion."""
for crit in criterion:
@@ -61,13 +57,12 @@ def crit_compare(criterion, client1, client2):
comp = statecompare(client1, client2)
elif crit == 'time':
comp = timecompare(client1, client2)
-
+
if comp != 0:
return comp
-
+
return 0
-
def print_fields(fields, cli, max_name, entrydict):
"""
Prints the fields specified in fields of cli, max_name
@@ -98,15 +93,14 @@ def print_fields(fields, cli, max_name, entrydict):
if len(entrydict) > 0:
display += " "
display += str(entrydict[cli])
- print(display)
-
+ print display
def print_entry(item, max_name):
fmt = ("%%-%ds " % (max_name))
fdata = item.entry.kind + ":" + item.entry.name
display = fmt % (fdata)
- print(display)
-
+ print display
+
fields = ""
sort = ""
badentry = ""
@@ -143,14 +137,14 @@ if expire != "":
if expire == c_inst.name:
if c_inst.expiration == None:
c_inst.expiration = datetime.datetime.now()
- print("Host expired.")
+ print "Host expired."
else:
c_inst.expiration = None
- print("Host un-expired.")
+ print "Host un-expired."
c_inst.save()
elif '-h' in args:
- print("""Usage: bcfg2-reports [option] ...
+ print """Usage: bcfg2-reports [option] ...
Options and arguments (and corresponding environment variables):
-a : shows all hosts, including expired hosts
@@ -176,13 +170,13 @@ Options and arguments (and corresponding environment variables):
(name,time,state)
--sort=ARG1,ARG2,... : sorts output on ARG1,ARG2,... (name,time,state)
--stale : shows hosts which haven't run in the last 24 hours
-""")
+"""
elif singlehost != "":
for c_inst in c_list:
if singlehost == c_inst.name:
baditems = c_inst.current_interaction.bad()
if len(baditems) > 0 and ('-b' in args or '-s' in args):
- print("Bad Entries:")
+ print "Bad Entries:"
max_name = -1
for item in baditems:
if len(item.entry.name) > max_name:
@@ -191,14 +185,14 @@ elif singlehost != "":
print_entry(item, max_name)
extraitems = c_inst.current_interaction.extra()
if len(extraitems) > 0 and ('-e' in args or '-s' in args):
- print("Extra Entries:")
+ print "Extra Entries:"
max_name = -1
for item in extraitems:
if len(item.entry.name) > max_name:
max_name = len(item.entry.name)
for item in extraitems:
print_entry(item, max_name)
-
+
else:
if fields == "":
@@ -214,19 +208,19 @@ else:
if extraentry != "":
extraentry = extraentry.split(',')
-
+
# stale hosts
if '--stale' in args:
for c_inst in c_list:
if c_inst.current_interaction.isstale():
result.append(c_inst)
# clean hosts
- elif '-c' in args:
+ elif '-c' in args:
for c_inst in c_list:
if c_inst.current_interaction.isclean():
result.append(c_inst)
# dirty hosts
- elif '-d' in args:
+ elif '-d' in args:
for c_inst in c_list:
if not c_inst.current_interaction.isclean():
result.append(c_inst)
@@ -287,7 +281,7 @@ else:
if sort != "":
result.sort(lambda x, y: crit_compare(sort, x, y))
-
+
if fields != "":
for c_inst in result:
if '-a' in args or c_inst.expiration == None: