Show the state.
[opensuse:osc-contrib.git] / osc-contrib.py
1 #
2 #   A contrib plugin for osc
3 #
4 #   This tool makes maintenance of openSUSE:Factory:Contrib more easier than an
5 #   universal osc commands.
6 #
7 #   Copyright: (c) 2009 Michal Vyskocil <mvyskocil@suse.cz>
8 #
9
10 @cmdln.option('-m', '--message', metavar='MESSAGE',
11               help='the request message (optional)')
12 @cmdln.option('-i', '--id', metavar='REQUEST_ID',
13               help='The concrete request id. Usefull whe multiple requests exists.')
14 @cmdln.option('-s', '--state', metavar='STATE',
15               help='Show a requests with a specified state.')
16 @cmdln.alias("cb")
17 def do_contrib(self, subcmd, opts, *args):
18     """${cmd_name}: Handling a requests for Contrib
19
20 osc subcommand for maintenance of openSUSE Contrib repository. This command
21 tries to make the maintenance process more easier, than common osc
22 commands. These commands are derived from existing ones, but are specialized.
23
24 osc contrib show [PACKAGE]
25 Show all new requests towards Contrib. The optional argument package will
26 filter only a requests to it.
27
28 Options:
29     -s, --state     filter the state (type 'any' for all)
30
31 osc contrib checkout PACKAGE
32 Checkout the requested package to the dir.
33
34 Options:
35     -i, --id        id of request (if multiple exists)
36
37
38 osc contrib [accept|decline|revoke] PACKAGE
39 Change the state of package to <state>
40
41 Options:
42     -i, --id        id of request (if multiple requests exists)
43     -m, --message   the submit message (optional for accept)
44
45 ${cmd_usage}
46 ${cmd_option_list}
47     """
48
49     import types
50     cmds = [cmd[12:] for cmd in dir(self) if cmd[0:12] == '_do_contrib_' and type(getattr(self, cmd)) == types.MethodType]
51     if not args or args[0] not in cmds:
52         raise oscerr.WrongArgs("Unknown contrib action. Choose one of %s." \
53                                 % ', '.join(cmds))
54     
55     command = args[0]
56
57     self.project = 'openSUSE:Factory:Contrib'
58     #self.project = 'home:mvyskocil'
59     self.apiurl  = conf.config['apiurl']
60
61     # call
62     getattr(self, "_do_contrib_%s" % (command))(opts, args[1:])
63
64 def _sr_from_package(self, package, reqid=None, req_state=''):
65     requests = get_submit_request_list(self.apiurl, self.project, package, req_state=req_state)
66     if len(requests) == 0:
67         raise oscerr.WrongArgs("No request for package %s found" % (package))
68     elif len(requests) > 1:
69         if reqid == None:
70             raise oscerr.WrongArgs(
71             "There are multiple requests (%s) towards package %s. Use -i/--id argument!" %
72             (", ".join([str(r.reqid) for r in requests]), package))
73         
74         ret = [req for req in requests if req.reqid == int(reqid)]
75         if len(ret) == 0:
76             raise oscerr.WrongArgs("The package %s and request id %s doesn't match! \
77                     Use one of these (%s)" % (package, reqid, ", ".join([str(r.reqid) for r in requests])))
78         requests = ret
79
80     return requests[0]
81
82 def _do_contrib_show(self, opts, args):
83
84     package = ''
85     if len(args) > 0:
86         package = args[0]
87
88     state = opts.state or 'new'
89     if state == 'any':
90         state = ''
91     
92     for sr in get_submit_request_list(self.apiurl, self.project, package, req_state=state):
93         print(sr)
94
95 def __do_contrib_new(self, opts, args):
96     src_project, src_package, dest_package = args[0], args[1], args[2]
97     if not dest_project or not package:
98         raise oscerr.WrongArgs("The project and package names are mandatory!!")
99     if not dest_package or dest_package == '':
100         dest_package = src_package
101
102     message = opts.message or "please add a '%s' to Contrib" % (package)
103     id = osc.core.create_submit_request(self.apiurl, src_project, src_package, self.project, dest_package, message)
104     print("Request id %d created" % (id))
105
106 def _do_contrib_accept(self, opts, args):
107     
108     return self._contrib_sr_change(opts, args, "accepted", 
109            "Reviewed and checked OK, welcome to Contrib.")
110
111 def _do_contrib_decline(self, opts, args):
112
113     if not opts.message:
114         raise oscerr.WrongArgs('A message is mandatory for decline')
115     
116     return self._contrib_sr_change(opts, args, "declined",
117            opts.message)
118
119 def _do_contrib_revoke(self, opts, args):
120     
121     if not opts.message:
122         raise oscerr.WrongArgs('A message is mandatory for decline')
123     
124     return self._contrib_sr_change(opts, args, "revoked",
125            opts.message)
126
127
128 def _do_contrib_co(self, opts, args):
129     return self._do_contrib_checkout(opts, args)
130
131 def _do_contrib_checkout(self, opts, args):
132     package = args[0]
133     if not package:
134         raise oscerr.WrongArgs("The package names are mandatory!!")
135
136     request = self._sr_from_package(package, opts.id)
137
138     checkout_package(self.apiurl,
139             request.src_project, package,
140             expand_link=True, prj_dir=request.src_project)
141
142 # the original API is *very* ugly!!
143 # return the meta in an xml form first
144 def _get_meta_xml(self, package):
145     path = quote_plus(self.project),
146     kind = 'prj'
147     if package:
148         path = path + (quote_plus(package),)
149         kind = 'pkg'
150     data = meta_exists(metatype=kind,
151                        path_args=path,
152                        template_args=None,
153                        create_new=False)
154     if data:
155         return ET.fromstring(''.join(data))
156     raise oscerr.PackageError('Meta data for package %s missing' % (package))
157
158 # return all persons from meta
159 def _get_persons_from_meta(self, meta):
160     return meta.getiterator('person')
161
162 def _get_roles_from_meta(self, meta, role):
163     assert(role in ['maintainer', 'bugowner'])
164     return [p for p in self._get_persons_from_meta(meta) if p.get('role') == role]
165
166 def _has_user_role(self, meta, role, user):
167     assert(role in ['maintainer', 'bugowner'])
168     if not get_user_meta(self.apiurl, user):
169         raise oscerr.WrongArgs("The user %s doesn't exists" % (user))
170
171     return user in [p.get('userid') for p in self._get_roles_from_meta(meta, role)]
172
173 # from osc.core, FIXME, this is broken
174 # look at the svn, or send a patch to obs
175 def _addBugowner(self, apiurl, prj, pac, user):
176     """ add a new bugowner to a package or project """
177     path = quote_plus(prj),
178     kind = 'prj'
179     if pac:
180         path = path + (quote_plus(pac),)
181         kind = 'pkg'
182     data = meta_exists(metatype=kind,
183                        path_args=path,
184                        template_args=None,
185                        create_new=False)
186                        
187     if data and get_user_meta(apiurl, user) != None:
188         tree = ET.fromstring(''.join(data))
189         found = False
190         for person in tree.getiterator('person'):
191             if person.get('userid') == user and person.get('role') == 'bugowner':
192                 found = True
193                 print "user already exists"
194                 break
195         if not found:
196             # the xml has a fixed structure
197             tree.insert(2, ET.Element('person', role='bugowner', userid=user))
198             print 'user \'%s\' added to \'%s\'' % (user, pac or prj)
199             edit_meta(metatype=kind,
200                       path_args=path,
201                       data=ET.tostring(tree))
202     else:
203         print "osc: an error occured"
204
205 def _contrib_sr_change(self, opts, args, action, message):
206
207     if len(args) == 0:
208         raise oscerr.WrongArgs('The package name is mandatory for %s' % (action))
209
210     package = args[0]
211     request = self._sr_from_package(package, opts.id)
212     
213     id = str(request.reqid)
214
215     # check the current state
216     sr = get_submit_request(self.apiurl, id)
217
218     if sr.state.name != 'new':
219         print "The state of %s request was changed to '%s' by '%s'" % (package, sr.state.name, sr.state.who)
220         res = raw_input("Do you want to change it to '%s'? [y/N] " % (action))
221         if res != 'y' and res != 'Y':
222             return
223
224     # change to the state action
225     response = change_submit_request_state(self.apiurl, id, action, message)
226     
227     # change the state for a new packages
228     if action == 'accepted' and not package in meta_get_packagelist(self.apiurl, self.project):
229         # fix the maintainer and a bugowner
230         meta = self._get_meta_xml(package)
231         who = sr.statehistory[0].who
232         if not self._has_user_role(meta, 'maintainer', who):
233             delMaintainer(self.apiurl, self.project, package, sr.state.who)
234             addMaintainer(self.apiurl, self.project, package, who)
235         if not self._has_user_role(meta, 'bugowner', who):
236             self._addBugowner(self.apiurl, self.project, package, who)
237
238     print(response)