DPDK CI discussions
 help / color / mirror / Atom feed
From: Thomas Monjalon <thomas.monjalon@6wind.com>
To: ci@dpdk.org
Subject: [dpdk-ci] [PATCH v2 4/7] tools: add patchwork client
Date: Thu,  1 Dec 2016 14:44:49 +0100	[thread overview]
Message-ID: <1480599892-14190-5-git-send-email-thomas.monjalon@6wind.com> (raw)
In-Reply-To: <1480599892-14190-1-git-send-email-thomas.monjalon@6wind.com>

The script pwclient is a frontend for patchwork.
It is imported from patchwork 1.1.
Another version can be used by setting DPDK_CI_PWCLIENT in config.

The config template pwclientrc must be copied in ~/.pwclientrc.

Signed-off-by: Thomas Monjalon <thomas.monjalon@6wind.com>
---
 config/ci.config  |   3 +
 config/pwclientrc |   9 +
 tools/pwclient    | 808 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 820 insertions(+)
 create mode 100644 config/pwclientrc
 create mode 100755 tools/pwclient

diff --git a/config/ci.config b/config/ci.config
index 2aeff04..722aeb5 100644
--- a/config/ci.config
+++ b/config/ci.config
@@ -2,3 +2,6 @@
 #         /etc/dpdk/ci.config
 #     or  ~/.config/dpdk/ci.config
 #     or  .ciconfig
+
+# The pwclient script is part of patchwork and is copied in dpdk-ci
+# export DPDK_CI_PWCLIENT=tools/pwclient
diff --git a/config/pwclientrc b/config/pwclientrc
new file mode 100644
index 0000000..d73e87e
--- /dev/null
+++ b/config/pwclientrc
@@ -0,0 +1,9 @@
+[options]
+default=dpdk
+
+[dpdk]
+url=http://dpdk.org/dev/patchwork/xmlrpc/
+
+# credentials are useless for read-only access
+#username: myuser
+#password: mypass
diff --git a/tools/pwclient b/tools/pwclient
new file mode 100755
index 0000000..f437786
--- /dev/null
+++ b/tools/pwclient
@@ -0,0 +1,808 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Patchwork command line client
+# Copyright (C) 2008 Nate Case <ncase@xes-inc.com>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from __future__ import print_function
+
+import os
+import sys
+try:
+    import xmlrpclib
+except ImportError:
+    # Python 3 has merged/renamed things.
+    import xmlrpc.client as xmlrpclib
+import argparse
+import string
+import subprocess
+import base64
+try:
+    import ConfigParser
+except ImportError:
+    # Python 3 has renamed things.
+    import configparser as ConfigParser
+import shutil
+import re
+
+# Add a shim for Python 2's unicode() helper.
+try:
+    unicode
+except NameError:
+    # Python 3 does everything by unicode now.
+    unicode = str
+
+# Default Patchwork remote XML-RPC server URL
+# This script will check the PW_XMLRPC_URL environment variable
+# for the URL to access.  If that is unspecified, it will fallback to
+# the hardcoded default value specified here.
+DEFAULT_URL = "http://patchwork/xmlrpc/"
+CONFIG_FILE = os.path.expanduser('~/.pwclientrc')
+
+
+class Filter(object):
+
+    """Filter for selecting patches."""
+
+    def __init__(self):
+        # These fields refer to specific objects, so they are special
+        # because we have to resolve them to IDs before passing the
+        # filter to the server
+        self.state = ""
+        self.project = ""
+
+        # The dictionary that gets passed to via XML-RPC
+        self.d = {}
+
+    def add(self, field, value):
+        if field == 'state':
+            self.state = value
+        elif field == 'project':
+            self.project = value
+        else:
+            # OK to add directly
+            self.d[field] = value
+
+    def resolve_ids(self, rpc):
+        """Resolve State, Project, and Person IDs based on filter strings."""
+        if self.state != "":
+            id = state_id_by_name(rpc, self.state)
+            if id == 0:
+                sys.stderr.write("Note: No State found matching %s*, "
+                                 "ignoring filter\n" % self.state)
+            else:
+                self.d['state_id'] = id
+
+        if self.project is not None:
+            id = project_id_by_name(rpc, self.project)
+            if id == 0:
+                sys.stderr.write("Note: No Project found matching %s, "
+                                 "ignoring filter\n" % self.project)
+            else:
+                self.d['project_id'] = id
+
+    def __str__(self):
+        """Return human-readable description of the filter."""
+        return str(self.d)
+
+
+class BasicHTTPAuthTransport(xmlrpclib.SafeTransport):
+
+    def __init__(self, username=None, password=None, use_https=False):
+        self.username = username
+        self.password = password
+        self.use_https = use_https
+        xmlrpclib.SafeTransport.__init__(self)
+
+    def authenticated(self):
+        return self.username is not None and self.password is not None
+
+    def send_host(self, connection, host):
+        xmlrpclib.Transport.send_host(self, connection, host)
+        if not self.authenticated():
+            return
+        credentials = '%s:%s' % (self.username, self.password)
+        auth = 'Basic ' + base64.encodestring(credentials).strip()
+        connection.putheader('Authorization', auth)
+
+    def make_connection(self, host):
+        if self.use_https:
+            fn = xmlrpclib.SafeTransport.make_connection
+        else:
+            fn = xmlrpclib.Transport.make_connection
+        return fn(self, host)
+
+
+def project_id_by_name(rpc, linkname):
+    """Given a project short name, look up the Project ID."""
+    if len(linkname) == 0:
+        return 0
+    projects = rpc.project_list(linkname, 0)
+    for project in projects:
+        if project['linkname'] == linkname:
+            return project['id']
+    return 0
+
+
+def state_id_by_name(rpc, name):
+    """Given a partial state name, look up the state ID."""
+    if len(name) == 0:
+        return 0
+    states = rpc.state_list(name, 0)
+    for state in states:
+        if state['name'].lower().startswith(name.lower()):
+            return state['id']
+    return 0
+
+
+def person_ids_by_name(rpc, name):
+    """Given a partial name or email address, return a list of the
+    person IDs that match."""
+    if len(name) == 0:
+        return []
+    people = rpc.person_list(name, 0)
+    return [x['id'] for x in people]
+
+
+def list_patches(patches, format_str=None):
+    """Dump a list of patches to stdout."""
+    if format_str:
+        format_field_re = re.compile("%{([a-z0-9_]+)}")
+
+        def patch_field(matchobj):
+            fieldname = matchobj.group(1)
+
+            if fieldname == "_msgid_":
+                # naive way to strip < and > from message-id
+                val = string.strip(str(patch["msgid"]), "<>")
+            else:
+                val = str(patch[fieldname])
+
+            return val
+
+        for patch in patches:
+            print(format_field_re.sub(patch_field, format_str))
+    else:
+        print("%-7s %-12s %s" % ("ID", "State", "Name"))
+        print("%-7s %-12s %s" % ("--", "-----", "----"))
+        for patch in patches:
+            print("%-7d %-12s %s" %
+                  (patch['id'], patch['state'], patch['name']))
+
+
+def action_list(rpc, filter, submitter_str, delegate_str, format_str=None):
+    filter.resolve_ids(rpc)
+
+    if submitter_str is not None:
+        ids = person_ids_by_name(rpc, submitter_str)
+        if len(ids) == 0:
+            sys.stderr.write("Note: Nobody found matching *%s*\n" %
+                             submitter_str)
+        else:
+            for id in ids:
+                person = rpc.person_get(id)
+                print('Patches submitted by %s <%s>:' %
+                      (unicode(person['name']).encode('utf-8'),
+                       unicode(person['email']).encode('utf-8')))
+                f = filter
+                f.add("submitter_id", id)
+                patches = rpc.patch_list(f.d)
+                list_patches(patches, format_str)
+        return
+
+    if delegate_str is not None:
+        ids = person_ids_by_name(rpc, delegate_str)
+        if len(ids) == 0:
+            sys.stderr.write("Note: Nobody found matching *%s*\n" %
+                             delegate_str)
+        else:
+            for id in ids:
+                person = rpc.person_get(id)
+                print('Patches delegated to %s <%s>:' %
+                      (person['name'], person['email']))
+                f = filter
+                f.add("delegate_id", id)
+                patches = rpc.patch_list(f.d)
+                list_patches(patches, format_str)
+        return
+
+    patches = rpc.patch_list(filter.d)
+    list_patches(patches, format_str)
+
+
+def action_projects(rpc):
+    projects = rpc.project_list("", 0)
+    print("%-5s %-24s %s" % ("ID", "Name", "Description"))
+    print("%-5s %-24s %s" % ("--", "----", "-----------"))
+    for project in projects:
+        print("%-5d %-24s %s" % (project['id'],
+                                 project['linkname'],
+                                 project['name']))
+
+
+def action_check_list(rpc):
+    checks = rpc.check_list()
+    print("%-5s %-16s %-8s %s" % ("ID", "Context", "State", "Patch"))
+    print("%-5s %-16s %-8s %s" % ("--", "-------", "-----", "-----"))
+    for check in checks:
+        print("%-5s %-16s %-8s %s" % (check['id'],
+                                      check['context'],
+                                      check['state'],
+                                      check['patch']))
+
+
+def action_check_info(rpc, check_id):
+    check = rpc.check_get(check_id)
+    s = "Information for check id %d" % (check_id)
+    print(s)
+    print('-' * len(s))
+    for key, value in sorted(check.items()):
+        print("- %- 14s: %s" % (key, unicode(value).encode("utf-8")))
+
+
+def action_check_create(rpc, patch_id, context, state, url, description):
+    try:
+        rpc.check_create(patch_id, context, state, url, description)
+    except xmlrpclib.Fault as f:
+        sys.stderr.write("Error creating check: %s\n" % f.faultString)
+
+
+def action_states(rpc):
+    states = rpc.state_list("", 0)
+    print("%-5s %s" % ("ID", "Name"))
+    print("%-5s %s" % ("--", "----"))
+    for state in states:
+        print("%-5d %s" % (state['id'], state['name']))
+
+
+def action_info(rpc, patch_id):
+    patch = rpc.patch_get(patch_id)
+    s = "Information for patch id %d" % (patch_id)
+    print(s)
+    print('-' * len(s))
+    for key, value in sorted(patch.items()):
+        print("- %- 14s: %s" % (key, unicode(value).encode("utf-8")))
+
+
+def action_get(rpc, patch_id):
+    patch = rpc.patch_get(patch_id)
+    s = rpc.patch_get_mbox(patch_id)
+
+    if patch == {} or len(s) == 0:
+        sys.stderr.write("Unable to get patch %d\n" % patch_id)
+        sys.exit(1)
+
+    base_fname = fname = os.path.basename(patch['filename'])
+    i = 0
+    while os.path.exists(fname):
+        fname = "%s.%d" % (base_fname, i)
+        i += 1
+
+    try:
+        f = open(fname, "w")
+    except:
+        sys.stderr.write("Unable to open %s for writing\n" % fname)
+        sys.exit(1)
+
+    try:
+        f.write(unicode(s).encode("utf-8"))
+        f.close()
+        print('Saved patch to %s' % fname)
+    except:
+        sys.stderr.write("Failed to write to %s\n" % fname)
+        sys.exit(1)
+
+
+def action_apply(rpc, patch_id, apply_cmd=None):
+    patch = rpc.patch_get(patch_id)
+    if patch == {}:
+        sys.stderr.write("Error getting information on patch ID %d\n" %
+                         patch_id)
+        sys.exit(1)
+
+    if apply_cmd is None:
+        print('Applying patch #%d to current directory' % patch_id)
+        apply_cmd = ['patch', '-p1']
+    else:
+        print('Applying patch #%d using %s' %
+              (patch_id, repr(' '.join(apply_cmd))))
+
+    print('Description: %s' % patch['name'])
+    s = rpc.patch_get_mbox(patch_id)
+    if len(s) > 0:
+        proc = subprocess.Popen(apply_cmd, stdin=subprocess.PIPE)
+        proc.communicate(unicode(s).encode('utf-8'))
+        return proc.returncode
+    else:
+        sys.stderr.write("Error: No patch content found\n")
+        sys.exit(1)
+
+
+def action_update_patch(rpc, patch_id, state=None, archived=None, commit=None):
+    patch = rpc.patch_get(patch_id)
+    if patch == {}:
+        sys.stderr.write("Error getting information on patch ID %d\n" %
+                         patch_id)
+        sys.exit(1)
+
+    params = {}
+
+    if state:
+        state_id = state_id_by_name(rpc, state)
+        if state_id == 0:
+            sys.stderr.write("Error: No State found matching %s*\n" % state)
+            sys.exit(1)
+        params['state'] = state_id
+
+    if commit:
+        params['commit_ref'] = commit
+
+    if archived:
+        params['archived'] = archived == 'yes'
+
+    success = False
+    try:
+        success = rpc.patch_set(patch_id, params)
+    except xmlrpclib.Fault as f:
+        sys.stderr.write("Error updating patch: %s\n" % f.faultString)
+
+    if not success:
+        sys.stderr.write("Patch not updated\n")
+
+
+def patch_id_from_hash(rpc, project, hash):
+    try:
+        patch = rpc.patch_get_by_project_hash(project, hash)
+    except xmlrpclib.Fault:
+        # the server may not have the newer patch_get_by_project_hash function,
+        # so fall back to hash-only.
+        patch = rpc.patch_get_by_hash(hash)
+
+    if patch == {}:
+        sys.stderr.write("No patch has the hash provided\n")
+        sys.exit(1)
+
+    patch_id = patch['id']
+    # be super paranoid
+    try:
+        patch_id = int(patch_id)
+    except:
+        sys.stderr.write("Invalid patch ID obtained from server\n")
+        sys.exit(1)
+    return patch_id
+
+auth_actions = ['check_create', 'update']
+
+
+def main():
+    hash_parser = argparse.ArgumentParser(add_help=False)
+    hash_parser.add_argument(
+        '-h', metavar='HASH', dest='hash', action='store',
+        help='''Lookup by patch hash'''
+    )
+    hash_parser.add_argument(
+        'id', metavar='ID', nargs='*', action='store', type=int,
+        help='Patch ID',
+    )
+    hash_parser.add_argument(
+        '-p', metavar='PROJECT',
+        help='''Lookup patch in project'''
+    )
+
+    filter_parser = argparse.ArgumentParser(add_help=False)
+    filter_parser.add_argument(
+        '-s', metavar='STATE',
+        help='''Filter by patch state (e.g., 'New', 'Accepted', etc.)'''
+    )
+    filter_parser.add_argument(
+        '-a', choices=['yes', 'no'],
+        help='''Filter by patch archived state'''
+    )
+    filter_parser.add_argument(
+        '-p', metavar='PROJECT',
+        help='''Filter by project name (see 'projects' for list)'''
+    )
+    filter_parser.add_argument(
+        '-w', metavar='WHO',
+        help='''Filter by submitter (name, e-mail substring search)'''
+    )
+    filter_parser.add_argument(
+        '-d', metavar='WHO',
+        help='''Filter by delegate (name, e-mail substring search)'''
+    )
+    filter_parser.add_argument(
+        '-n', metavar='MAX#',
+        type=int,
+        help='''Return first n results'''
+    )
+    filter_parser.add_argument(
+        '-N', metavar='MAX#',
+        type=int,
+        help='''Return last N results'''
+    )
+    filter_parser.add_argument(
+        '-m', metavar='MESSAGEID',
+        help='''Filter by Message-Id'''
+    )
+    filter_parser.add_argument(
+        '-f', metavar='FORMAT',
+        help='''Print output in the given format. You can use tags matching '''
+        '''fields, e.g. %%{id}, %%{state}, or %%{msgid}.'''
+    )
+    filter_parser.add_argument(
+        'patch_name', metavar='STR', nargs='?',
+        help='substring to search for patches by name',
+    )
+
+    action_parser = argparse.ArgumentParser(
+        prog='pwclient',
+        epilog='Use \'pwclient <command> --help\' for more info',
+    )
+
+    subparsers = action_parser.add_subparsers(
+        title='Commands',
+    )
+    apply_parser = subparsers.add_parser(
+        'apply', parents=[hash_parser], conflict_handler='resolve',
+        help='''Apply a patch (in the current dir, using -p1)'''
+    )
+    apply_parser.set_defaults(subcmd='apply')
+    git_am_parser = subparsers.add_parser(
+        'git-am', parents=[hash_parser], conflict_handler='resolve',
+        help='''Apply a patch to current git branch using "git am".'''
+    )
+    git_am_parser.set_defaults(subcmd='git_am')
+    git_am_parser.add_argument(
+        '-s', '--signoff',
+        action='store_true',
+        help='''pass --signoff to git-am'''
+    )
+    get_parser = subparsers.add_parser(
+        'get', parents=[hash_parser], conflict_handler='resolve',
+        help='''Download a patch and save it locally'''
+    )
+    get_parser.set_defaults(subcmd='get')
+    info_parser = subparsers.add_parser(
+        'info', parents=[hash_parser], conflict_handler='resolve',
+        help='''Display patchwork info about a given patch ID'''
+    )
+    info_parser.set_defaults(subcmd='info')
+    projects_parser = subparsers.add_parser(
+        'projects',
+        help='''List all projects'''
+    )
+    projects_parser.set_defaults(subcmd='projects')
+    check_list_parser = subparsers.add_parser(
+        'check-list',
+        add_help=False,
+        help='''List all checks'''
+    )
+    check_list_parser.set_defaults(subcmd='check_list')
+    check_info_parser = subparsers.add_parser(
+        'check-info',
+        add_help=False,
+        help='''Show information for a given check'''
+    )
+    check_info_parser.set_defaults(subcmd='check_info')
+    check_info_parser.add_argument(
+        'check_id', metavar='ID', action='store', type=int,
+        help='Check ID',)
+    check_create_parser = subparsers.add_parser(
+        'check-create', parents=[hash_parser], conflict_handler='resolve',
+        help='Add a check to a patch')
+    check_create_parser.set_defaults(subcmd='check_create')
+    check_create_parser.add_argument(
+        '-c', metavar='CONTEXT')
+    check_create_parser.add_argument(
+        '-s', choices=('pending', 'success', 'warning', 'fail'))
+    check_create_parser.add_argument(
+        '-u', metavar='TARGET_URL', default="")
+    check_create_parser.add_argument(
+        '-d', metavar='DESCRIPTION', default="")
+    states_parser = subparsers.add_parser(
+        'states',
+        help='''Show list of potential patch states'''
+    )
+    states_parser.set_defaults(subcmd='states')
+    view_parser = subparsers.add_parser(
+        'view', parents=[hash_parser], conflict_handler='resolve',
+        help='''View a patch'''
+    )
+    view_parser.set_defaults(subcmd='view')
+    update_parser = subparsers.add_parser(
+        'update', parents=[hash_parser], conflict_handler='resolve',
+        help='''Update patch''',
+        epilog='''Using a COMMIT-REF allows for only one ID to be specified''',
+    )
+    update_parser.add_argument(
+        '-c', metavar='COMMIT-REF',
+        help='''commit reference hash'''
+    )
+    update_parser.add_argument(
+        '-s', metavar='STATE',
+        help='''Set patch state (e.g., 'Accepted', 'Superseded' etc.)'''
+    )
+    update_parser.add_argument(
+        '-a', choices=['yes', 'no'],
+        help='''Set patch archived state'''
+    )
+    update_parser.set_defaults(subcmd='update')
+    list_parser = subparsers.add_parser("list",
+                                        # aliases=['search'],
+                                        parents=[filter_parser],
+                                        help='''List patches, using the optional filters specified
+        below and an optional substring to search for patches
+        by name'''
+                                        )
+    list_parser.set_defaults(subcmd='list')
+    search_parser = subparsers.add_parser("search",
+                                          parents=[filter_parser],
+                                          help='''Alias for "list"'''
+                                          )
+    # Poor man's argparse aliases:
+    # We register the "search" parser but effectively use "list" for the
+    # help-text.
+    search_parser.set_defaults(subcmd='list')
+    if len(sys.argv) < 2:
+        action_parser.print_help()
+        sys.exit(0)
+
+    args = action_parser.parse_args()
+    args = dict(vars(args))
+    action = args.get('subcmd')
+
+    if args.get('hash') and len(args.get('id')):
+        # mimic mutual exclusive group
+        locals()[action + '_parser'].error(
+            "[-h HASH] and [ID [ID ...]] are mutually exlusive")
+
+    # set defaults
+    filt = Filter()
+    commit_str = None
+    url = DEFAULT_URL
+
+    archived_str = args.get('a')
+    state_str = args.get('s')
+    project_str = args.get('p')
+    submitter_str = args.get('w')
+    delegate_str = args.get('d')
+    format_str = args.get('f')
+    hash_str = args.get('hash')
+    patch_ids = args.get('id')
+    msgid_str = args.get('m')
+    if args.get('c'):
+        # update multiple IDs with a single commit-hash does not make sense
+        if action == 'update' and patch_ids and len(patch_ids) > 1:
+            update_parser.error(
+                "Declining update with COMMIT-REF on multiple IDs")
+        commit_str = args.get('c')
+
+    if state_str is None and archived_str is None and action == 'update':
+        update_parser.error(
+            'Must specify one or more update options (-a or -s)')
+
+    if args.get('n') is not None:
+        try:
+            filt.add("max_count", args.get('n'))
+        except:
+            action_parser.error("Invalid maximum count '%s'" % args.get('n'))
+
+    if args.get('N') is not None:
+        try:
+            filt.add("max_count", 0 - args.get('N'))
+        except:
+            action_parser.error("Invalid maximum count '%s'" % args.get('N'))
+
+    do_signoff = args.get('signoff')
+
+    # grab settings from config files
+    config = ConfigParser.ConfigParser()
+    config.read([CONFIG_FILE])
+
+    if not config.has_section('options') and os.path.exists(CONFIG_FILE):
+        sys.stderr.write('~/.pwclientrc is in the old format. Migrating it...')
+
+        old_project = config.get('base', 'project')
+
+        new_config = ConfigParser.ConfigParser()
+        new_config.add_section('options')
+
+        new_config.set('options', 'default', old_project)
+        new_config.add_section(old_project)
+
+        new_config.set(old_project, 'url', config.get('base', 'url'))
+        if config.has_option('auth', 'username'):
+            new_config.set(
+                old_project, 'username', config.get('auth', 'username'))
+        if config.has_option('auth', 'password'):
+            new_config.set(
+                old_project, 'password', config.get('auth', 'password'))
+
+        old_config_file = CONFIG_FILE + '.orig'
+        shutil.copy2(CONFIG_FILE, old_config_file)
+
+        with open(CONFIG_FILE, 'wb') as fd:
+            new_config.write(fd)
+
+        sys.stderr.write(' Done.\n')
+        sys.stderr.write(
+            'Your old ~/.pwclientrc was saved to %s\n' % old_config_file)
+        sys.stderr.write(
+            'and was converted to the new format. You may want to\n')
+        sys.stderr.write('inspect it before continuing.\n')
+        sys.exit(1)
+
+    if not project_str:
+        try:
+            project_str = config.get('options', 'default')
+        except:
+            action_parser.error(
+                "No default project configured in ~/.pwclientrc")
+
+    if not config.has_section(project_str):
+        sys.stderr.write(
+            'No section for project %s in ~/.pwclientrc\n' % project_str)
+        sys.exit(1)
+    if not config.has_option(project_str, 'url'):
+        sys.stderr.write(
+            'No URL for project %s in ~/.pwclientrc\n' % project_str)
+        sys.exit(1)
+    if not do_signoff and config.has_option('options', 'signoff'):
+        do_signoff = config.getboolean('options', 'signoff')
+    if not do_signoff and config.has_option(project_str, 'signoff'):
+        do_signoff = config.getboolean(project_str, 'signoff')
+
+    url = config.get(project_str, 'url')
+
+    transport = None
+    if action in auth_actions:
+        if config.has_option(project_str, 'username') and \
+                config.has_option(project_str, 'password'):
+
+            use_https = url.startswith('https')
+
+            transport = BasicHTTPAuthTransport(
+                config.get(project_str, 'username'),
+                config.get(project_str, 'password'),
+                use_https)
+
+        else:
+            sys.stderr.write("The %s action requires authentication, but no "
+                             "username or password\nis configured\n" % action)
+            sys.exit(1)
+
+    if project_str:
+        filt.add("project", project_str)
+
+    if state_str:
+        filt.add("state", state_str)
+
+    if archived_str:
+        filt.add("archived", archived_str == 'yes')
+
+    if msgid_str:
+        filt.add("msgid", msgid_str)
+
+    try:
+        rpc = xmlrpclib.Server(url, transport=transport)
+    except:
+        sys.stderr.write("Unable to connect to %s\n" % url)
+        sys.exit(1)
+
+    # It should be safe to assume hash_str is not zero, but who knows..
+    if hash_str is not None:
+        patch_ids = [patch_id_from_hash(rpc, project_str, hash_str)]
+
+    # helper for non_empty() to print correct helptext
+    h = locals()[action + '_parser']
+
+    # Require either hash_str or IDs for
+    def non_empty(h, patch_ids):
+        """Error out if no patch IDs were specified"""
+        if patch_ids is None or len(patch_ids) < 1:
+            sys.stderr.write("Error: Missing Argument! Either [-h HASH] or "
+                             "[ID [ID ...]] are required\n")
+            if h:
+                h.print_help()
+            sys.exit(1)
+        return patch_ids
+
+    if action == 'list' or action == 'search':
+        if args.get('patch_name') is not None:
+            filt.add("name__icontains", args.get('patch_name'))
+        action_list(rpc, filt, submitter_str, delegate_str, format_str)
+
+    elif action.startswith('project'):
+        action_projects(rpc)
+
+
+    elif action.startswith('state'):
+        action_states(rpc)
+
+    elif action == 'view':
+        pager = os.environ.get('PAGER')
+        if pager:
+            pager = subprocess.Popen(
+                pager.split(), stdin=subprocess.PIPE
+            )
+        if pager:
+            i = list()
+            for patch_id in non_empty(h, patch_ids):
+                s = rpc.patch_get_mbox(patch_id)
+                if len(s) > 0:
+                    i.append(unicode(s).encode("utf-8"))
+            if len(i) > 0:
+                pager.communicate(input="\n".join(i))
+            pager.stdin.close()
+        else:
+            for patch_id in non_empty(h, patch_ids):
+                s = rpc.patch_get_mbox(patch_id)
+                if len(s) > 0:
+                    print(unicode(s).encode("utf-8"))
+
+    elif action == 'info':
+        for patch_id in non_empty(h, patch_ids):
+            action_info(rpc, patch_id)
+
+    elif action == 'get':
+        for patch_id in non_empty(h, patch_ids):
+            action_get(rpc, patch_id)
+
+    elif action == 'apply':
+        for patch_id in non_empty(h, patch_ids):
+            ret = action_apply(rpc, patch_id)
+            if ret:
+                sys.stderr.write("Apply failed with exit status %d\n" % ret)
+                sys.exit(1)
+
+    elif action == 'git_am':
+        cmd = ['git', 'am']
+        if do_signoff:
+            cmd.append('-s')
+        for patch_id in non_empty(h, patch_ids):
+            ret = action_apply(rpc, patch_id, cmd)
+            if ret:
+                sys.stderr.write("'git am' failed with exit status %d\n" % ret)
+                sys.exit(1)
+
+    elif action == 'update':
+        for patch_id in non_empty(h, patch_ids):
+            action_update_patch(rpc, patch_id, state=state_str,
+                                archived=archived_str, commit=commit_str
+                                )
+
+    elif action == 'check_list':
+        action_check_list(rpc)
+
+    elif action == 'check_info':
+        check_id = args['check_id']
+        action_check_info(rpc, check_id)
+
+    elif action == 'check_create':
+        for patch_id in non_empty(h, patch_ids):
+            action_check_create(
+                rpc, patch_id, args['c'], args['s'], args['u'], args['d'])
+
+    else:
+        sys.stderr.write("Unknown action '%s'\n" % action)
+        action_parser.print_help()
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main()
-- 
2.7.0

  parent reply	other threads:[~2016-12-01 13:45 UTC|newest]

Thread overview: 64+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-11-25 17:02 [dpdk-ci] [PATCH 0/7] first scripts for CI integration Thomas Monjalon
2016-11-25 17:02 ` [dpdk-ci] [PATCH 1/7] tools: add mail filter Thomas Monjalon
2016-11-25 17:02 ` [dpdk-ci] [PATCH 2/7] tools: add mail parser Thomas Monjalon
2016-11-25 17:02 ` [dpdk-ci] [PATCH 3/7] config: add loader and template Thomas Monjalon
2016-11-25 17:02 ` [dpdk-ci] [PATCH 4/7] tools: add patchwork client Thomas Monjalon
2016-11-25 17:02 ` [dpdk-ci] [PATCH 5/7] tools: add per-patch report mailer Thomas Monjalon
2016-11-25 17:02 ` [dpdk-ci] [PATCH 6/7] tools: add patchwork integration Thomas Monjalon
2016-11-25 17:02 ` [dpdk-ci] [PATCH 7/7] tests: add checkpatch Thomas Monjalon
2016-12-01 13:44 ` [dpdk-ci] [PATCH v2 0/7] first scripts for CI integration Thomas Monjalon
2016-12-01 13:44   ` [dpdk-ci] [PATCH v2 1/7] tools: add mail filter Thomas Monjalon
2016-12-01 13:44   ` [dpdk-ci] [PATCH v2 2/7] tools: add mail parser Thomas Monjalon
2016-12-01 13:44   ` [dpdk-ci] [PATCH v2 3/7] config: add loader and template Thomas Monjalon
2016-12-01 13:44   ` Thomas Monjalon [this message]
2016-12-01 13:44   ` [dpdk-ci] [PATCH v2 5/7] tools: add per-patch report mailer Thomas Monjalon
2016-12-01 13:44   ` [dpdk-ci] [PATCH v2 6/7] tools: add patchwork integration Thomas Monjalon
2016-12-01 13:44   ` [dpdk-ci] [PATCH v2 7/7] tests: add checkpatch Thomas Monjalon
2016-12-01 16:58   ` [dpdk-ci] [PATCH v3 0/7] first scripts for CI integration Thomas Monjalon
2016-12-01 16:58     ` [dpdk-ci] [PATCH v3 1/7] tools: add mail filter Thomas Monjalon
2016-12-01 16:58     ` [dpdk-ci] [PATCH v3 2/7] tools: add mail parser Thomas Monjalon
2016-12-01 16:58     ` [dpdk-ci] [PATCH v3 3/7] config: add loader and template Thomas Monjalon
2016-12-01 16:58     ` [dpdk-ci] [PATCH v3 4/7] tools: add patchwork client Thomas Monjalon
2016-12-01 16:58     ` [dpdk-ci] [PATCH v3 5/7] tools: add per-patch report mailer Thomas Monjalon
2016-12-01 16:59     ` [dpdk-ci] [PATCH v3 6/7] tools: add patchwork integration Thomas Monjalon
2016-12-01 16:59     ` [dpdk-ci] [PATCH v3 7/7] tests: add checkpatch Thomas Monjalon
2016-12-05 13:26   ` [dpdk-ci] [PATCH v4 0/7] first scripts for CI integration Thomas Monjalon
2016-12-05 13:26     ` [dpdk-ci] [PATCH v4 1/7] tools: add mail filter Thomas Monjalon
2016-12-05 13:26     ` [dpdk-ci] [PATCH v4 2/7] tools: add mail parser Thomas Monjalon
2016-12-05 13:26     ` [dpdk-ci] [PATCH v4 3/7] config: add loader and template Thomas Monjalon
2016-12-05 13:26     ` [dpdk-ci] [PATCH v4 4/7] tools: add patchwork client Thomas Monjalon
2016-12-05 13:26     ` [dpdk-ci] [PATCH v4 5/7] tools: add per-patch report mailer Thomas Monjalon
2016-12-05 13:26     ` [dpdk-ci] [PATCH v4 6/7] tools: add patchwork integration Thomas Monjalon
2016-12-05 13:26     ` [dpdk-ci] [PATCH v4 7/7] tests: add checkpatch Thomas Monjalon
2016-12-06  6:34       ` Wei, FangfangX
2016-12-06  8:40         ` Thomas Monjalon
2016-12-06  9:04           ` Wei, FangfangX
2016-12-07  5:48           ` Wei, FangfangX
2016-12-07  9:32             ` Thomas Monjalon
2016-12-08  9:02               ` Wei, FangfangX
2016-12-08 13:11                 ` Thomas Monjalon
2016-12-09  8:51                   ` Wei, FangfangX
2016-12-09  9:16                     ` Thomas Monjalon
2016-12-09 10:07                       ` Mcnamara, John
2016-12-09 10:11                         ` Thomas Monjalon
2016-12-09 12:11                           ` Mcnamara, John
2016-12-12  9:27                       ` Wei, FangfangX
2016-12-12  9:34                         ` Wei, FangfangX
2016-12-12  9:58                           ` Thomas Monjalon
2016-12-13  8:29                             ` Wei, FangfangX
2016-12-13  8:49                               ` Thomas Monjalon
2016-12-13  9:24                                 ` Wei, FangfangX
2016-12-21 11:45                                   ` Thomas Monjalon
2016-12-12  9:39                         ` Thomas Monjalon
2016-12-13  8:22                           ` Wei, FangfangX
2016-12-14 23:05     ` [dpdk-ci] [PATCH v5 0/9] first scripts for CI integration Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 1/9] tools: add mail filter Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 2/9] tools: add mail parser Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 3/9] config: add loader and template Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 4/9] tools: add patchwork client Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 5/9] tools: fix pwclient for proxy and python 3 Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 6/9] tools: add patch mail download Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 7/9] tools: add per-patch report mailer Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 8/9] tools: add patchwork integration Thomas Monjalon
2016-12-14 23:05       ` [dpdk-ci] [PATCH v5 9/9] tests: add checkpatch Thomas Monjalon
2016-12-21 11:46       ` [dpdk-ci] [PATCH v5 0/9] first scripts for CI integration Thomas Monjalon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1480599892-14190-5-git-send-email-thomas.monjalon@6wind.com \
    --to=thomas.monjalon@6wind.com \
    --cc=ci@dpdk.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).