2014-02-12 16:46:54 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# (C) 2014 mhrusecky@suse.cz, openSUSE.org
|
|
|
|
# (C) 2014 tchvatal@suse.cz, openSUSE.org
|
|
|
|
# Distribute under GPLv2 or GPLv3
|
|
|
|
|
|
|
|
import logging
|
2014-02-12 17:48:18 +01:00
|
|
|
from xml.etree import cElementTree as ET
|
2014-02-12 16:46:54 +01:00
|
|
|
|
|
|
|
import yaml
|
|
|
|
|
2014-02-12 17:48:18 +01:00
|
|
|
from osc import oscerr
|
|
|
|
from osc.core import delete_package
|
|
|
|
from osc.core import get_request
|
|
|
|
from osc.core import make_meta_url
|
|
|
|
from osc.core import makeurl
|
|
|
|
from osc.core import metafile
|
|
|
|
from osc.core import http_GET
|
|
|
|
from osc.core import http_POST
|
|
|
|
from osc.core import http_PUT
|
|
|
|
from osc.core import link_pac
|
2014-02-12 16:46:54 +01:00
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
|
2014-02-12 17:58:19 +01:00
|
|
|
class StagingAPI(object):
|
2014-02-12 16:46:54 +01:00
|
|
|
"""
|
|
|
|
Class containing various api calls to work with staging projects.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, apiurl):
|
|
|
|
"""
|
2014-02-12 17:48:18 +01:00
|
|
|
Initialize instance variables
|
2014-02-12 16:46:54 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
self.apiurl = apiurl
|
2014-02-12 17:48:18 +01:00
|
|
|
self.rings = ['openSUSE:Factory:Rings:0-Bootstrap',
|
|
|
|
'openSUSE:Factory:Rings:1-MinimalX']
|
2014-02-12 16:46:54 +01:00
|
|
|
self.ring_packages = self._generate_ring_packages()
|
|
|
|
|
|
|
|
|
|
|
|
def _generate_ring_packages(self):
|
|
|
|
"""
|
|
|
|
Generate dictionary with names of the rings
|
|
|
|
:return dictionary with ring names
|
|
|
|
"""
|
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
ret = {}
|
2014-02-12 16:46:54 +01:00
|
|
|
|
|
|
|
for prj in self.rings:
|
|
|
|
url = makeurl(self.apiurl, ['source', prj])
|
|
|
|
root = http_GET(url)
|
|
|
|
for entry in ET.parse(root).getroot().findall('entry'):
|
|
|
|
ret[entry.attrib['name']] = prj
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def get_package_information(self, project, pkgname):
|
|
|
|
"""
|
|
|
|
Get the revision packagename and source project to copy from
|
|
|
|
based on content provided
|
|
|
|
:param project: the project we are having the package in
|
|
|
|
:param pkgname: name of the package we want to identify
|
|
|
|
:return dict ( project, package, revision, md5sum )
|
|
|
|
"""
|
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
package_info = {}
|
2014-02-12 16:46:54 +01:00
|
|
|
|
|
|
|
url = makeurl(self.apiurl, ['source', project, pkgname])
|
|
|
|
content = http_GET(url)
|
|
|
|
root = ET.parse(content).getroot().find('linkinfo')
|
|
|
|
package_info['srcmd5'] = root.attrib['srcmd5']
|
|
|
|
package_info['rev'] = root.attrib['rev']
|
|
|
|
package_info['project'] = root.attrib['project']
|
|
|
|
package_info['package'] = root.attrib['package']
|
|
|
|
|
|
|
|
return package_info
|
|
|
|
|
|
|
|
|
|
|
|
def move_between_project(self, source_project, package, destination_project):
|
|
|
|
"""
|
|
|
|
Move selected package from one staging to another
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Get the relevant information from source
|
|
|
|
package_info = self.get_package_information('source_project', 'package')
|
|
|
|
|
|
|
|
# Copy the package
|
|
|
|
#FIXME: add the data from orginal project yaml to the destination one
|
2014-02-12 18:19:46 +01:00
|
|
|
link_pac(package_info['project'],
|
|
|
|
package_info['package'],
|
|
|
|
destination_project,
|
|
|
|
package,
|
|
|
|
force=True,
|
|
|
|
rev=package_info['srcmd5'])
|
2014-02-12 16:46:54 +01:00
|
|
|
|
|
|
|
# Delete the first location
|
|
|
|
message = 'moved to {0}'.format(destination_project)
|
|
|
|
delete_package(self.apiurl, source_project, package, msg=message)
|
|
|
|
#FIXME: delete the data from YAML
|
|
|
|
|
|
|
|
|
|
|
|
def get_staging_projects(self):
|
|
|
|
"""
|
|
|
|
Get all current running staging projects
|
|
|
|
:return list of known staging projects
|
|
|
|
"""
|
|
|
|
|
|
|
|
projects = []
|
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
url = makeurl(self.apiurl, ['search', 'project',
|
|
|
|
'id?match=starts-with(@name,\'openSUSE:Factory:Staging:\')'])
|
2014-02-12 16:46:54 +01:00
|
|
|
projxml = http_GET(url)
|
|
|
|
root = ET.parse(projxml).getroot()
|
|
|
|
for val in root.findall('project'):
|
|
|
|
projects.append(val.get('name'))
|
|
|
|
return projects
|
|
|
|
|
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
def staging_change_review_state(self, request_id, newstate, message):
|
2014-02-12 16:46:54 +01:00
|
|
|
"""
|
|
|
|
Change review state of the staging request
|
2014-02-12 18:19:46 +01:00
|
|
|
:param request_id: id of the request
|
2014-02-12 16:46:54 +01:00
|
|
|
:param newstate: state of the new request
|
|
|
|
:param message: message for the review
|
|
|
|
"""
|
|
|
|
""" taken from osc/osc/core.py, improved:
|
|
|
|
- verbose option added,
|
|
|
|
- empty by_user=& removed.
|
|
|
|
- numeric id can be int().
|
|
|
|
"""
|
2014-02-12 18:19:46 +01:00
|
|
|
query = {
|
|
|
|
'cmd': 'changereviewstate',
|
|
|
|
'newstate': newstate,
|
|
|
|
'by_group': 'factory-staging',
|
|
|
|
'comment': message
|
|
|
|
}
|
2014-02-12 16:46:54 +01:00
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
url = makeurl(self.apiurl, ['request', str(request_id)], query=query)
|
2014-02-12 17:48:18 +01:00
|
|
|
http_POST(url, data=message)
|
2014-02-12 16:46:54 +01:00
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
|
2014-02-12 16:46:54 +01:00
|
|
|
def accept_non_ring_request(self, request):
|
|
|
|
"""
|
2014-02-12 18:19:46 +01:00
|
|
|
Accept review of requests that are not yet in any ring so we
|
|
|
|
don't delay their testing.
|
2014-02-12 16:46:54 +01:00
|
|
|
:param request: request to check
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Consolidate all data from request
|
|
|
|
request_id = int(request.get('id'))
|
|
|
|
action = request.findall('action')
|
|
|
|
if not action:
|
|
|
|
raise oscerr.WrongArgs('Request {0} has no action'.format(request_id))
|
|
|
|
# we care only about first action
|
|
|
|
action = action[0]
|
|
|
|
|
|
|
|
# Where are we targeting the package
|
|
|
|
target_project = action.find('target').get('project')
|
|
|
|
target_package = action.find('target').get('package')
|
|
|
|
|
|
|
|
# If the values are empty it is no error
|
|
|
|
if not target_project or not target_package:
|
2014-02-12 18:19:46 +01:00
|
|
|
logging.info('no target/package in request {0}, action {1}; '.format(request_id, action))
|
2014-02-12 16:46:54 +01:00
|
|
|
|
|
|
|
# Verify the package ring
|
|
|
|
ring = self.ring_packages.get(target_package, None)
|
2014-02-12 18:19:46 +01:00
|
|
|
if not ring:
|
2014-02-12 16:46:54 +01:00
|
|
|
# accept the request here
|
|
|
|
message = "No need for staging, not in tested ring project."
|
|
|
|
self.staging_change_review_state(request_id, 'accepted', message)
|
|
|
|
|
|
|
|
|
|
|
|
def get_open_requests(self):
|
|
|
|
"""
|
|
|
|
Get all requests with open review for staging project
|
|
|
|
that are not yet included in any staging project
|
|
|
|
:return list of pending open review requests
|
|
|
|
"""
|
|
|
|
|
|
|
|
requests = []
|
|
|
|
|
|
|
|
# xpath query, using the -m, -r, -s options
|
|
|
|
where = "@by_group='factory-staging'+and+@state='new'"
|
|
|
|
|
|
|
|
url = makeurl(self.apiurl, ['search','request'], "match=state/@name='review'+and+review["+where+"]")
|
|
|
|
f = http_GET(url)
|
|
|
|
root = ET.parse(f).getroot()
|
|
|
|
|
|
|
|
for rq in root.findall('request'):
|
|
|
|
requests.append(rq)
|
|
|
|
|
|
|
|
return requests
|
|
|
|
|
2014-02-12 18:19:46 +01:00
|
|
|
|
2014-02-12 16:46:54 +01:00
|
|
|
def dispatch_open_requests(self):
|
|
|
|
"""
|
|
|
|
Verify all requests and dispatch them to staging projects or approve them
|
|
|
|
"""
|
|
|
|
|
|
|
|
# get all current pending requests
|
|
|
|
requests = self.get_open_requests()
|
|
|
|
# check if we can reduce it down by accepting some
|
|
|
|
for rq in requests:
|
|
|
|
self.accept_non_ring_request(rq)
|
|
|
|
|
|
|
|
# FIXME: dispatch to various staging projects automatically
|
|
|
|
|
|
|
|
|
|
|
|
def get_prj_pseudometa(self, project):
|
|
|
|
"""
|
|
|
|
Gets project data from YAML in project description
|
|
|
|
:param project: project to read data from
|
|
|
|
:return structured object with metadata
|
|
|
|
"""
|
|
|
|
|
|
|
|
url = make_meta_url('prj', project, self.apiurl)
|
|
|
|
data = http_GET(url).readlines()
|
|
|
|
root = ET.fromstring(''.join(data))
|
|
|
|
description = root.find('description')
|
|
|
|
# If YAML parsing fails, load default
|
|
|
|
# FIXME: Better handling of errors
|
|
|
|
# * broken description
|
|
|
|
# * directly linked packages
|
|
|
|
# * removed linked packages
|
|
|
|
try:
|
|
|
|
data = yaml.load(description.text)
|
|
|
|
data['requests']
|
|
|
|
except:
|
|
|
|
data = yaml.load('requests: []')
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
def set_prj_pseudometa(self, project, meta):
|
|
|
|
"""
|
|
|
|
Sets project description to the YAML of the provided object
|
|
|
|
:param project: project to save into
|
|
|
|
:param meta: data to save
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Get current metadata
|
|
|
|
url = make_meta_url('prj', project, self.apiurl)
|
|
|
|
data = http_GET(url).readlines()
|
|
|
|
root = ET.fromstring(''.join(data))
|
|
|
|
# Find description
|
|
|
|
description = root.find('description')
|
|
|
|
# Replace it with yaml
|
|
|
|
description.text = yaml.dump(meta)
|
|
|
|
# Find title
|
|
|
|
title = root.find('title')
|
|
|
|
# Put something nice into title as well
|
|
|
|
new_title = []
|
|
|
|
for request in meta['requests']:
|
|
|
|
new_title.append(request['package'])
|
|
|
|
title.text = ', '.join(new_title)
|
|
|
|
# Write XML back
|
|
|
|
url = make_meta_url('prj',project, self.apiurl, force=True)
|
|
|
|
f = metafile(url, ET.tostring(root))
|
|
|
|
http_PUT(f.url, file=f.filename)
|
|
|
|
|
|
|
|
|
|
|
|
def _add_rq_to_prj_pseudometa(self, project, request_id, package):
|
|
|
|
"""
|
|
|
|
Records request as part of the project within metadata
|
|
|
|
:param project: project to record into
|
|
|
|
:param request_id: request id to record
|
|
|
|
:param package: package the request is about
|
|
|
|
"""
|
|
|
|
|
|
|
|
data = self.get_prj_pseudometa(project)
|
|
|
|
append = True
|
|
|
|
for request in data['requests']:
|
|
|
|
if request['package'] == package:
|
|
|
|
request['id'] = request_id
|
|
|
|
append = False
|
|
|
|
if append:
|
|
|
|
data['requests'].append( { 'id': request_id, 'package': package} )
|
|
|
|
self.set_prj_pseudometa(project, data)
|
|
|
|
# FIXME Add sr to group request as well
|
|
|
|
|
|
|
|
|
|
|
|
def sr_to_prj(self, request_id, project):
|
|
|
|
"""
|
|
|
|
Links sources from request to project
|
|
|
|
:param request_id: request to link
|
|
|
|
:param project: project to link into
|
|
|
|
"""
|
|
|
|
|
|
|
|
# read info from sr
|
|
|
|
req = get_request(self.apiurl, request_id)
|
|
|
|
if not req:
|
|
|
|
raise oscerr.WrongArgs("Request {0} not found".format(request_id))
|
|
|
|
act = req.get_actions("submit")
|
|
|
|
if not act:
|
|
|
|
raise oscerr.WrongArgs("Request {0} is not a submit request".format(request_id))
|
|
|
|
act=act[0]
|
|
|
|
|
|
|
|
src_prj = act.src_project
|
|
|
|
src_rev = act.src_rev
|
|
|
|
src_pkg = act.src_package
|
|
|
|
tar_pkg = act.tgt_package
|
|
|
|
|
|
|
|
# expand the revision to a md5
|
|
|
|
url = makeurl(self.apiurl, ['source', src_prj, src_pkg], { 'rev': src_rev, 'expand': 1 })
|
|
|
|
f = http_GET(url)
|
|
|
|
root = ET.parse(f).getroot()
|
|
|
|
src_rev = root.attrib['srcmd5']
|
|
|
|
src_vrev = root.attrib['vrev']
|
|
|
|
#print "osc linkpac -r %s %s/%s %s/%s" % (src_rev, src_prj, src_pkg, project, tar_pkg)
|
|
|
|
|
|
|
|
# link stuff
|
|
|
|
self._add_rq_to_prj_pseudometa(project, int(request_id), src_pkg)
|
|
|
|
link_pac(src_prj, src_pkg, project, tar_pkg, force=True, rev=src_rev, vrev=src_vrev)
|
|
|
|
# FIXME If there are links in parent project, make sure that current
|