diff -r f958607559a6 -r 48aa82a0931f components/ruby/puppet/tools/update_smf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/ruby/puppet/tools/update_smf.py Mon Nov 28 16:54:02 2016 -0800 @@ -0,0 +1,313 @@ +#!/usr/bin/python2.7 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. +# + +''' +Utility program for helping with the upgrade of puppet to a newer +version. This program will take a puppet configuration file that +has been generated via the command sequence + +puppet agent --genconfig > puppet.conf + +and use the data in that configuration file to replace the +associated Puppet SMF user configuratable properties with the +properties that are allowed in the new version of puppet + +NOTE: This file should not be included with the puppet release +''' + +import os +import re +import sys +import textwrap + +from lxml import etree +from optparse import OptionParser + + +COMMENT_PATTERN = re.compile(".*# ?(.*)") +CONFIG_VALUE_PATTERN = re.compile("([\S]+)\s*=\s*(\S*)") + +DEFAULT_VALUE_STR = "The default value is " + +# SMF defined property types. For a list of +# all available types see +# /usr/share/lib/xml/dtd/service_bundle.dtd.1 +TYPE_ASTRING = "astring" +TYPE_BOOLEAN = "boolean" +TYPE_INTEGER = "integer" +TYPE_HOST = "host" +TYPE_HOSTNAME = "hostname" +TYPE_NETADDRESS = "net_address" +TYPE_URI = "uri" + + +# Dictionary of currently defined property types to associate +# with a specified property. Any property not defined here +# is assumed to have a property type of astring, integer, +# or boolean +PROP_TYPE = { + 'server': TYPE_HOST, + 'archive_file_server': TYPE_HOST, + 'bindaddress': TYPE_NETADDRESS, + 'ca_server': TYPE_HOST, + 'certname': TYPE_HOSTNAME, + 'couchdb_url': TYPE_URI, + 'dbserver': TYPE_HOST, + 'dns_alt_names': TYPE_HOST, + 'http_proxy_host': TYPE_HOST, + 'inventory_server': TYPE_HOST, + 'ldapserver': TYPE_HOST, + 'ldapuser': TYPE_HOSTNAME, + 'module_repository': TYPE_URI, + 'queue_source': TYPE_URI, + 'report_server': TYPE_HOST, + 'reporturl': TYPE_URI, + 'smtpserver': TYPE_HOST, + 'srv_domain': TYPE_HOST, +} + +# Dictionary used to hold properites and the resulting xml code +PUPPET_CONFIG_DICT = dict() + + +def err(msg): + '''Output standard error message''' + # Duplicate the syntax of the parser.error + sys.stderr.write("%(prog)s: error: %(msg)s\n" % + {"prog": os.path.basename(sys.argv[0]), "msg": msg}) + + +def create_config_element(key, key_type, desc_text): + '''Create a basic xml entry following the basic pattern of + + + + ${desc_text} + + + ''' + prop_pattern = etree.Element( + "prop_pattern", + name=key, + type=key_type, + required="false") + desc = etree.SubElement(prop_pattern, "description") + loctext = etree.SubElement(desc, "loctext") + loctext.text = "\n%s\n\t " % desc_text + loctext.set('{http://www.w3.org/XML/1998/namespace}lang', 'C') + return prop_pattern + + +def determine_type(key, value): + '''Determine the xml property type to associate with the + specified key + ''' + + # Does the key have a specified xml property type + # already defined? + try: + return PROP_TYPE[key] + except KeyError: + pass + + # Use the value to determine the xml property type + if value.isdigit(): + return TYPE_INTEGER + if value.lower() in ['false', 'true']: + return TYPE_BOOLEAN + return TYPE_ASTRING + + +def process_grouping(lines): + '''Process the lines in the list. The last entry should be + a 'key=value' entry + ''' + + # The last field should be a key = value pair + # If it's not then the format of the file is not matching + # the expected format of + # + # Description + # The default value is "xxxx" + # key = value + # + key_value = lines.pop() + match = CONFIG_VALUE_PATTERN.match(key_value) + if not match: + raise TypeError("Last line in grouping is not in expected " + "format of 'key = value'\n%s" % + "\n".join(lines)) + key = match.group(1) + value = match.group(2) + + default_value_line = lines.pop() + if not default_value_line.startswith(DEFAULT_VALUE_STR): + # Not a match. Last line was still part of the description + lines.append(default_value_line) + + key_type = determine_type(key, value) + + # remaining lines are the descriptor field + desc = textwrap.fill("\n".join(lines),79) + PUPPET_CONFIG_DICT[key] = (key, key_type, desc) + + +def parse_puppet_config(filename): + '''Parse the puppet configuration file that is generated by + puppet agent --genconfig + ''' + parameter_list = [] + agent_check = True + with open(filename, 'r') as f_handle: + for line in f_handle: + if agent_check: + if line.startswith("[agent]"): + # Throw away the initial starting block code in the + del parameter_list[:] + agent_check = False + continue + line = line.strip().replace("\n", "") + if not line: + # If parameter_list is not empty, process the data and + # generate an xml structure + process_grouping(parameter_list) + # Done processing, delete all the saved entries + del parameter_list[:] + continue + + match = COMMENT_PATTERN.match(line) + if match: + line = match.group(1) + parameter_list.append(line) + f_handle.close() + + +def update_smf_file(smf_xml_file, output_file): + '''Replace the puppet property definitions in the specified SMF + file with those that are stored in PUPPET_CONFIG_DICT + ''' + + try: + parser = etree.XMLParser(remove_blank_text=True) + tree = etree.parse(smf_xml_file, parser) + root = tree.getroot() + template = root.find("service/template") + puppet_desc = template.find("common_name/loctext") + puppet_desc.text = "Puppet" + + pg_pattern = template.find("pg_pattern") + except IOError as msg: + err(msg) + return -1 + except etree.XMLSyntaxError as msg: + err(msg) + return -1 + except NameError as msg: + err("XML file %s does not match expected formated" % smf_xml_file) + + # Delete the pg_pattern nodes and it's children + # This is the structure that will be rebuilt based + # on the genconfig information that was read in + if pg_pattern is not None: + template.remove(pg_pattern) + + # + pg_pattern = etree.SubElement( + template, + "pg_pattern", + name="config", + type="application", + required="false") + for key in sorted(PUPPET_CONFIG_DICT.iterkeys()): + values = PUPPET_CONFIG_DICT[key] + element = create_config_element(values[0], values[1], values[2]) + pg_pattern.append(element) + + # Write out the contents of the updated puppet SMF config file + print "Writting out contents of new SMF configuration file to: %s" % \ + output_file + with open(output_file, "w") as f_handle: + f_handle.write(etree.tostring(tree, pretty_print=True)) + f_handle.close() + + +def option_list(): + '''Build the option list for this utility''' + desc = "Utility for assisting in the upgrading of Solaris Puppet SMF file" + usage = "usage: %prog -c -s " \ + "[-o ]\n" + opt_list = OptionParser(description=desc, usage=usage) + + opt_list.add_option("-c", "--config", dest="config", default=None, + action="store", type="string", nargs=1, + metavar="", + help="Puppet configuration file generated via" + "genconfig option to puppet. i.e. " + "puppet agent --genconfig > puppet.conf") + opt_list.add_option("-s", "--smf", dest="smf_xml", default=None, + action="store", type="string", nargs=1, + metavar="", + help="Current solaris Puppet SMF XML configuration" + " file. This file is located in " + "/components/puppet/files/puppet.xml") + opt_list.add_option("-o", "--output", dest="output", default=None, + action="store", type="string", nargs=1, + metavar="", + help="The name of the new puppet.xml file ") + return opt_list + + +def main(): + '''Execute this utility based on the options supplied by the user''' + parser = option_list() + + (options, _args) = parser.parse_args() + + if not options.config or not options.smf_xml or \ + not options.output: + err("Required options not specified") + parser.print_help() + sys.exit(-1) + + if not os.path.isfile(options.config): + err("%s does not exist or is not a regular file\n" + % options.config) + sys.exit(-1) + if not os.path.isfile(options.smf_xml): + err("%s does not exist or is not a regular file\n" + % options.smf_xml) + sys.exit(-1) + if os.path.exists(options.output): + err("specified file %s already exist\n" + % options.output) + sys.exit(-1) + + parse_puppet_config(options.config) + update_smf_file(options.smf_xml, options.output) + +if __name__ == '__main__': + main()