version_builder.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. """
  2. Licensed to the Apache Software Foundation (ASF) under one
  3. or more contributor license agreements. See the NOTICE file
  4. distributed with this work for additional information
  5. regarding copyright ownership. The ASF licenses this file
  6. to you under the Apache License, Version 2.0 (the
  7. "License"); you may not use this file except in compliance
  8. with the License. You may obtain a copy of the License at
  9. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing, software
  11. distributed under the License is distributed on an "AS IS" BASIS,
  12. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. See the License for the specific language governing permissions and
  14. limitations under the License.
  15. """
  16. import optparse
  17. import os
  18. import subprocess
  19. import sys
  20. import xml.etree.ElementTree as ET
  21. def load_file(filename):
  22. """
  23. Loads the specified XML file
  24. """
  25. if os.path.exists(filename):
  26. tree = ET.ElementTree()
  27. tree.parse(filename)
  28. root = tree.getroot()
  29. else:
  30. attribs = {}
  31. attribs['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
  32. attribs['xsi:noNamespaceSchemaLocation'] = "version_definition.xsd"
  33. root = ET.Element("repository-version", attribs)
  34. ET.SubElement(root, "release")
  35. ET.SubElement(root, "manifest")
  36. ET.SubElement(root, "available-services")
  37. ET.SubElement(root, "repository-info")
  38. return root
  39. def save_file(xml, filename):
  40. """
  41. Saves the XML file
  42. """
  43. p = subprocess.Popen(['xmllint', '--format', '--output', filename, '-'], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
  44. (stdout, stderr) = p.communicate(input=ET.tostring(xml))
  45. def check_xmllint():
  46. """
  47. Verifies utility xmllint is available
  48. """
  49. try:
  50. p = subprocess.Popen(['xmllint', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
  51. (stdout, stderr) = p.communicate()
  52. if p.returncode != 0:
  53. raise Exception("xmllint command does not appear to be available")
  54. except:
  55. raise Exception("xmllint command does not appear to be available")
  56. def validate_file(filename, xsdfile):
  57. """
  58. Validates the XML file against the XSD
  59. """
  60. args = ['xmllint', '--noout', '--load-trace', '--schema', xsdfile, filename]
  61. p = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
  62. (stdout, stderr) = p.communicate()
  63. if p.returncode != 0:
  64. raise Exception(stderr)
  65. if len(stdout) > 0:
  66. print stdout
  67. if len(stderr) > 0:
  68. print stderr
  69. def update_simple(parent, name, value):
  70. """
  71. Helper method to either update or create the element
  72. """
  73. element = parent.find('./' + name)
  74. if element is None:
  75. element = ET.SubElement(parent, name)
  76. element.text = value
  77. else:
  78. element.text = value
  79. def process_release(xmlroot, options):
  80. """
  81. Create elements of the 'release' parent
  82. """
  83. release_element = xmlroot.find("./release")
  84. if release_element is None:
  85. raise Exception("Element 'release' is not found")
  86. if options.release_type:
  87. update_simple(release_element, "type", options.release_type)
  88. if options.release_stack:
  89. update_simple(release_element, "stack-id", options.release_stack)
  90. if options.release_version:
  91. update_simple(release_element, "version", options.release_version)
  92. if options.release_build:
  93. update_simple(release_element, "build", options.release_build)
  94. if options.release_compatible:
  95. update_simple(release_element, "compatible-with", options.release_compatible)
  96. if options.release_notes:
  97. update_simple(release_element, "release-notes", options.release_notes)
  98. if options.release_display:
  99. update_simple(release_element, "display", options.release_display)
  100. if options.release_package_version:
  101. update_simple(release_element, "package-version", options.release_package_version)
  102. def process_manifest(xmlroot, options):
  103. """
  104. Creates the manifest element
  105. """
  106. if not options.manifest:
  107. return
  108. manifest_element = xmlroot.find("./manifest")
  109. if manifest_element is None:
  110. raise Exception("Element 'manifest' is not found")
  111. service_element = manifest_element.find("./service[@id='{0}']".format(options.manifest_id))
  112. if service_element is None:
  113. service_element = ET.SubElement(manifest_element, "service")
  114. service_element.set('id', options.manifest_id)
  115. service_element.set('name', options.manifest_service)
  116. service_element.set('version', options.manifest_version)
  117. if options.manifest_version_id:
  118. service_element.set('version-id', options.manifest_version_id)
  119. def process_available(xmlroot, options):
  120. """
  121. Processes available service elements
  122. """
  123. if not options.available:
  124. return
  125. manifest_element = xmlroot.find("./manifest")
  126. if manifest_element is None:
  127. raise Exception("'manifest' element is not found")
  128. service_element = manifest_element.find("./service[@id='{0}']".format(options.manifest_id))
  129. if service_element is None:
  130. raise Exception("Cannot add an available service for {0}; it's not on the manifest".format(options.manifest_id))
  131. available_element = xmlroot.find("./available-services")
  132. if available_element is None:
  133. raise Exception("'available-services' is not found")
  134. service_element = available_element.find("./service[@idref='{0}']".format(options.manifest_id))
  135. if service_element is not None:
  136. available_element.remove(service_element)
  137. service_element = ET.SubElement(available_element, "service")
  138. service_element.set('idref', options.manifest_id)
  139. if options.available_components:
  140. components = options.available_components.split(',')
  141. for component in components:
  142. e = ET.SubElement(service_element, 'component')
  143. e.text = component
  144. def process_repo(xmlroot, options):
  145. """
  146. Processes repository options. This method doesn't update or create individual elements, it
  147. creates the entire repo structure
  148. """
  149. if not options.repo:
  150. return
  151. repo_parent = xmlroot.find("./repository-info")
  152. if repo_parent is None:
  153. raise Exception("'repository-info' element is not found")
  154. os_element = repo_parent.find("./os[@family='{0}']".format(options.repo_os))
  155. if os_element is None:
  156. os_element = ET.SubElement(repo_parent, 'os')
  157. os_element.set('family', options.repo_os)
  158. repo_element = os_element.find("./repo/[reponame='{0}']".format(options.repo_name))
  159. if repo_element is not None:
  160. os_element.remove(repo_element)
  161. repo_element = ET.SubElement(os_element, 'repo')
  162. e = ET.SubElement(repo_element, 'baseurl')
  163. e.text = options.repo_url
  164. e = ET.SubElement(repo_element, 'repoid')
  165. e.text = options.repo_id
  166. e = ET.SubElement(repo_element, 'reponame')
  167. e.text = options.repo_name
  168. def validate_manifest(parser, options):
  169. """
  170. Validates manifest options from the command line
  171. """
  172. if not options.manifest:
  173. return
  174. template = "When specifying --manifest, {0} is also required"
  175. if not options.manifest_id:
  176. parser.error(template.format("--manifest-id"))
  177. if not options.manifest_service:
  178. parser.error(template.format("--manifest-service"))
  179. if not options.manifest_version:
  180. parser.error(template.format("--manifest-version"))
  181. def validate_available(parser, options):
  182. """
  183. Validates available service options from the command line
  184. """
  185. if not options.available:
  186. return
  187. if not options.manifest_id:
  188. parser.error("When specifying --available, --manifest-id is also required")
  189. def validate_repo(parser, options):
  190. """
  191. Validates repo options from the command line
  192. """
  193. if not options.repo:
  194. return
  195. template = "When specifying --repo, {0} is also required"
  196. if not options.repo_os:
  197. parser.error(template.format("--repo-os"))
  198. if not options.repo_url:
  199. parser.error(template.format("--repo-url"))
  200. if not options.repo_id:
  201. parser.error(template.format("--repo-id"))
  202. if not options.repo_name:
  203. parser.error(template.format("--repo-name"))
  204. def main(argv):
  205. parser = optparse.OptionParser(
  206. epilog="OS utility 'xmllint' is required for this tool to function. It handles pretty-printing and XSD validation.")
  207. parser.add_option('--file', dest='filename',
  208. help="The output XML file")
  209. parser.add_option('--finalize', action='store_true', dest='finalize',
  210. help="Finalize and validate the XML file")
  211. parser.add_option('--xsd', dest='xsd_file',
  212. help="The XSD location when finalizing")
  213. parser.add_option('--release-type', type='choice', choices=['STANDARD', 'PATCH'], dest='release_type' ,
  214. help="Indicate the release type: i.e. STANDARD or PATCH")
  215. parser.add_option('--release-stack', dest='release_stack',
  216. help="The stack id: e.g. HDP-2.4")
  217. parser.add_option('--release-version', dest='release_version',
  218. help="The release version without build number: e.g. 2.4.0.1")
  219. parser.add_option('--release-build', dest='release_build',
  220. help="The release build number: e.g. 1234")
  221. parser.add_option('--release-compatible', dest='release_compatible',
  222. help="Regular Expression string to identify version compatibility for patches: e.g. 2.4.1.[0-9]")
  223. parser.add_option('--release-notes', dest='release_notes',
  224. help="A http link to the documentation notes")
  225. parser.add_option('--release-display', dest='release_display',
  226. help="The display name for this release")
  227. parser.add_option('--release-package-version', dest='release_package_version',
  228. help="Identifier to use when installing packages, generally a part of the package name")
  229. parser.add_option('--manifest', action='store_true', dest='manifest',
  230. help="Add a manifest service with other options: --manifest-id, --manifest-service, --manifest-version, --manifest-version-id")
  231. parser.add_option('--manifest-id', dest='manifest_id',
  232. help="Unique ID for a service in a manifest. Required when specifying --manifest and --available")
  233. parser.add_option('--manifest-service', dest='manifest_service')
  234. parser.add_option('--manifest-version', dest='manifest_version')
  235. parser.add_option('--manifest-version-id', dest='manifest_version_id')
  236. parser.add_option('--available', action='store_true', dest='available',
  237. help="Add an available service with other options: --manifest-id, --available-components")
  238. parser.add_option('--available-components', dest='available_components',
  239. help="A CSV of service components that are intended to be upgraded via patch. \
  240. Omitting this implies the entire service should be upgraded")
  241. parser.add_option('--repo', action='store_true', dest='repo',
  242. help="Add repository data with options: --repo-os, --repo-url, --repo-id, --repo-name")
  243. parser.add_option('--repo-os', dest='repo_os',
  244. help="The operating system type: i.e. redhat6, redhat7, debian7, ubuntu12, ubuntu14, suse11")
  245. parser.add_option('--repo-url', dest='repo_url',
  246. help="The base url for the repository data")
  247. parser.add_option('--repo-id', dest='repo_id', help="The ID of the repo")
  248. parser.add_option('--repo-name', dest='repo_name', help="The name of the repo")
  249. (options, args) = parser.parse_args()
  250. check_xmllint()
  251. # validate_filename
  252. if not options.filename:
  253. parser.error("--file option is required")
  254. validate_manifest(parser, options)
  255. validate_available(parser, options)
  256. validate_repo(parser, options)
  257. # validate_finalize
  258. if options.finalize and not options.xsd_file:
  259. parser.error("Must supply XSD (--xsd) when finalizing")
  260. # load file
  261. root = load_file(options.filename)
  262. process_release(root, options)
  263. process_manifest(root, options)
  264. process_available(root, options)
  265. process_repo(root, options)
  266. # save file
  267. save_file(root, options.filename)
  268. if options.finalize:
  269. validate_file(options.filename, options.xsd_file)
  270. if __name__ == "__main__":
  271. main(sys.argv)