project-config/jenkins/scripts/ZanataUtils.py
Steve Kowalik 4c8bd4f741 No longer deal with locales when generating Zanata
Zanata 3.7.1 moved locale configuration from the client having to
specify them in the zanata.xml file, to having the server make all
the decisions. As such, the server no longer includes any locales
information in the returned configuration.

Change-Id: I2e8870280c30a4b2983996e5e9803933db986add
2015-08-17 13:02:30 +10:00

233 lines
7.9 KiB
Python
Executable File

# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from io import BytesIO
import json
from lxml import etree
import os
import re
import requests
try:
import configparser
except ImportError:
import ConfigParser as configparser
try:
from urllib.parse import urljoin
except ImportError:
from urlparse import urljoin
class IniConfig:
"""Object that stores zanata.ini configuration
Read zanata.ini and make its values available.
Attributes:
inifile: The path to the ini file to load values from.
"""
def __init__(self, inifile):
self.inifile = inifile
self._load_config()
def _load_config(self):
"""Load configuration from the zanata.ini file
Parses the ini file and stores its data.
"""
if not os.path.isfile(self.inifile):
raise ValueError('zanata.ini file not found.')
config = configparser.ConfigParser()
try:
config.read(self.inifile)
except configparser.Error:
raise ValueError('zanata.ini could not be parsed, please check '
'format.')
for item in config.items('servers'):
item_type = item[0].split('.')[1]
if item_type in ('username', 'key', 'url'):
setattr(self, item_type, item[1])
class ZanataRestService:
def __init__(self, zconfig, content_type='application/xml'):
self.url = zconfig.url
if "charset" not in content_type:
content_type = "%s;charset=utf8" % content_type
self.headers = {'Accept': content_type,
'Content-Type': content_type,
'X-Auth-User': zconfig.username,
'X-Auth-Token': zconfig.key}
def _construct_url(self, url_fragment):
return urljoin(self.url, url_fragment)
def query(self, url_fragment, verify=False, raise_errors=True):
request_url = self._construct_url(url_fragment)
try:
r = requests.get(request_url, verify=verify, headers=self.headers)
except requests.exceptions.ConnectionError:
raise ValueError('Connection error')
if raise_errors and r.status_code != 200:
raise ValueError('Got status code %s for %s' %
(r.status_code, request_url))
if raise_errors and not r.content:
raise ValueError('Did not recieve any data from %s' % request_url)
return r
def push(self, url_fragment, data, verify=False):
request_url = self._construct_url(url_fragment)
try:
return requests.put(request_url, verify=verify,
headers=self.headers, data=json.dumps(data))
except requests.exceptions.ConnectionError:
raise ValueError('Connection error')
class ProjectConfig:
"""Object that stores zanata.xml per-project configuration.
Given an existing zanata.xml, read in the values and make
them accessible. Otherwise, write out a zanata.xml file
for the project given the supplied values.
Attributes:
zconfig (IniConfig): zanata.ini values
xmlfile (str): path to zanata.xml to read or write
rules (list): list of two-ples with pattern and rules
"""
def __init__(self, zconfig, xmlfile, rules, **kwargs):
self.rest_service = ZanataRestService(zconfig)
self.xmlfile = xmlfile
self.rules = self._parse_rules(rules)
if os.path.isfile(os.path.abspath(xmlfile)):
self._load_config()
else:
for key, value in kwargs.items():
setattr(self, key, value)
self._create_config()
def _get_tag_prefix(self, root):
"""XML utility method
Get the namespace of the XML file so we can
use it to search for tags.
"""
return '{%s}' % etree.QName(root).namespace
def _parse_rules(self, rules):
"""Parse a two-ple of pattern, rule.
Returns a list of dictionaries with 'pattern' and 'rule' keys.
"""
return [{'pattern': rule[0], 'rule': rule[1]} for rule in rules]
def _load_config(self):
"""Load configuration from an existing zanata.xml
Load and store project configuration from zanata.xml
"""
try:
with open(self.xmlfile, 'r') as f:
xml = etree.parse(f)
except IOError:
raise ValueError('Cannot load zanata.xml for this project')
except etree.ParseError:
raise ValueError('Cannot parse zanata.xml for this project')
root = xml.getroot()
tag_prefix = self._get_tag_prefix(root)
self.project = root.find('%sproject' % tag_prefix).text
self.version = root.find('%sproject-version' % tag_prefix).text
self.srcdir = root.find('%ssrc-dir' % tag_prefix).text
self.txdir = root.find('%strans-dir' % tag_prefix).text
rules = root.find('%srules' % tag_prefix)
self.rules = self._parse_rules([(tag.get('pattern', tag.text))
for tag in rules.getchildren()])
def _create_config(self):
"""Create zanata.xml
Use the supplied parameters to create zanata.xml by downloading
a base version of the file and adding customizations.
"""
xml = self._fetch_zanata_xml()
self._add_configuration(xml)
self._write_xml(xml)
def _fetch_zanata_xml(self, verify=False):
"""Get base zanata.xml
Download a basic version of the configuration for the project
using Zanata's REST API.
Attributes:
verify (bool): Verify the SSL certificate from the Zanata server.
Default false.
"""
r = self.rest_service.query(
'/rest/projects/p/%s/iterations/i/%s/config'
% (self.project, self.version), verify=verify)
project_config = r.content
p = etree.XMLParser(remove_blank_text=True)
try:
xml = etree.parse(BytesIO(project_config), p)
except etree.ParseError:
raise ValueError('Error parsing xml output')
return xml
def _add_configuration(self, xml):
"""
Insert additional configuration
Add locale mapping rules to the base zanata.xml retrieved from
the server.
Args:
xml (etree): zanata.xml file contents
"""
root = xml.getroot()
s = etree.SubElement(root, 'src-dir')
s.text = self.srcdir
t = etree.SubElement(root, 'trans-dir')
t.text = self.txdir
rules = etree.SubElement(root, 'rules')
for rule in self.rules:
new_rule = etree.SubElement(rules, 'rule')
new_rule.attrib['pattern'] = rule['pattern']
new_rule.text = rule['rule']
if self.excludes:
excludes = etree.SubElement(root, 'excludes')
excludes.text = self.excludes
tag_prefix = self._get_tag_prefix(root)
# Work around https://bugzilla.redhat.com/show_bug.cgi?id=1219624
# by removing port number in URL if it's there
url = root.find('%surl' % tag_prefix)
url.text = re.sub(':443', '', url.text)
def _write_xml(self, xml):
"""Write xml
Write out xml to zanata.xml.
"""
try:
xml.write(self.xmlfile, pretty_print=True)
except IOError:
raise ValueError('Error writing zanata.xml.')