Previous implementation was broken as there ware still some leftovers of newdata variable mixed with new data variable approach. Made it simple and working.
325 lines
11 KiB
Python
325 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# (C) 2014 mhrusecky@suse.cz, openSUSE.org
|
|
# (C) 2014 tchvatal@suse.cz, openSUSE.org
|
|
# Distribute under GPLv2 or GPLv3
|
|
|
|
import logging
|
|
from xml.etree import cElementTree as ET
|
|
|
|
import yaml
|
|
|
|
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
|
|
|
|
|
|
class StagingAPI(object):
|
|
"""
|
|
Class containing various api calls to work with staging projects.
|
|
"""
|
|
|
|
def __init__(self, apiurl):
|
|
"""
|
|
Initialize instance variables
|
|
"""
|
|
|
|
self.apiurl = apiurl
|
|
self.rings = ['openSUSE:Factory:Rings:0-Bootstrap',
|
|
'openSUSE:Factory:Rings:1-MinimalX']
|
|
self.ring_packages = self._generate_ring_packages()
|
|
|
|
|
|
def _generate_ring_packages(self):
|
|
"""
|
|
Generate dictionary with names of the rings
|
|
:return dictionary with ring names
|
|
"""
|
|
|
|
ret = {}
|
|
|
|
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 )
|
|
"""
|
|
|
|
package_info = {}
|
|
|
|
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
|
|
link_pac(package_info['project'],
|
|
package_info['package'],
|
|
destination_project,
|
|
package,
|
|
force=True,
|
|
rev=package_info['srcmd5'])
|
|
|
|
# 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 = []
|
|
|
|
url = makeurl(self.apiurl, ['search', 'project',
|
|
'id?match=starts-with(@name,\'openSUSE:Factory:Staging:\')'])
|
|
projxml = http_GET(url)
|
|
root = ET.parse(projxml).getroot()
|
|
for val in root.findall('project'):
|
|
projects.append(val.get('name'))
|
|
return projects
|
|
|
|
|
|
def staging_change_review_state(self, request_id, newstate, message):
|
|
"""
|
|
Change review state of the staging request
|
|
:param request_id: id of the request
|
|
: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().
|
|
"""
|
|
query = {
|
|
'cmd': 'changereviewstate',
|
|
'newstate': newstate,
|
|
'by_group': 'factory-staging',
|
|
'comment': message
|
|
}
|
|
|
|
url = makeurl(self.apiurl, ['request', str(request_id)], query=query)
|
|
http_POST(url, data=message)
|
|
|
|
|
|
def accept_non_ring_request(self, request):
|
|
"""
|
|
Accept review of requests that are not yet in any ring so we
|
|
don't delay their testing.
|
|
: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:
|
|
logging.info('no target/package in request {0}, action {1}; '.format(request_id, action))
|
|
|
|
# Verify the package ring
|
|
ring = self.ring_packages.get(target_package, None)
|
|
if not ring:
|
|
# 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
|
|
|
|
|
|
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 _remove_rq_from_prj_pseudometa(self, project, package):
|
|
"""
|
|
Delete request from the project pseudometa
|
|
:param project: project to remove from
|
|
:param package: package we want to remove from meta
|
|
"""
|
|
|
|
data = self.get_prj_pseudometa(project)
|
|
data['requests'] = filter(lambda x: x['package'] != package, data['requests'])
|
|
self.set_prj_pseudometa(project, newdata)
|
|
# 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
|