summaryrefslogtreecommitdiffstats
path: root/src/fedora-hosted.py
blob: c58a20a4f13b4427623237bf617c478f53dce040 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/python
# fedora-hosted - a commandline frontend for the Fedora Hosted Projects Trac
#
# Copyright (C) 2008 Red Hat Inc.
# Author: Jesse Keating <jkeating@redhat.com>
# 
# This program 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.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

# TODO: this file should probably go away and be far more generic
# And we should load specific things like url structure from a config
# store of some sort.

import getpass
import optparse
import offtrac
import sys
import time

# Define some constants
BASEURL = 'fedorahosted.org/'

# List currently available commands
cmdlist = ('list-tickets', 'list-milestones', 'ticket-info', 'milestone-info',
           'new-ticket', 'new-milestone', 'update-ticket')

# Define some functions
def setup_action_parser(action):
    """Setup parsers for the various action types"""

    usage = "usage: %%prog %s [options]" % action
    p = optparse.OptionParser(usage = usage)

    if action == "list-tickets":
        p.add_option("--owner", "-o")
        p.add_option("--status", "-s", default="!=closed",
                     help="Query string for status, default is '!=closed'")
        p.add_option("--component", "-c")
        p.add_option("--type", "-t")

#    elif action == "list-milestones":
#        p.add_option("--name", "-n", 
#                     help="Show information about a particular milestone")
#        p.add_option("--all", "-a", action="store_true",
#                     help="Show all milestones, otherwise only show active.")

    elif action == "ticket-info":
        p.set_usage("usage: %%prog %s [ticket numbers]" % action)

    elif action == "milestone_info":
        p.set_usage("usage: %%prog %s [milestones]" % action)

    elif action == "new-ticket":
        p.add_option("--summary", "-s", help="REQUIRED!")
        p.add_option("--description", "-d", help="REQUIRED!")
        p.add_option("--type", "-t", default=None)
        p.add_option("--priority", "-p", default=None)
        p.add_option("--milestone", "-m", default=None)
        p.add_option("--component", "-C", default=None)
        p.add_option("--version", "-v", default=None)
        p.add_option("--keyword", "-k", action="append",
                     help="Keyword to add, can be used multiple times.")
        p.add_option("--assignee", "-a", default=None)
        p.add_option("--cc", action="append",
                     help="Carbon Copy address, can be used multiple times.")
        # This one is a little backwards.  The rpc call is actually notify,
        # and defaults to false, but we want to default to true.
        p.add_option("--stealth", action="store_false", default=True,
                     help="Suppress initial notification of this ticket.")

    elif action == "update-ticket":
        p.add_option("--ticket", "-n", help="Ticket number.  REQUIRED!")
        p.add_option("--comment", "-c", default='')
        p.add_option("--summary", "-s", default=None)
        p.add_option("--description", "-d", default=None)
        p.add_option("--type", "-t", default=None)
        p.add_option("--priority", "-p", default=None)
        p.add_option("--milestone", "-m", default=None)
        p.add_option("--component", "-C", default=None)
        p.add_option("--version", "-v", default=None)
        p.add_option("--keyword", "-k", action="append",
                     help="Keyword to add, can be used multiple times.")
        p.add_option("--assignee", "-a", default=None)
        p.add_option("--cc", action="append",
                     help="Carbon Copy address, can be used multiple times.")
        p.add_option("--status", "-S", default=None)
        p.add_option("--resolution", "-r", default=None)
        # This one is a little backwards.  The rpc call is actually notify,
        # and defaults to false, but we want to default to true.
        p.add_option("--stealth", action="store_false", default=True,
                     help="Suppress notification of this update.")

    elif action == "new-milestone":
        p.add_option("--name", "-n", help="REQUIRED!")
        p.add_option("--description", "-d", default=None)
        p.add_option("--due", "-D", default=None,
                     help="Due date in MM-DD-YY format.")

    return p


# get command line options
usage = "usage: %prog [global options] COMMAND [options]"
usage += "\nCommands: %s" % ', '.join(cmdlist)
parser = optparse.OptionParser(usage=usage)
parser.disable_interspersed_args()

# TODO: Try to get this info from fedora config files
parser.add_option("--user", "-u")
parser.add_option("--password", "-p")
parser.add_option("--project", "-P")

# Parse our global options
(opts, args) = parser.parse_args()

# See if we got a command
if len(args) and args[0] in cmdlist:
    action = args.pop(0)
else:
    parser.print_help()
    sys.exit(1)

# Parse the command
action_parser = setup_action_parser(action)
(actopt, actargs) = action_parser.parse_args(args)

if not opts.user:
    opts.user=raw_input('Username: ')

if not opts.password:
    opts.password=getpass.getpass('Password for %s: ' % opts.user)

if not opts.project:
    opts.project=raw_input('Project space: ')


# Create the TracServ object
uri = 'https://%s:%s@%s/%s/login/xmlrpc' % (opts.user,
                                            opts.password,
                                            BASEURL,
                                            opts.project)
trac = offtrac.TracServer(uri)

# Try to do something
if action == "list-tickets":
    # setup the query string
    query = "status%s" % actopt.status
    if actopt.owner:
        query += "&owner=%s" % actopt.owner
    if actopt.component:
        query += "&component=%s" % actopt.component
    if actopt.type:
        query += "&type=%s" % actopt.type

    results = trac.query_tickets(query)
    print results

elif action == "list-milestones":
    results = trac.list_milestones()
    print results

elif action == "ticket-info":
    if not actargs: # FIXME, this isn't working
        action_parser.print_help()
        sys.exit(1)
    # Setup the trac object for multicall
    trac.setup_multicall()
    for number in actargs:
        trac.get_ticket(number)
    # Do the multicall and print out the results
    for result in trac.do_multicall():
        print result

elif action == "milestone-info":
    if not actargs: # FIXME, this isn't working
        action_parser.print_help()
        sys.exit(1)
    trac.setup_multicall()
    for milestone in actargs:
        trac.get_milestone(milestone)
    for result in trac.do_multicall():
        print result

elif action == "new-ticket":
    # Check to make sure we got all we need
    if actopt.summary and actopt.description:
        pass
    else:
        action_parser.print_help()
        sys.exit(1)
    # Wrap up our keywords and cc into one string, if any
    keywords = None
    ccs = None
    if actopt.keyword:
        keywords = ' '.join(actopt.keyword)
    if actopt.cc:
        ccs = ' '.join(actopt.cc)

    result = trac.create_ticket(actopt.summary, actopt.description,
                              actopt.type, actopt.priority, actopt.milestone,
                              actopt.component, actopt.version, keywords,
                              actopt.assignee, ccs, actopt.stealth)
    print result

elif action == "update-ticket":
    # Check to make sure we got all we need
    if actopt.ticket:
        pass
    else:
        action_parser.print_help()
        sys.exit(1)
    # Wrap up our keywords and cc into one string, if any
    keywords = None
    ccs = None
    if actopt.keyword:
        keywords = ' '.join(actopt.keyword)
    if actopt.cc:
        ccs = ' '.join(actopt.cc)

    result = trac.update_ticket(actopt.ticket, actopt.comment, actopt.summary,
                                actopt.type, actopt.description,
                                actopt.priority, actopt.milestone,
                                actopt.component, actopt.version, keywords,
                                ccs, actopt.status, actopt.resolution,
                                actopt.assignee, actopt.stealth)
    print result

elif action == "new-milestone":
    if not actopt.name:
        action_parser.print_help()
        sys.exit(1)
    # Convert due date to seconds if needed
    due = None
    if actopt.due:
        due = int(time.mktime(time.strptime(actopt.due, "%m-%d-%y")))

    result = trac.create_milestone(actopt.name, actopt.description, due)
    print result # The result here is "0" if successful, printing isn't fun