blob: a235368bb772eb3120b4799b29d9b2da3ee9976e [file] [log] [blame]
#!/usr/bin/env python3
# pylint: disable=invalid-name
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
#
""" Process patch file attachments from JIRA using a query """
#
# we actually want native encoding so tell pylint to be quiet
#
# pylint: disable=unspecified-encoding
from argparse import ArgumentParser
from tempfile import NamedTemporaryFile
from xml.etree import ElementTree
import os
import pathlib
import re
import sys
import requests
def http_get(resource, ignore_error=False, username=None, password=None):
""" get the contents of a URL """
try:
if username and password:
response = requests.get(resource, auth=(username, password), timeout=10)
else:
response = requests.get(resource, timeout=10)
response.raise_for_status()
except requests.exceptions.HTTPError as http_err:
errstr = str(http_err)
print(
f'%{resource} returns HTTP error %{response.status_code}: %{errstr}\n'
)
if ignore_error:
return ''
print('Aborting.')
sys.exit(1)
return response.text
def parse_jira_data(filename):
""" returns a map of (project, issue) => attachment id """
tree = ElementTree.parse(filename)
root = tree.getroot()
jirapattern = re.compile(r'([A-Z]+)\-([0-9]+)')
result = {}
for item in root.findall('./channel/item'):
jirakey = item.find('key')
if jirakey is None:
continue
jiraissue = jirakey.text
matcher = jirapattern.match(jiraissue)
if not matcher:
continue
jiraissue = (matcher.group(1), matcher.group(2))
attachmentids = []
for jiraattachment in item.findall('./attachments/attachment'):
attachmentid = jiraattachment.get('id')
try:
attachmentids.append(int(attachmentid))
except ValueError:
pass
if attachmentids:
attachmentids.sort()
result[jiraissue] = attachmentids[-1]
return result
def main(): #pylint: disable=too-many-branches, too-many-statements, too-many-locals
""" main program """
parser = ArgumentParser(prog='jenkins-admin')
if os.getenv('JENKINS_URL'):
parser.set_defaults(jenkinsurl=os.getenv('JENKINS_URL'))
if os.getenv('JOB_NAME'):
parser.set_defaults(jenkinsJobName=os.getenv('JOB_NAME'))
else:
parser.set_defaults(jenkinsJobName='PreCommit-Admin')
parser.set_defaults(jenkinsJobTemplate='PreCommit-{project}')
parser.add_argument('--initialize',
action='store_true',
dest='jenkinsInit',
help='Start a new patch_tested.txt file')
parser.add_argument('--jenkins-jobname',
type=str,
dest='jenkinsJobName',
help='PreCommit-Admin JobName',
metavar='JOB_NAME')
parser.add_argument('--jenkins-project-template',
type=str,
dest='jenkinsJobTemplate',
help='Template for project jobs',
metavar='TEMPLATE')
parser.add_argument('--jenkins-token',
type=str,
dest='jenkinsToken',
help='Jenkins Token',
metavar='TOKEN')
parser.add_argument('--jenkins-url',
type=str,
dest='jenkinsurl',
help='Jenkins base URL',
metavar='URL')
parser.add_argument(
'--jenkins-url-override',
type=str,
dest='jenkinsurloverrides',
action='append',
help='Project specific Jenkins base URL',
metavar='PROJECT=URL',
)
parser.add_argument('--jira-filter',
type=str,
dest='jiraFilter',
help='JIRA filter URL',
metavar='URL')
parser.add_argument('--jira-user',
type=str,
dest='jiraUser',
help='JIRA username')
parser.add_argument('--jira-password',
type=str,
dest='jiraPassword',
help='JIRA password')
parser.add_argument('--live',
dest='live',
action='store_true',
help='Submit Job to jenkins')
parser.add_argument('--max-history',
dest='history',
type=int,
help='Maximum history to store',
default=5000)
parser.add_argument(
'-V',
'--version',
dest='release_version',
action='store_true',
default=False,
help="display version information for jenkins-admin and exit.")
options = parser.parse_args()
# Handle the version string right away and exit
if options.release_version:
execname = pathlib.Path(__file__)
binversion = execname.joinpath("..", "..", "VERSION").resolve()
mvnversion = execname.joinpath("..", "..", "..", "..", "..", ".mvn",
"maven.config").resolve()
if binversion.exists():
with open(binversion, encoding='utf-8') as ver_file:
print(ver_file.read().strip())
elif mvnversion.exists():
with open(mvnversion, encoding='utf-8') as ver_file:
print(ver_file.read().split('=')[1].strip())
sys.exit(0)
token_frag = ''
if options.jenkinsToken:
token_frag = f'token={options.jenkinsToken}'
else:
token_frag = 'token={project}-token'
if not options.jiraFilter:
parser.error('ERROR: --jira-filter is a required argument.')
if not options.jenkinsurl:
parser.error(
'ERROR: --jenkins-url or the JENKINS_URL environment variable is required.'
)
if options.history < 0:
parser.error('ERROR: --max-history must be 0 or a positive integer.')
jenkinsurloverrides = {}
if options.jenkinsurloverrides:
for override in options.jenkinsurloverrides:
if '=' not in override:
parser.error('Invalid Jenkins Url Override: ' + override)
(project, url) = override.split('=', 1)
jenkinsurloverrides[project.upper()] = url
tempfile = NamedTemporaryFile(delete=False) # pylint: disable=consider-using-with
try:
jobloghistory = None
if not options.jenkinsInit:
lsb = 'lastSuccessfulBuild/artifact/patch_tested.txt'
lcb = 'lastCompletedBuild/artifact/patch_tested.txt'
jobloghistory = http_get(
f'{options.jenkinsurl}/job/{options.jenkinsJobName}/{lsb}',
True)
# if we don't have a successful build available try the last build
if not jobloghistory:
jobloghistory = http_get(
f'{options.jenkinsurl}/job/{options.jenkinsJobName}/{lcb}')
jobloghistory = jobloghistory.strip().split('\n')
if 'TESTED ISSUES' not in jobloghistory[0]:
print(
'Downloaded patch_tested.txt control file may be corrupted. Failing.'
)
sys.exit(1)
# we are either going to write a new one or rewrite the old one
joblog = open('patch_tested.txt', 'w+') # pylint: disable=consider-using-with
if jobloghistory:
if len(jobloghistory) > options.history:
jobloghistory = [jobloghistory[0]] \
+ jobloghistory[len(jobloghistory) \
- options.history:]
for jobhistoryrecord in jobloghistory:
joblog.write(jobhistoryrecord + '\n')
else:
joblog.write('TESTED ISSUES\n')
joblog.flush()
rssdata = http_get(options.jiraFilter, False, options.jiraUser,
options.jiraPassword)
tempfile.write(rssdata.encode('utf-8'))
tempfile.flush()
for (key, attachment) in list(parse_jira_data(tempfile.name).items()):
(project, issue) = key
if jenkinsurloverrides.get(project):
url = jenkinsurloverrides[project]
else:
url = options.jenkinsurl
jenkinsurltemplate = url + '/job/' \
+ options.jenkinsJobTemplate \
+ '/buildWithParameters?' + token_frag \
+ '&ISSUE_NUM={issue}&ATTACHMENT_ID={attachment}'
url_args = {
'project': project,
'issue': issue,
'attachment': attachment,
}
jenkinsurl = jenkinsurltemplate.format(**url_args)
# submit job
jobname = f'{project}-{issue},{attachment}'
if not jobloghistory or jobname not in jobloghistory:
print(jobname + ' has not been processed, submitting')
joblog.write(jobname + '\n')
joblog.flush()
if options.live:
http_get(jenkinsurl, True)
else:
print('GET ' + jenkinsurl)
else:
print(jobname + ' has been processed, ignoring')
joblog.close()
finally:
if options.live:
os.remove(tempfile.name)
else:
print('JIRA Data is located: ' + tempfile.name)
if __name__ == '__main__':
main()