components/ruby/puppet/files/update_smf.py
branchs11u2-sru
changeset 3460 5c5af6e58474
child 3770 ca450a806cc1
equal deleted inserted replaced
3457:6358358b4186 3460:5c5af6e58474
       
     1 #! /usr/bin/python
       
     2 #
       
     3 # CDDL HEADER START
       
     4 #
       
     5 # The contents of this file are subject to the terms of the
       
     6 # Common Development and Distribution License (the "License").
       
     7 # You may not use this file except in compliance with the License.
       
     8 #
       
     9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
    10 # or http://www.opensolaris.org/os/licensing.
       
    11 # See the License for the specific language governing permissions
       
    12 # and limitations under the License.
       
    13 #
       
    14 # When distributing Covered Code, include this CDDL HEADER in each
       
    15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    16 # If applicable, add the following below this CDDL HEADER, with the
       
    17 # fields enclosed by brackets "[]" replaced with your own identifying
       
    18 # information: Portions Copyright [yyyy] [name of copyright owner]
       
    19 #
       
    20 # CDDL HEADER END
       
    21 #
       
    22 
       
    23 #
       
    24 # Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
       
    25 #
       
    26 
       
    27 '''
       
    28 Utility program for helping with the upgrade of puppet to a newer
       
    29 version.  This program will take a puppet configuration file that
       
    30 has been generated via the command sequence
       
    31 
       
    32 puppet agent --genconfig > puppet.conf
       
    33 
       
    34 and use the data in that configuration file to replace the
       
    35 associated Puppet SMF user configuratable properties with the
       
    36 properties that are allowed in the new version of puppet
       
    37 
       
    38 NOTE: This file should not be included with the puppet release
       
    39 '''
       
    40 
       
    41 import os
       
    42 import re
       
    43 import sys
       
    44 
       
    45 from lxml import etree
       
    46 from optparse import OptionParser
       
    47 
       
    48 
       
    49 COMMENT_PATTERN = re.compile(".*# ?(.*)")
       
    50 CONFIG_VALUE_PATTERN = re.compile("([\S]+)\s*=\s*(\S*)")
       
    51 
       
    52 DEFAULT_VALUE_STR = "The default value is "
       
    53 
       
    54 # SMF defined property types.  For a list of
       
    55 # all available types see
       
    56 # /usr/share/lib/xml/dtd/service_bundle.dtd.1
       
    57 TYPE_ASTRING = "astring"
       
    58 TYPE_BOOLEAN = "boolean"
       
    59 TYPE_INTEGER = "integer"
       
    60 TYPE_HOST = "host"
       
    61 TYPE_HOSTNAME = "hostname"
       
    62 TYPE_NETADDRESS = "net_address"
       
    63 TYPE_URI = "uri"
       
    64 
       
    65 
       
    66 # Dictionary of currently defined property types to associate
       
    67 # with a specified property.  Any property not defined here
       
    68 # is assumed to have a property type of astring, integer,
       
    69 # or boolean
       
    70 PROP_TYPE = {
       
    71     'server': TYPE_HOST,
       
    72     'archive_file_server': TYPE_HOST,
       
    73     'bindaddress': TYPE_NETADDRESS,
       
    74     'ca_server': TYPE_HOST,
       
    75     'certname': TYPE_HOSTNAME,
       
    76     'couchdb_url': TYPE_URI,
       
    77     'dbserver': TYPE_HOST,
       
    78     'dns_alt_names': TYPE_HOST,
       
    79     'http_proxy_host': TYPE_HOST,
       
    80     'inventory_server': TYPE_HOST,
       
    81     'ldapserver': TYPE_HOST,
       
    82     'ldapuser': TYPE_HOSTNAME,
       
    83     'module_repository': TYPE_URI,
       
    84     'queue_source': TYPE_URI,
       
    85     'report_server': TYPE_HOST,
       
    86     'reporturl': TYPE_URI,
       
    87     'smtpserver': TYPE_HOST,
       
    88     'srv_domain': TYPE_HOST,
       
    89 }
       
    90 
       
    91 # Dictionary used to hold properites and the resulting xml code
       
    92 PUPPET_CONFIG_DICT = dict()
       
    93 
       
    94 
       
    95 def err(msg):
       
    96     '''Output standard error message'''
       
    97     # Duplicate the syntax of the parser.error
       
    98     sys.stderr.write("%(prog)s: error: %(msg)s\n" %
       
    99                      {"prog": os.path.basename(sys.argv[0]), "msg": msg})
       
   100 
       
   101 
       
   102 def create_config_element(key, key_type, desc_text):
       
   103     '''Create a basic xml entry following the basic pattern of
       
   104 
       
   105         <prop_pattern name='${key}' type='${key_type}'
       
   106                 required='false'>
       
   107                     <description> <loctext xml:lang='C'>
       
   108                          ${desc_text}
       
   109                     </loctext> </description>
       
   110         </prop_pattern>
       
   111     '''
       
   112     prop_pattern = etree.Element(
       
   113         "prop_pattern",
       
   114         name=key,
       
   115         type=key_type,
       
   116         required="false")
       
   117     desc = etree.SubElement(prop_pattern, "description")
       
   118     loctext = etree.SubElement(desc, "loctext")
       
   119     loctext.text = "\n%s\n\t    " % desc_text
       
   120     loctext.set('{http://www.w3.org/XML/1998/namespace}lang', 'C')
       
   121     return prop_pattern
       
   122 
       
   123 
       
   124 def determine_type(key, value):
       
   125     '''Determine the xml property type to associate with the
       
   126        specified key
       
   127     '''
       
   128 
       
   129     # Does the key have a specified xml property type
       
   130     # already defined?
       
   131     try:
       
   132         return PROP_TYPE[key]
       
   133     except KeyError:
       
   134         pass
       
   135 
       
   136     # Use the value to determine the xml property type
       
   137     if value.isdigit():
       
   138         return TYPE_INTEGER
       
   139     if value.lower() in ['false', 'true']:
       
   140         return TYPE_BOOLEAN
       
   141     return TYPE_ASTRING
       
   142 
       
   143 
       
   144 def process_grouping(lines):
       
   145     '''Process the lines in the list.  The last entry should be
       
   146        a 'key=value' entry
       
   147     '''
       
   148 
       
   149     # The last field should be a key = value pair
       
   150     # If it's not then the format of the file is not matching
       
   151     # the expected format of
       
   152     #
       
   153     # Description
       
   154     # The default value is "xxxx"
       
   155     # key = value
       
   156     #
       
   157     key_value = lines.pop()
       
   158     match = CONFIG_VALUE_PATTERN.match(key_value)
       
   159     if not match:
       
   160         raise TypeError("Last line in grouping is not in expected "
       
   161                         "format of 'key = value'\n%s" %
       
   162                         "\n".join(lines))
       
   163     key = match.group(1)
       
   164     value = match.group(2)
       
   165 
       
   166     default_value_line = lines.pop()
       
   167     if not default_value_line.startswith(DEFAULT_VALUE_STR):
       
   168         # Not a match.  Last line was still part of the description
       
   169         lines.append(default_value_line)
       
   170 
       
   171     key_type = determine_type(key, value)
       
   172 
       
   173     # remaining lines are the descriptor field
       
   174     desc = '\n'.join(lines)
       
   175     PUPPET_CONFIG_DICT[key] = (key, key_type, desc)
       
   176 
       
   177 
       
   178 def parse_puppet_config(filename):
       
   179     '''Parse the puppet configuration file that is generated by
       
   180        puppet agent --genconfig
       
   181     '''
       
   182     parameter_list = []
       
   183     agent_check = True
       
   184     with open(filename, 'r') as f_handle:
       
   185         for line in f_handle:
       
   186             if agent_check:
       
   187                 if line.startswith("[agent]"):
       
   188                     # Throw away the initial starting block code in the
       
   189                     del parameter_list[:]
       
   190                     agent_check = False
       
   191                     continue
       
   192             line = line.strip().replace("\n", "")
       
   193             if not line:
       
   194                 # If parameter_list is not empty, process the data and
       
   195                 # generate an xml structure
       
   196                 process_grouping(parameter_list)
       
   197                 # Done processing, delete all the saved entries
       
   198                 del parameter_list[:]
       
   199                 continue
       
   200 
       
   201             match = COMMENT_PATTERN.match(line)
       
   202             if match:
       
   203                 line = match.group(1)
       
   204             parameter_list.append(line)
       
   205     f_handle.close()
       
   206 
       
   207 
       
   208 def update_smf_file(smf_xml_file, output_file, version):
       
   209     '''Replace the puppet property definitions in the specified SMF
       
   210        file with those that are stored in PUPPET_CONFIG_DICT
       
   211     '''
       
   212 
       
   213     try:
       
   214         parser = etree.XMLParser(remove_blank_text=True)
       
   215         tree = etree.parse(smf_xml_file, parser)
       
   216         root = tree.getroot()
       
   217         template = root.find("service/template")
       
   218         puppet_desc = template.find("common_name/loctext")
       
   219         puppet_desc.text = "Puppet version %s" % version
       
   220 
       
   221         pg_pattern = template.find("pg_pattern")
       
   222     except IOError as msg:
       
   223         err(msg)
       
   224         return -1
       
   225     except etree.XMLSyntaxError as msg:
       
   226         err(msg)
       
   227         return -1
       
   228     except NameError as msg:
       
   229         err("XML file %s does not match expected formated" % smf_xml_file)
       
   230 
       
   231     # Delete the pg_pattern nodes and it's children
       
   232     # This is the structure that will be rebuilt based
       
   233     # on the genconfig information that was read in
       
   234     if pg_pattern is not None:
       
   235         template.remove(pg_pattern)
       
   236 
       
   237     # <pg_pattern name='config' type='application' required='false'>
       
   238     pg_pattern = etree.SubElement(
       
   239         template,
       
   240         "pg_pattern",
       
   241         name="config",
       
   242         type="application",
       
   243         required="false")
       
   244     for key in sorted(PUPPET_CONFIG_DICT.iterkeys()):
       
   245         values = PUPPET_CONFIG_DICT[key]
       
   246         element = create_config_element(values[0], values[1], values[2])
       
   247         pg_pattern.append(element)
       
   248 
       
   249     # Write out the contents of the updated puppet SMF config file
       
   250     print "Writting out contents of new SMF configuration file to: %s" % \
       
   251         output_file
       
   252     with open(output_file, "w") as f_handle:
       
   253         f_handle.write(etree.tostring(tree, pretty_print=True))
       
   254     f_handle.close()
       
   255 
       
   256 
       
   257 def option_list():
       
   258     '''Build the option list for this utility'''
       
   259     desc = "Utility for assisting in the upgrading of Solaris Puppet SMF file"
       
   260     usage = "usage: %prog -c <puppet_config_file> -s <smf_confilg_file> " \
       
   261             "-v <puppet_version> [-o <output_file>]\n"
       
   262     opt_list = OptionParser(description=desc, usage=usage)
       
   263 
       
   264     opt_list.add_option("-c", "--config", dest="config", default=None,
       
   265                         action="store", type="string", nargs=1,
       
   266                         metavar="<puppet_config_file>",
       
   267                         help="Puppet configuration file generated via"
       
   268                              "genconfig option to puppet. i.e. "
       
   269                              "puppet agent --genconfig > puppet.conf")
       
   270     opt_list.add_option("-s", "--smf", dest="smf_xml", default=None,
       
   271                         action="store", type="string", nargs=1,
       
   272                         metavar="<smf_config_file>",
       
   273                         help="Current solaris Puppet SMF XML configuration"
       
   274                              " file. This file is located in <userland_tree>"
       
   275                              "/components/puppet/files/puppet.xml")
       
   276     opt_list.add_option("-o", "--output", dest="output", default=None,
       
   277                         action="store", type="string", nargs=1,
       
   278                         metavar="<output_file>",
       
   279                         help="The name of the new puppet.xml file ")
       
   280     opt_list.add_option("-v", "--version", dest="version", default="None",
       
   281                         action="store", type="string", nargs=1,
       
   282                         metavar="<puppet_version>",
       
   283                         help="Puppet Version of update")
       
   284 
       
   285     return opt_list
       
   286 
       
   287 
       
   288 def main():
       
   289     '''Execute this utility based on the options supplied by the user'''
       
   290     parser = option_list()
       
   291 
       
   292     (options, _args) = parser.parse_args()
       
   293 
       
   294     if not options.output and options.version:
       
   295         options.output = "puppet.%s.xml" % options.version
       
   296 
       
   297     if not options.config or not options.smf_xml or \
       
   298             not options.output or not options.version:
       
   299         err("Required options not specified")
       
   300         parser.print_help()
       
   301         sys.exit(-1)
       
   302 
       
   303     if not os.path.isfile(options.config):
       
   304         err("%s does not exist or is not a regular file\n"
       
   305             % options.config)
       
   306         sys.exit(-1)
       
   307     if not os.path.isfile(options.smf_xml):
       
   308         err("%s does not exist or is not a regular file\n"
       
   309             % options.smf_xml)
       
   310         sys.exit(-1)
       
   311     if os.path.exists(options.output):
       
   312         err("specified file %s already exist\n"
       
   313             % options.output)
       
   314         sys.exit(-1)
       
   315 
       
   316     parse_puppet_config(options.config)
       
   317     update_smf_file(options.smf_xml, options.output, options.version)
       
   318 
       
   319 if __name__ == '__main__':
       
   320     main()