Hello community,

here is the log from the commit of package openSUSE-release-tools for 
openSUSE:Factory checked in at 2018-11-10 17:01:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openSUSE-release-tools (Old)
 and      /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openSUSE-release-tools"

Sat Nov 10 17:01:41 2018 rev:143 rq:647511 version:20181109.3aae284

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/openSUSE-release-tools/openSUSE-release-tools.changes
    2018-11-09 07:56:33.207564789 +0100
+++ 
/work/SRC/openSUSE:Factory/.openSUSE-release-tools.new/openSUSE-release-tools.changes
       2018-11-10 17:02:34.779518898 +0100
@@ -1,0 +2,35 @@
+Fri Nov 09 07:56:04 UTC 2018 - opensuse-releaset...@opensuse.org
+
+- Update to version 20181109.3aae284:
+  * adi: Fix format for untracked requests
+
+-------------------------------------------------------------------
+Thu Nov 08 22:33:55 UTC 2018 - opensuse-releaset...@opensuse.org
+
+- Update to version 20181108.f4371f4:
+  * dist/spec: provide obs-operator subpackage.
+  * userscript/staging-move-drag-n-drop: rework to utilize OBS Operator server.
+  * obs_operator: provide initial version of server.
+  * userscript/staging-move-drag-n-drop: provide option to click to start.
+  * userscript/staging-move-drag-n-drop: move browser compatability check to 
init.
+
+-------------------------------------------------------------------
+Thu Nov 08 17:21:30 UTC 2018 - opensuse-releaset...@opensuse.org
+
+- Update to version 20181108.09fd2ba:
+  * Remove map_ring_package_to_subject - it always return project
+  * No longer build disable on selecting non-ring packages to letter prjs
+
+-------------------------------------------------------------------
+Thu Nov 08 17:08:51 UTC 2018 - opensuse-releaset...@opensuse.org
+
+- Update to version 20181108.e8daf87:
+  * new bugowner tool
+
+-------------------------------------------------------------------
+Thu Nov 08 16:58:35 UTC 2018 - opensuse-releaset...@opensuse.org
+
+- Update to version 20181108.151a4fe:
+  * [ARM] Skip build number comparison
+
+-------------------------------------------------------------------

Old:
----
  openSUSE-release-tools-20181108.4293b6b.obscpio

New:
----
  openSUSE-release-tools-20181109.3aae284.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ openSUSE-release-tools.spec ++++++
--- /var/tmp/diff_new_pack.CHPZQc/_old  2018-11-10 17:02:35.551517956 +0100
+++ /var/tmp/diff_new_pack.CHPZQc/_new  2018-11-10 17:02:35.551517956 +0100
@@ -20,7 +20,7 @@
 %define source_dir openSUSE-release-tools
 %define announcer_filename factory-package-news
 Name:           openSUSE-release-tools
-Version:        20181108.4293b6b
+Version:        20181109.3aae284
 Release:        0
 Summary:        Tools to aid in staging and release work for openSUSE/SUSE
 License:        GPL-2.0-or-later AND MIT
@@ -199,6 +199,17 @@
 Requires:       perl-XML-Simple
 Requires(pre):  shadow
 
+%package obs-operator
+Summary:        Server used to perform staging operations
+Group:          Development/Tools/Other
+BuildArch:      noarch
+Requires:       osc-plugin-staging = %{version}
+Requires(pre):  shadow
+
+%description obs-operator
+Server used to perform staging operations as a service instead of requiring
+the osc staging plugin to be utilized directly.
+
 %description repo-checker
 Repository checker service that inspects built RPMs from stagings.
 
@@ -367,6 +378,17 @@
   /usr/bin/systemctl try-restart --no-block grafana-server
 fi
 
+%pre obs-operator
+getent passwd osrt-obs-operator > /dev/null || \
+  useradd -r -m -s /sbin/nologin -c "user for 
openSUSE-release-tools-obs-operator" osrt-obs-operator
+exit 0
+
+%postun obs-operator
+%systemd_postun
+if [ -x /usr/bin/systemctl ] && /usr/bin/systemctl is-enabled 
osrt-obs-operator ; then
+  /usr/bin/systemctl try-restart --no-block osrt-obs-operator
+fi
+
 %pre repo-checker
 getent passwd osrt-repo-checker > /dev/null || \
   useradd -r -m -s /sbin/nologin -c "user for 
openSUSE-release-tools-repo-checker" osrt-repo-checker
@@ -413,6 +435,7 @@
 %doc README.md
 %{_bindir}/osrt-biarchtool
 %{_bindir}/osrt-bs_mirrorfull
+%{_bindir}/osrt-bugowner
 %{_bindir}/osrt-build-fail-reminder
 %{_bindir}/osrt-checknewer
 %{_bindir}/osrt-check_source_in_factory
@@ -544,6 +567,10 @@
 %{_unitdir}/osrt-metrics-access.service
 %{_unitdir}/osrt-metrics-access.timer
 
+%files obs-operator
+%{_bindir}/osrt-obs_operator
+%{_unitdir}/osrt-obs-operator.service
+
 %files repo-checker
 %defattr(-,root,root,-)
 %{_bindir}/osrt-repo_checker

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.CHPZQc/_old  2018-11-10 17:02:35.591517907 +0100
+++ /var/tmp/diff_new_pack.CHPZQc/_new  2018-11-10 17:02:35.591517907 +0100
@@ -1,6 +1,6 @@
 <servicedata>
   <service name="tar_scm">
     <param 
name="url">https://github.com/openSUSE/openSUSE-release-tools.git</param>
-    <param 
name="changesrevision">824fab13fdeaac4f14ed10409c8ff7844eb3e07c</param>
+    <param 
name="changesrevision">3aae284ff2c32b4c4f0ed528aed4355b312a3a2a</param>
   </service>
 </servicedata>

++++++ openSUSE-release-tools-20181108.4293b6b.obscpio -> 
openSUSE-release-tools-20181109.3aae284.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openSUSE-release-tools-20181108.4293b6b/bugowner.py 
new/openSUSE-release-tools-20181109.3aae284/bugowner.py
--- old/openSUSE-release-tools-20181108.4293b6b/bugowner.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20181109.3aae284/bugowner.py     2018-11-09 
08:50:52.000000000 +0100
@@ -0,0 +1,253 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018 SUSE LLC
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from ConfigParser import ConfigParser
+from xdg.BaseDirectory import load_first_config
+from lxml import etree as ET
+from collections import namedtuple
+
+import sys
+import cmdln
+import logging
+import urllib2
+import osc.core
+import yaml
+import os
+import ldap
+
+import ToolBase
+
+logger = logging.getLogger()
+
+FACTORY = "openSUSE:Factory"
+
+Owner = namedtuple('Owner', ('kind', 'name'))
+Person = namedtuple('Person', ('login', 'email', 'realname'))
+
+class BugownerTool(ToolBase.ToolBase):
+
+    def __init__(self):
+        ToolBase.ToolBase.__init__(self)
+        self.project = None
+        self.reference_projects = None
+        self.package_metas = dict()
+        self.release_managers = None
+        self.persons = {}
+
+    def resolve_person(self, name):
+        if name in self.persons:
+            return self.persons[name]
+
+        url = self.makeurl(['person', name])
+        root = ET.fromstring(self.cached_GET(url))
+
+        person = Person(*[ root.find('./{}'.format(field)).text for field in 
Person._fields ])
+        self.persons[name] = person
+
+        return person
+
+    def find_packages_with_missing_bugowner(self):
+        url = self.makeurl(['search', 'missing_owner'], { 'project': 
self.project, 'filter': 'bugowner'})
+        root = ET.fromstring(self.cached_GET(url))
+
+        missing = []
+        for node in root.findall('missing_owner'):
+            missing.append(node.get('package'))
+
+        return missing
+
+    def find_owner(self, package, role = 'bugowner'):
+        # XXX: not actually looking for package but binary
+        # https://github.com/openSUSE/open-build-service/issues/4359
+        url = self.makeurl(['search', 'owner'], { 'binary': package})
+        root = ET.fromstring(self.cached_GET(url))
+        ret = []
+        for node in root.findall('./owner/person[@role="{}"]'.format(role)):
+            ret.append(Owner('person', node.get('name')))
+        for node in root.findall('./owner/group[@role="{}"]'.format(role)):
+            ret.append(Owner('group', node.get('name')))
+
+        return ret
+
+    def add_bugowner(self, package, owner):
+        url = self.makeurl(['source', self.project, package, '_meta'])
+        root = ET.fromstring(self.cached_GET(url))
+        idname = 'userid' if owner.kind == 'person' else 'groupid'
+        # XXX: can't use 'and' here to filter for bugowner too
+        exists = root.findall('./{}[@{}="{}"]'.format(owner.kind, idname, 
owner.name))
+        for node in exists:
+            if node.get('role') == 'bugowner':
+                logger.debug("%s/%s already has %s %s", self.project, package, 
owner.kind, owner.name)
+            return
+
+        node = ET.SubElement(root, owner.kind)
+        node.set(idname, owner.name)
+        node.set('role', 'bugowner')
+
+        data = ET.tostring(root)
+        logger.debug(data)
+        self.http_PUT(url, data=data)
+
+    def package_get_last_committer(self, package):
+        project = self.project
+        srcrev = osc.core.get_source_rev(self.apiurl, project, package)
+
+        if 'requestid' in srcrev:
+            r = osc.core.get_request(self.apiurl, srcrev['requestid'])
+            user = r.statehistory[0].who
+        else:
+            user = srcrev['user']
+
+        if self.is_release_manager(user):
+            logging.debug("%s was last touched by %s, ignored."%(package, 
user))
+            return None
+
+        return [ Owner('person', user) ]
+
+    def is_release_manager(self, name):
+        if self.release_managers is None:
+            self.release_managers = set()
+            url = self.makeurl(['group', 'sle-release-managers'])
+            root = ET.fromstring(self.cached_GET(url))
+            for node in root.findall('.//person[@userid]'):
+                self.release_managers.add(node.get('userid'))
+            # XXX: hardcoded bot
+            self.release_managers.add('leaper')
+            logger.debug("release managers %s", self.release_managers)
+
+        return name in self.release_managers
+
+
+class CommandLineInterface(ToolBase.CommandLineInterface):
+
+    def __init__(self, *args, **kwargs):
+        ToolBase.CommandLineInterface.__init__(self, args, kwargs)
+
+    def get_optparser(self):
+        parser = ToolBase.CommandLineInterface.get_optparser(self)
+        parser.add_option('-p', '--project', dest='project', metavar='PROJECT',
+                        help='project to process (default: %s)' % FACTORY,
+                        default = FACTORY)
+        parser.add_option('--reference-project', metavar='PROJECT',
+                action='append', help='reference project')
+        return parser
+
+    def setup_tool(self):
+        tool = BugownerTool()
+        tool.project = self.options.project
+        return tool
+
+    def do_missing(self, subcmd, opts):
+        """${cmd_name}: find packages with missing bugowner
+
+        Beware of https://github.com/openSUSE/open-build-service/issues/4172
+        when using this with SLE service packs or update projects
+
+        ${cmd_usage}
+        ${cmd_option_list}
+        """
+
+        pkgs = self.tool.find_packages_with_missing_bugowner()
+        for p in pkgs:
+            print(p)
+
+    @cmdln.option('-r', '--role', metavar='ROLE', help='role to look up', 
default="bugowner")
+    @cmdln.option('-s', '--set', action='store_true', help='set bugowner in 
specified project')
+    @cmdln.option('--request', action='store_true', help='print osc request 
lines')
+    @cmdln.option('--employee', action='store_true', help='only filter 
employees')
+    def do_owner(self, subcmd, opts, *package):
+        """${cmd_name}: find owners of the given pacakge
+
+        ${cmd_usage}
+        ${cmd_option_list}
+        """
+
+        l = ldap.initialize("ldap://pan.suse.de";)
+        l.simple_bind_s()
+
+        for p in package:
+            owners = self.tool.find_owner(p, opts.role)
+            if not owners:
+                logger.info("%s does not have owners", p)
+                continue
+            for o in owners:
+                logger.info("%s -> %s %s", p, o.kind, o.name)
+                if opts.set:
+                    self.tool.add_bugowner(p, o)
+                elif opts.request:
+                    name = o.name
+                    if o.kind == 'group':
+                        name = 'group:' + name
+                    print("osc -A {} reqbs -r bugowner -m 'copy bug owner from 
previous codestream' {} {} {}".format(self.tool.apiurl, self.tool.project, p, 
name))
+                elif opts.employee:
+                    if o.kind != 'person':
+                        logger.debug('%s not a person', o.name)
+                        continue
+                    person = self.tool.resolve_person(o.name)
+                    if person.email.endswith('@suse.com'):
+                        print p, o.name
+                    else:
+                        logger.debug('%s skipped', o.name)
+
+    def do_addbugowner(self, subcmd, opts, package, *persons):
+        """${cmd_name}: add person as bugowner unless already set
+
+        ${cmd_usage}
+        ${cmd_option_list}
+        """
+
+        for p in persons:
+            o = Owner('person', p)
+            logger.info("%s -> %s %s", package, o.kind, o.name)
+            self.tool.add_bugowner(p, o)
+
+    @cmdln.option('--set', action='store_true',
+                  help='request bugowner')
+    @cmdln.option('--request', action='store_true', help='print osc request 
lines')
+    def do_lastsubmitter(self, subcmd, opts, *packages):
+        """${cmd_name}: show last committer for packages
+
+        excludes release managers
+
+        ${cmd_name} PROJECT PACKAGE...
+
+        ${cmd_option_list}
+        """
+
+        for p in packages:
+            owners = self.tool.package_get_last_committer(p)
+            if not owners:
+                logger.info("%s does not have owners", p)
+                continue
+            for o in owners:
+                logger.info("%s -> %s %s", p, o.kind, o.name)
+                if opts.set:
+                    self.tool.add_bugowner(p, o)
+                if opts.request:
+                    name = o.name
+                    if o.kind == 'group':
+                        name = 'group:' + name
+                    print("osc -A {} reqbs -r bugowner -m 'add last submitter 
as bug owner' {} {} {}".format(self.tool.apiurl, self.tool.project, p, name))
+
+if __name__ == "__main__":
+    app = CommandLineInterface()
+    sys.exit(app.main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20181108.4293b6b/dist/package/openSUSE-release-tools.spec
 
new/openSUSE-release-tools-20181109.3aae284/dist/package/openSUSE-release-tools.spec
--- 
old/openSUSE-release-tools-20181108.4293b6b/dist/package/openSUSE-release-tools.spec
        2018-11-08 17:10:04.000000000 +0100
+++ 
new/openSUSE-release-tools-20181109.3aae284/dist/package/openSUSE-release-tools.spec
        2018-11-09 08:50:52.000000000 +0100
@@ -199,6 +199,17 @@
 Requires:       perl-XML-Simple
 Requires(pre):  shadow
 
+%package obs-operator
+Summary:        Server used to perform staging operations
+Group:          Development/Tools/Other
+BuildArch:      noarch
+Requires:       osc-plugin-staging = %{version}
+Requires(pre):  shadow
+
+%description obs-operator
+Server used to perform staging operations as a service instead of requiring
+the osc staging plugin to be utilized directly.
+
 %description repo-checker
 Repository checker service that inspects built RPMs from stagings.
 
@@ -367,6 +378,17 @@
   /usr/bin/systemctl try-restart --no-block grafana-server
 fi
 
+%pre obs-operator
+getent passwd osrt-obs-operator > /dev/null || \
+  useradd -r -m -s /sbin/nologin -c "user for 
openSUSE-release-tools-obs-operator" osrt-obs-operator
+exit 0
+
+%postun obs-operator
+%systemd_postun
+if [ -x /usr/bin/systemctl ] && /usr/bin/systemctl is-enabled 
osrt-obs-operator ; then
+  /usr/bin/systemctl try-restart --no-block osrt-obs-operator
+fi
+
 %pre repo-checker
 getent passwd osrt-repo-checker > /dev/null || \
   useradd -r -m -s /sbin/nologin -c "user for 
openSUSE-release-tools-repo-checker" osrt-repo-checker
@@ -413,6 +435,7 @@
 %doc README.md
 %{_bindir}/osrt-biarchtool
 %{_bindir}/osrt-bs_mirrorfull
+%{_bindir}/osrt-bugowner
 %{_bindir}/osrt-build-fail-reminder
 %{_bindir}/osrt-checknewer
 %{_bindir}/osrt-check_source_in_factory
@@ -544,6 +567,10 @@
 %{_unitdir}/osrt-metrics-access.service
 %{_unitdir}/osrt-metrics-access.timer
 
+%files obs-operator
+%{_bindir}/osrt-obs_operator
+%{_unitdir}/osrt-obs-operator.service
+
 %files repo-checker
 %defattr(-,root,root,-)
 %{_bindir}/osrt-repo_checker
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20181108.4293b6b/obs_operator.py 
new/openSUSE-release-tools-20181109.3aae284/obs_operator.py
--- old/openSUSE-release-tools-20181108.4293b6b/obs_operator.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/openSUSE-release-tools-20181109.3aae284/obs_operator.py 2018-11-09 
08:50:52.000000000 +0100
@@ -0,0 +1,194 @@
+#!/usr/bin/python3
+
+import argparse
+from http.cookies import SimpleCookie
+from http.cookiejar import Cookie, LWPCookieJar
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from socketserver import ThreadingMixIn
+import json
+import tempfile
+import os
+from osclib import common
+import subprocess
+import sys
+import time
+from urllib.parse import urlparse
+
+# Available in python 3.7.
+class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
+    pass
+
+class RequestHandler(BaseHTTPRequestHandler):
+    COOKIE_NAME = 'openSUSE_session' # Both OBS and IBS.
+    POST_ACTIONS = ['select']
+
+    def do_GET(self):
+        if self.path != '/':
+            self.send_response(404)
+            self.end_headers()
+            return
+
+        self.send_response(200)
+        self.send_header('Content-type', 'text/plain')
+        self.end_headers()
+
+        self.write_string('namespace: {}\n'.format(common.NAME))
+        self.write_string('name: {}\n'.format('OBS Operator'))
+        self.write_string('version: {}\n'.format(common.VERSION))
+
+    def do_POST(self):
+        action = self.path.lstrip('/')
+        if action not in self.POST_ACTIONS:
+            self.send_response(404)
+            self.end_headers()
+            return
+
+        data = self.data_parse()
+        user = data.get('user')
+        apiurl = self.apiurl_get()
+        if not data or not user or not apiurl:
+            self.send_response(400)
+            self.end_headers()
+            return
+        if self.debug:
+            print('data: {}'.format(data))
+            print('apiurl: {}'.format(apiurl))
+
+        session = self.session_get()
+        if not session:
+            self.send_response(401)
+            self.end_headers()
+            return
+        if self.debug:
+            print('session: {}'.format(session))
+
+        self.send_response(200)
+        self.send_header('Content-type', 'text/plain')
+        self.send_header('Access-Control-Allow-Credentials', 'true')
+        self.send_header('Access-Control-Allow-Origin', 
self.headers.get('Origin'))
+        self.end_headers()
+
+        with tempfile.NamedTemporaryFile() as cookiejar_file:
+            with tempfile.NamedTemporaryFile() as oscrc_file:
+                self.oscrc_create(oscrc_file, apiurl, cookiejar_file, user)
+                self.cookiejar_create(cookiejar_file, session)
+
+                func = getattr(self, 'handle_{}'.format(action))
+                commands = func(data)
+                for command in commands:
+                    self.write_string('$ {}\n'.format(' '.join(command)))
+                    if not self.execute(oscrc_file, command):
+                        self.write_string('failed')
+                        break
+
+    def data_parse(self):
+        data = self.rfile.read(int(self.headers['Content-Length']))
+        return json.loads(data.decode('utf-8'))
+
+    def apiurl_get(self):
+        if self.apiurl:
+            return self.apiurl
+
+        origin = self.headers.get('Origin')
+        if not origin:
+            return None
+
+        # Strip port if present.
+        domain = urlparse(origin).netloc.split(':', 2)[0]
+        if '.' not in domain:
+            return None
+
+        # Remove first subdomain and replace with api subdomain.
+        domain_parent = '.'.join(domain.split('.')[1:])
+        return 'https://api.{}'.format(domain_parent)
+
+    def session_get(self):
+        if self.session:
+            return self.session
+        else:
+            cookie = self.headers.get('Cookie')
+            if cookie:
+                cookie = SimpleCookie(cookie)
+                if self.COOKIE_NAME in cookie:
+                    return cookie[self.COOKIE_NAME].value
+
+        return None
+
+    def oscrc_create(self, oscrc_file, apiurl, cookiejar_file, user):
+        oscrc_file.write('\n'.join([
+            '[general]',
+            'apiurl = {}'.format(apiurl),
+            'cookiejar = {}'.format(cookiejar_file.name),
+            'staging.color = 0',
+            '[{}]'.format(apiurl),
+            'user = {}'.format(user),
+            'pass = invalid',
+            '',
+        ]).encode('utf-8'))
+        oscrc_file.flush()
+
+        # In order to avoid osc clearing the cookie file the modified time of
+        # the oscrc file must be set further into the past.
+        # if int(round(config_mtime)) > int(os.stat(cookie_file).st_mtime):
+        recent_past = time.time() - 3600
+        os.utime(oscrc_file.name, (recent_past, recent_past))
+
+    def cookiejar_create(self, cookiejar_file, session):
+        cookie_jar = LWPCookieJar(cookiejar_file.name)
+        cookie_jar.set_cookie(Cookie(0, self.COOKIE_NAME, session,
+            None, False,
+            '', False, True,
+            '/', True,
+            True,
+            None, None, None, None, {}))
+        cookie_jar.save()
+        cookiejar_file.flush()
+
+    def execute(self, oscrc_file, command):
+        env = os.environ
+        env['OSC_CONFIG'] = oscrc_file.name
+
+        # Would be preferrable to stream incremental output, but python http
+        # server does not seem to support this easily.
+        result = subprocess.run(command, env=env, stdout=self.wfile, 
stderr=self.wfile)
+        return result.returncode == 0
+
+    def write_string(self, string):
+        self.wfile.write(string.encode('utf-8'))
+
+    def staging_command(self, project, subcommand):
+        return ['osc', 'staging', '-p', project, subcommand]
+
+    def handle_select(self, data):
+        for staging, requests in data['selection'].items():
+            command = self.staging_command(data['project'], 'select')
+            if 'move' in data and data['move']:
+                command.append('--move')
+            command.append(staging)
+            command.extend(requests)
+            yield command
+
+def main(args):
+    RequestHandler.apiurl = args.apiurl
+    RequestHandler.session = args.session
+    RequestHandler.debug = args.debug
+
+    with ThreadedHTTPServer((args.host, args.port), RequestHandler) as httpd:
+        print('listening on {}:{}'.format(args.host, args.port))
+        httpd.serve_forever()
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='OBS Operator server used to 
perform staging operations.')
+    parser.set_defaults(func=main)
+
+    parser.add_argument('--host', default='', help='host name to which to 
bind')
+    parser.add_argument('--port', type=int, default=8080, help='port number to 
which to bind')
+    parser.add_argument('-A', '--apiurl',
+        help='OBS instance API URL to use instead of basing from request 
origin')
+    parser.add_argument('--session',
+        help='session cookie value to use instead of any passed cookie')
+    parser.add_argument('-d', '--debug', action='store_true',
+        help='print debugging information')
+
+    args = parser.parse_args()
+    sys.exit(args.func(args))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20181108.4293b6b/osclib/adi_command.py 
new/openSUSE-release-tools-20181109.3aae284/osclib/adi_command.py
--- old/openSUSE-release-tools-20181108.4293b6b/osclib/adi_command.py   
2018-11-08 17:10:04.000000000 +0100
+++ new/openSUSE-release-tools-20181109.3aae284/osclib/adi_command.py   
2018-11-09 08:50:52.000000000 +0100
@@ -35,7 +35,7 @@
                 return
             if len(info['untracked_requests']):
                 print(query_project + " " + Fore.YELLOW + 'untracked: ' + ', 
'.join(['{}[{}]'.format(
-                    Fore.CYAN + req['package'] + Fore.RESET + " " + 
req['number']) for req in info['untracked_requests']]))
+                    Fore.CYAN + req['package'] + Fore.RESET, req['number']) 
for req in info['untracked_requests']]))
                 return
             if len(info['obsolete_requests']):
                 print(query_project + " " + Fore.YELLOW + 'obsolete: ' + ', 
'.join(['{}[{}]'.format(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20181108.4293b6b/osclib/stagingapi.py 
new/openSUSE-release-tools-20181109.3aae284/osclib/stagingapi.py
--- old/openSUSE-release-tools-20181108.4293b6b/osclib/stagingapi.py    
2018-11-08 17:10:04.000000000 +0100
+++ new/openSUSE-release-tools-20181109.3aae284/osclib/stagingapi.py    
2018-11-09 08:50:52.000000000 +0100
@@ -836,12 +836,11 @@
 
         orig_project = project
         self._remove_package_from_prj_pseudometa(project, package)
-        project = self.map_ring_package_to_subject(project, package)
         if self._supersede:
             self.is_package_disabled(project, package, store=True)
 
         for sub_prj, sub_pkg in self.get_sub_packages(package, project):
-            sub_prj = self.map_ring_package_to_subject(project, sub_pkg)
+            sub_prj = project
             if self._supersede:
                 self.is_package_disabled(sub_prj, sub_pkg, store=True)
             # Skip inner-project links for letter staging
@@ -1090,21 +1089,6 @@
 
         return True
 
-    def map_ring_package_to_subject(self, project, pkg):
-        """
-        Returns the subproject (if any) to use for the pkg depending on the 
ring
-        the package is in
-        :param project the staging prj
-        :param pkg the package to add
-        """
-        # it's actually a pretty stupid algorithm, but it might become more 
complex later
-
-        # assuming it is in adi staging, workaround for 
https://progress.opensuse.org/issues/9646
-        if self.is_adi_project(project):
-            return project
-
-        return project
-
     def get_sub_packages(self, package, project):
         """
         Returns a list of packages that need to be linked to main package.
@@ -1164,11 +1148,10 @@
         """
 
         tar_pkg = act.tgt_package
-        project = self.map_ring_package_to_subject(project, tar_pkg)
         self.create_and_wipe_package(project, tar_pkg)
 
         for sub_prj, sub_pkg in self.get_sub_packages(tar_pkg, project):
-            sub_prj = self.map_ring_package_to_subject(project, sub_pkg)
+            sub_prj = project
             self.create_and_wipe_package(sub_prj, sub_pkg)
 
             # create a link so unselect can find it
@@ -1178,13 +1161,11 @@
 
         return tar_pkg
 
-    def submit_to_prj(self, act, project, force_enable_build=False):
+    def submit_to_prj(self, act, project):
         """
         Links sources from request to project
         :param act: action for submit request
         :param project: project to link into
-        :param force_enable_build: overwrite the ring criteria to enable
-               or disable the build
         """
 
         src_prj = act.src_project
@@ -1192,21 +1173,7 @@
         src_pkg = act.src_package
         tar_pkg = act.tgt_package
 
-        disable_build = False
-        # The force_enable_build will avoid the
-        # map_ring_package_to_subproject
-        if not force_enable_build:
-            if self.crings and not self.ring_packages.get(tar_pkg) and not 
self.is_adi_project(project):
-                disable_build = True
-                logging.warning("{}/{} not in ring, build 
disabled".format(project, tar_pkg))
-            else:
-                project = self.map_ring_package_to_subject(project, tar_pkg)
-
-            if self._supersede:
-                disable_build = self._package_disabled.get('/'.join([project, 
tar_pkg]), disable_build)
-
-        self.create_package_container(project, tar_pkg,
-                                      disable_build=disable_build)
+        self.create_package_container(project, tar_pkg)
 
         # expand the revision to a md5
         url = self.makeurl(['source', src_prj, src_pkg],
@@ -1233,7 +1200,7 @@
             baselibs = True
 
         for sub_prj, sub_pkg in self.get_sub_packages(tar_pkg, project):
-            sub_prj = self.map_ring_package_to_subject(project, sub_pkg)
+            sub_prj = project
             # Skip inner-project links for letter staging
             if not self.is_adi_project(project) and sub_prj == project:
                 continue
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20181108.4293b6b/systemd/osrt-obs-operator.service 
new/openSUSE-release-tools-20181109.3aae284/systemd/osrt-obs-operator.service
--- 
old/openSUSE-release-tools-20181108.4293b6b/systemd/osrt-obs-operator.service   
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/openSUSE-release-tools-20181109.3aae284/systemd/osrt-obs-operator.service   
    2018-11-09 08:50:52.000000000 +0100
@@ -0,0 +1,10 @@
+[Unit]
+Description=openSUSE Release Tools: OBS Operator
+
+[Service]
+User=osrt-obs-operator
+ExecStart=/usr/bin/osrt-obs_operator --debug
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20181108.4293b6b/totest-manager.py 
new/openSUSE-release-tools-20181109.3aae284/totest-manager.py
--- old/openSUSE-release-tools-20181108.4293b6b/totest-manager.py       
2018-11-08 17:10:04.000000000 +0100
+++ new/openSUSE-release-tools-20181109.3aae284/totest-manager.py       
2018-11-09 08:50:52.000000000 +0100
@@ -817,7 +817,7 @@
         return self.iso_build_version(self.project + ':ToTest', 
self.main_products[0])
 
 
-class ToTest151ARM(ToTestBaseNew):
+class ToTest151ARM(ToTest151):
     main_products = [
         '000product:openSUSE-cd-mini-aarch64',
         '000product:openSUSE-dvd5-dvd-aarch64',
@@ -832,9 +832,8 @@
 
     # Leap 15.1 ARM still need to update snapshot
     set_snapshot_number = True
-
-    # product_repo openqa_group jobs_num values are specific to aarch64
-    # TODO: How to handle the other entries of main_products ?
+    # JeOS doesn't follow build numbers of main isos
+    need_same_build_number = False
 
     def openqa_group(self):
         return 'openSUSE Leap 15 AArch64'
@@ -842,9 +841,6 @@
     def jobs_num(self):
         return 10
 
-    def get_current_snapshot(self):
-        return self.iso_build_version(self.project + ':ToTest', 
self.main_products[0])
-
 
 class ToTest150Ports(ToTestBaseNew):
     main_products = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openSUSE-release-tools-20181108.4293b6b/userscript/staging-move-drag-n-drop.user.js
 
new/openSUSE-release-tools-20181109.3aae284/userscript/staging-move-drag-n-drop.user.js
--- 
old/openSUSE-release-tools-20181108.4293b6b/userscript/staging-move-drag-n-drop.user.js
     2018-11-08 17:10:04.000000000 +0100
+++ 
new/openSUSE-release-tools-20181109.3aae284/userscript/staging-move-drag-n-drop.user.js
     2018-11-09 08:50:52.000000000 +0100
@@ -1,7 +1,7 @@
 // ==UserScript==
 // @name         OSRT Staging Move Drag-n-Drop
 // @namespace    openSUSE/openSUSE-release-tools
-// @version      0.1.0
+// @version      0.2.0
 // @description  Provide staging request moving interface on staging dashboard.
 // @author       Jimmy Berry
 // @match        */project/staging_projects/*
@@ -16,10 +16,25 @@
 
 (function()
 {
+    // Exclude not usable browsers.
+    if (!document.querySelectorAll || !('draggable' in 
document.createElement('span'))) {
+        return;
+    }
+
+    // Ensure user is logged in.
+    if (!document.querySelector('#link-to-user-home')) {
+        return;
+    }
+
     // Add explanation of trigger shortcut to legend box.
     var explanation = document.createElement('div');
     explanation.id = 'osrt-explanation';
-    explanation.innerText = 'press ctrl+m to move requests';
+    explanation.innerText = 'enter move mode';
+    explanation.setAttribute('title', 'ctrl + m');
+    explanation.onclick = function() {
+        initMoveInterface();
+        this.onclick = null;
+    };
     document.querySelector('#legends').appendChild(explanation);
 
     window.onkeyup = function(e) {
@@ -35,6 +50,12 @@
     padding: 10px;
     background-color: #d9b200;
     color: white;
+    cursor: pointer;
+}
+
+#osrt-explanation.osrt-active
+{
+    cursor: default;
 }
 
 #osrt-summary
@@ -43,13 +64,55 @@
     z-index: 10000;
     bottom: 0;
     left: 0;
-    width: 95%;
-    height: 100px;
-    padding: 10px;
-    background-color: black;
-    color: #18f018;
-    white-space: pre;
-    overflow: scroll;
+    width: 100%%;
+    height: 2em;
+    padding-top: 0.7em;
+    text-align: center;
+    background-color: white;
+}
+
+#osrt-summary button
+{
+    margin-left: 10px;
+}
+
+#osrt-summary progress
+{
+    display: none;
+}
+
+#osrt-summary.osrt-progress progress
+{
+    display: inline;
+}
+
+#osrt-summary.osrt-progress span,
+#osrt-summary.osrt-progress button,
+#osrt-summary.osrt-success button
+{
+    display: none;
+}
+
+#osrt-summary.osrt-failed
+{
+    background-color: red;
+    color: white;
+}
+
+#osrt-summary.osrt-failed span
+{
+    display: inline;
+}
+
+#osrt-summary.osrt-failed progress
+{
+    display: none;
+}
+
+#osrt-summary.osrt-success
+{
+    background-color: green;
+    color: white;
 }
 
 /* drop target state */
@@ -91,19 +154,7 @@
 
 })();
 
-var initMoveInterface = function(){
-    //exclude older browsers by the features we need them to support
-    //and legacy opera explicitly so we don't waste time on a dead browser
-    if
-    (
-        !document.querySelectorAll
-        ||
-        !('draggable' in document.createElement('span'))
-        ||
-        window.opera
-    )
-    { return; }
-
+var initMoveInterface = function() {
     // Update explanation text and add new legend entries.
     function addLegend(type)
     {
@@ -118,7 +169,10 @@
     addLegend('Moved');
     addLegend('Selected');
 
-    document.querySelector('#osrt-explanation').innerText = 'move mode 
activated: drag box around requests or ctrl/shift+click requests to select and 
drag a request to another staging.';
+    var explanation = document.querySelector('#osrt-explanation');
+    explanation.innerText = 'drag box around requests or ctrl/shift + click 
requests to select and drag a request to another staging.';
+    explanation.setAttribute('title', 'move mode activated');
+    explanation.classList.add('osrt-active');
 
     // @resource will not work since served without proper MIME type.
     
$.get('https://raw.githubusercontent.com/p34eu/selectables/master/selectables.css',
 function(data, status) {
@@ -143,7 +197,7 @@
             e.osrtContinue = (e.target.getAttribute('data-draggable') != 
'item' &&
                               e.target.tagName != 'A' &&
                               e.target.tagName != 'LABEL' &&
-                              e.target.id != 'osrt-summary');
+                              !e.target.id.startsWith('osrt-'));
         },
         // Abuse key option by setting the value in start callback whic is run
         // first and the value determines if drag selection is started.
@@ -171,38 +225,96 @@
         return parent.querySelector('div.letter a').innerText;
     }
 
+    var summary = {};
     function updateSummary()
     {
         var summaryElement = document.querySelector('div#osrt-summary');
         if (!summaryElement) {
             summaryElement = document.createElement('div');
             summaryElement.id = 'osrt-summary';
+            summaryElement.appendChild(document.createElement('span'))
+
+            var button = document.createElement('button');
+            button.innerText = 'Apply';
+            button.onclick = applyChanges;
+            summaryElement.appendChild(button);
+
+            summaryElement.appendChild(document.createElement('progress'))
             document.body.appendChild(summaryElement);
         }
 
         var elements = document.querySelectorAll('.osrt-moved');
-        var summary = {};
+        summary = {};
         var staging;
         for (var i = 0; i < elements.length; i++) {
             staging = getStaging(elements[i]);
+            if (!isNaN(staging)) {
+                staging = 'adi:' + staging;
+            }
             if (!(staging in summary)) {
                 summary[staging] = [];
             }
             summary[staging].push(elements[i].children[0].innerText.trim());
         }
 
-        var summaryText = '';
+        summaryElement.children[0].innerText = elements.length + ' request(s) 
to move affecting ' + Object.keys(summary).length + ' stagings(s)';
+        summaryElement.children[2].setAttribute('max', elements.length);
+    }
+
+    function applyChanges()
+    {
+        var summaryElement = document.querySelector('div#osrt-summary');
+        summaryElement.classList.add('osrt-progress');
+
+        var user = 
document.querySelector('#link-to-user-home').innerText.trim();
         var pathParts = window.location.pathname.split('/');
         var project = pathParts[pathParts.length - 1];
-        for (var key in summary) {
-            staging = key;
-            if (!isNaN(key)) {
-                staging = 'adi:' + key;
-            }
-            summaryText += 'osc staging -p ' + project + ' select --move ' + 
staging + ' ' + summary[key].join(' ') + "\n";
+
+        var data = JSON.stringify({'user': user, 'project': project, 'move': 
true, 'selection': summary});
+        var domain_parent = 
window.location.hostname.split('.').splice(1).join('.');
+        var subdomain = domain_parent.endsWith('suse.de') ? 'tortuga' : 
'operator';
+        var url = 'https://' + subdomain + '.' + domain_parent + '/select';
+        $.post({url: url, data: data, crossDomain: true, xhrFields: 
{withCredentials: true},
+                success: applyChangesSuccess}).fail(applyChangesFailed);
+    }
+
+    function applyChangesSuccess(data)
+    {
+        // Could provide link to this in UI.
+        console.log(data);
+
+        var summaryElement = document.querySelector('div#osrt-summary');
+        summaryElement.classList.add('osrt-complete');
+        if (data.trim().endsWith('failed')) {
+            applyChangesFailed();
+            return;
+        }
+
+        var expected = summaryElement.children[2].getAttribute('max');
+        if ((data.match(/\(\d+\/\d+\)/g) || []).length == expected) {
+            summaryElement.children[0].innerText = 'Moved ' + expected + ' 
request(s).';
+            summaryElement.children[2].setAttribute('value', expected);
+            summaryElement.classList.add('osrt-success');
+            summaryElement.classList.remove('osrt-progress');
+
+            // Could reset UI in a more elegant way.
+            reloadShortly();
+            return;
         }
 
-        summaryElement.innerText = summaryText;
+        applyChangesFailed();
+    }
+
+    function applyChangesFailed()
+    {
+        var summaryElement = document.querySelector('div#osrt-summary');
+        summaryElement.children[0].innerText = 'Failed to move requests.';
+        summaryElement.classList.add('osrt-failed');
+    }
+
+    function reloadShortly()
+    {
+        setTimeout(function() { window.location.reload(); }, 3000);
     }
 
     //get the collection of draggable targets and add their draggable attribute

++++++ openSUSE-release-tools.obsinfo ++++++
--- /var/tmp/diff_new_pack.CHPZQc/_old  2018-11-10 17:02:36.135517244 +0100
+++ /var/tmp/diff_new_pack.CHPZQc/_new  2018-11-10 17:02:36.135517244 +0100
@@ -1,5 +1,5 @@
 name: openSUSE-release-tools
-version: 20181108.4293b6b
-mtime: 1541693404
-commit: 4293b6bce15bd3c6c0356d90ae80360a08d9b059
+version: 20181109.3aae284
+mtime: 1541749852
+commit: 3aae284ff2c32b4c4f0ed528aed4355b312a3a2a
 


Reply via email to