Bodhi directive created. It provides action download, which downloads rpms
from builds of specified bodhi update.
Bodhi utils are used for communitation with bodhi
kparal | |
tflink | |
mkrizek | |
jskladan |
Bodhi directive created. It provides action download, which downloads rpms
from builds of specified bodhi update.
Bodhi utils are used for communitation with bodhi
Tests for both b.-directive and b.-utils are included
Lint Skipped |
Unit Tests Skipped |
Overall, it looks pretty good. The runner args may need to be updated if we decide to go with more generic item and item_type route in T106 but we can deal with that later.
libtaskotron/directives/bodhi_directive.py | ||
---|---|---|
57 | I'd like to see this extracted into a standalone method since we'll be adding more code for bodhi comments etc. | |
81 | This is a bit more complex than it needs to be, I think it'd be better as: [build['nvr'] for build in res['builds]] |
Path | |||
---|---|---|---|
A | M | libtaskotron/bodhi_utils.py (30 lines) | |
A | M | libtaskotron/directives/bodhi_directive.py (93 lines) | |
M | libtaskotron/runner.py (13 lines) | ||
A | M | testing/test_bodhi_directive.py (164 lines) | |
A | M | testing/test_bodhi_utils.py (54 lines) |
Commit | Tree | Parents | Author | Summary | Date |
---|---|---|---|---|---|
75156613e87f | 38e542e5bcee | 34ec8783d7d8 | Petr Schindler | action downloaded put in the standalone method | Mar 28 2014, 2:01 PM |
34ec8783d7d8 | 2ec27c1de616 | b55c05399ad6 | Petr Schindler | T47 bodhi download (Show More…) | Mar 26 2014, 3:24 PM |
b55c05399ad6 | 1a4e4e609ef6 | 19e74ed4d5d4 | Petr Schindler | Final update, huraaaaaaaaaa | Mar 26 2014, 3:22 PM |
19e74ed4d5d4 | 039862be175d | c164bf49f128 | Petr Schindler | update | Mar 26 2014, 3:10 PM |
c164bf49f128 | 244678413f33 | 33ea169b810e | Petr Schindler | Comments added | Mar 26 2014, 1:37 PM |
33ea169b810e | ea87936068ef | edff5b5d0f59 | Petr Schindler | Unit tests finished, known bugs fixed | Mar 18 2014, 3:21 PM |
edff5b5d0f59 | 6bfbb710cd42 | ae6c0bfebac6 | Petr Schindler | Logging and one test added (Show More…) | Mar 12 2014, 3:31 PM |
ae6c0bfebac6 | 26ca424aa883 | 8158705593ea | Petr Schindler | Bodhi directive and b. utils created (Show More…) | Mar 12 2014, 9:52 AM |
1 | # -*- coding: utf-8 -*- | ||||
---|---|---|---|---|---|
2 | # Copyright 2010-2014, Red Hat, Inc. | ||||
3 | # License: GNU General Public License version 2 or later | ||||
4 | | ||||
5 | '''Utility functions for dealing with Bodhi''' | ||||
6 | | ||||
7 | import fedora.client | ||||
8 | | ||||
9 | from .logger import log | ||||
10 | | ||||
11 | bodhi = fedora.client.bodhi.BodhiClient() | ||||
12 | | ||||
13 | | ||||
14 | def query_update(package): | ||||
15 | '''Get the last Bodhi update matching criteria. | ||||
16 | | ||||
17 | @param package package NVR or package name or update title or update ID | ||||
18 | Note: Only NVR allowed, not ENVR. See | ||||
19 | https://fedorahosted.org/bodhi/ticket/592. | ||||
20 | | ||||
21 | @return most recent Bodhi update object matching criteria, or None | ||||
22 | when no update is found. | ||||
23 | ''' | ||||
24 | log.debug('Searching Bodhi updates for: %s', package) | ||||
25 | res = bodhi.query(package=package, limit=1) | ||||
26 | if res['updates']: | ||||
27 | return res['updates'][0] | ||||
28 | else: | ||||
29 | return None | ||||
30 | |
1 | # -*- coding: utf-8 -*- | ||||
---|---|---|---|---|---|
2 | # Copyright 2014, Red Hat, Inc. | ||||
3 | # License: GNU General Public License version 2 or later | ||||
4 | | ||||
5 | import libtaskotron.bodhi_utils as bodhi | ||||
6 | from libtaskotron.koji_utils import KojiClient | ||||
7 | from libtaskotron.directives import BaseDirective | ||||
8 | | ||||
9 | from libtaskotron.logger import log | ||||
10 | from libtaskotron.exceptions import TaskotronDirectiveError | ||||
11 | | ||||
12 | directive_class = 'BodhiDirective' | ||||
13 | | ||||
14 | class BodhiDirective(BaseDirective): | ||||
15 | """The bodhi directive interfaces with Bodhi to facilitate | ||||
16 | various bodhi actions. | ||||
17 | | ||||
18 | format: "bodhi: | ||||
19 | action: [download] | ||||
20 | <command args>" | ||||
21 | | ||||
22 | Valid Commands: | ||||
23 | | ||||
24 | download | ||||
25 | Downloads rpms of builds from update specified by its name or id | ||||
26 | format: "bodhi: | ||||
27 | action: download | ||||
28 | bodhi-id: <name or id of update> | ||||
29 | arch=<desired arch>" | ||||
30 | """ | ||||
31 | def __init__(self, bodhi_api=None, koji_session=None): | ||||
32 | | ||||
33 | if bodhi_api: | ||||
34 | self.bodhi_api = bodhi_api | ||||
35 | else: | ||||
36 | self.bodhi_api = bodhi | ||||
37 | | ||||
38 | if koji_session: | ||||
39 | self.koji_session = koji_session | ||||
40 | else: | ||||
41 | self.koji_session = KojiClient() | ||||
42 | | ||||
43 | def action_download(self, update, arches, workdir): | ||||
44 | | ||||
45 | res = self.bodhi_api.query_update(update) | ||||
46 | | ||||
47 | if res is None: | ||||
48 | raise TaskotronDirectiveError("Update with id '%s' wasn't found" | ||||
49 | % update) | ||||
50 | | ||||
51 | nvrs = [build['nvr'] for build in res['builds']] | ||||
52 | | ||||
53 | downloaded_rpms = [] | ||||
54 | | ||||
55 | for nvr in nvrs: | ||||
56 | res = self.koji_session.get_nvr_rpms(nvr, workdir, arches) | ||||
57 | downloaded_rpms.extend(res) | ||||
58 | | ||||
59 | return downloaded_rpms | ||||
60 | | ||||
61 | def process(self, input_data, env_data): | ||||
62 | output_data = {} | ||||
63 | | ||||
64 | valid_actions = ['download'] | ||||
65 | | ||||
66 | action = input_data['action'] | ||||
67 | | ||||
68 | if action not in valid_actions: | ||||
69 | raise TaskotronDirectiveError('%s is not a valid command for bodhi directive' | ||||
70 | % action) | ||||
71 | | ||||
72 | if action == 'download': | ||||
73 | if 'bodhi_id' not in input_data or 'arch' not in input_data: | ||||
74 | detected_args = ', '.join(input_data.keys()) | ||||
75 | raise TaskotronDirectiveError("The bodhi directive 'download'" | ||||
76 | " requires both 'bodhi-id' and" | ||||
77 | " 'arch' arguments. Detected arguments: %s" % detected_args) | ||||
78 | | ||||
79 | workdir = env_data['workdir'] | ||||
80 | update = input_data['bodhi_id'] | ||||
81 | if input_data['arch'] == 'all': | ||||
82 | arches = ['i386', 'x86_64', 'noarch', 'armhfp'] | ||||
83 | else: | ||||
84 | arches = [input_data['arch']] | ||||
85 | if 'noarch' not in arches and arches != ['src']: | ||||
86 | arches.append('noarch') | ||||
87 | | ||||
88 | log.info("getting rpms for update %s (%s) and downloading to %s", | ||||
89 | update, arches, workdir) | ||||
90 | | ||||
91 | output_data['downloaded_rpms'] = self.action_download(update, arches, workdir) | ||||
92 | | ||||
93 | return output_data |
Show First 20 Lines • Show All 127 Lines • ▼ Show 20 Line(s) | 127 | def _validate_env(self): | |||
---|---|---|---|---|---|
128 | raise NotImplementedError("Environment validation is not" | 128 | raise NotImplementedError("Environment validation is not" | ||
129 | " yet implemented") | 129 | " yet implemented") | ||
130 | 130 | | |||
131 | 131 | | |||
132 | def get_argparser(): | 132 | def get_argparser(): | ||
133 | parser = argparse.ArgumentParser() | 133 | parser = argparse.ArgumentParser() | ||
134 | parser.add_argument("task", nargs=1, help="task to run") | 134 | parser.add_argument("task", nargs=1, help="task to run") | ||
135 | parser.add_argument("-a", "--arch", | 135 | parser.add_argument("-a", "--arch", | ||
136 | choices=["i386", "x86_64", "armhfp", "noarch"], help="" | 136 | choices=["i386", "x86_64", "armhfp", "noarch", "all"], | ||
137 | "Architecture to be used; support arches are x86,\n" | 137 | help=""" | ||
138 | "x86_64, and armhfp.\nIf omitted, defaults to noarch.") | 138 | Architecture to be used; support arches are x86, x86_64, and armhfp. | ||
139 | If omitted, defaults to noarch. If every architechtures are wanted | ||||
140 | use all. | ||||
141 | """) | ||||
139 | parser.add_argument("-e", "--envr", help="envr to be used as input") | 142 | parser.add_argument("-e", "--envr", help="envr to be used as input") | ||
140 | parser.add_argument("-t", "--tag", help="koji tag to be used as input") | 143 | parser.add_argument("-t", "--tag", help="koji tag to be used as input") | ||
144 | parser.add_argument("-b", "--bodhi-id", | ||||
145 | help=""" | ||||
146 | Identification of bodhi update. Update id and name could be used. | ||||
147 | """) | ||||
141 | return parser | 148 | return parser | ||
142 | 149 | | |||
143 | 150 | | |||
144 | def main(): | 151 | def main(): | ||
145 | parser = get_argparser() | 152 | parser = get_argparser() | ||
146 | args = parser.parse_args() | 153 | args = parser.parse_args() | ||
147 | 154 | | |||
148 | logger.init(level=logging.DEBUG) | 155 | logger.init(level=logging.DEBUG) | ||
Show All 14 Lines |
1 | # -*- coding: utf-8 -*- | ||||
---|---|---|---|---|---|
2 | # Copyright 2014, Red Hat, Inc. | ||||
3 | # License: GNU General Public License version 2 or later | ||||
4 | | ||||
5 | '''Unit tests for libtaskotron/directives/bodhi_directive.py''' | ||||
6 | | ||||
7 | import pytest | ||||
8 | from dingus import Dingus | ||||
9 | | ||||
10 | from libtaskotron.directives import bodhi_directive | ||||
11 | from libtaskotron.exceptions import TaskotronDirectiveError | ||||
12 | | ||||
13 | class TestBodhiDownloads(): | ||||
14 | | ||||
15 | def setup_method(self, method): | ||||
16 | '''Run this before every test invocation''' | ||||
17 | self.ref_arch = 'x86_64' | ||||
18 | self.ref_bodhi_id = 'FEDORA-1234-56789' | ||||
19 | self.ref_workdir = '/var/tmp/foo' | ||||
20 | self.ref_taskfile = '/foo/bar/task.yml' | ||||
21 | self.ref_nvr1 = 'foo-1.2.3.fc99' | ||||
22 | self.ref_nvr2 = 'bar-1.2.3.fc99' | ||||
23 | self.ref_update = {'title': 'Random update', | ||||
24 | 'builds': [{'nvr': self.ref_nvr1, 'package': {}}, | ||||
25 | {'nvr': self.ref_nvr2, 'package': {}} | ||||
26 | ], | ||||
27 | 'other_keys': {} | ||||
28 | } | ||||
29 | self.ref_input = {'action': 'download', | ||||
30 | 'arch': self.ref_arch, | ||||
31 | 'bodhi_id': self.ref_bodhi_id | ||||
32 | } | ||||
33 | self.ref_envdata = {'workdir': self.ref_workdir} | ||||
34 | self.ref_rpmfile = '%s/%s.rpm' % (self.ref_workdir, self.ref_nvr1) | ||||
35 | | ||||
36 | self.helper = bodhi_directive.BodhiDirective() | ||||
37 | | ||||
38 | | ||||
39 | def test_action_download_existing_update(self): | ||||
40 | '''Test download of existing update''' | ||||
41 | stub_bodhi = Dingus(query_update__returns = self.ref_update) | ||||
42 | stub_koji = Dingus(get_nvr_rpms__returns = [self.ref_rpmfile]) | ||||
43 | | ||||
44 | test_bodhi = bodhi_directive.BodhiDirective(stub_bodhi, stub_koji) | ||||
45 | | ||||
46 | output_data = test_bodhi.process(self.ref_input, self.ref_envdata) | ||||
47 | | ||||
48 | ref_output_data = {'downloaded_rpms': [self.ref_rpmfile, | ||||
49 | self.ref_rpmfile]} | ||||
50 | getrpms_calls = stub_koji.calls() | ||||
51 | requested_nvrs = map(lambda x: x[1][0], getrpms_calls) | ||||
52 | | ||||
53 | # checks whether get_nvr_rpms was called for every update | ||||
54 | # with correct nvrs | ||||
55 | assert len(getrpms_calls) == 2 | ||||
56 | assert requested_nvrs == [self.ref_nvr1, self.ref_nvr2] | ||||
57 | assert output_data == ref_output_data | ||||
58 | | ||||
59 | | ||||
60 | def test_action_download_all_archs(self): | ||||
61 | '''Test download when all arches are demanded''' | ||||
62 | self.ref_input['arch'] = 'all' | ||||
63 | | ||||
64 | stub_bodhi = Dingus(query_update__returns = self.ref_update) | ||||
65 | stub_koji = Dingus(get_nvr_rpms__returns = [self.ref_rpmfile]) | ||||
66 | | ||||
67 | test_bodhi = bodhi_directive.BodhiDirective(stub_bodhi, stub_koji) | ||||
68 | | ||||
69 | test_bodhi.process(self.ref_input, self.ref_envdata) | ||||
70 | | ||||
71 | ref_arches = set(['i386', 'x86_64', 'noarch', 'armhfp']) | ||||
72 | getrpms_calls = stub_koji.calls() | ||||
73 | req_arches = map(lambda x: x[1][2], getrpms_calls) | ||||
74 | | ||||
75 | # checks whether all get_nvr_rpms calls demanded all arches | ||||
76 | assert True == all(map(lambda x: set(x) == ref_arches, req_arches)) | ||||
77 | | ||||
78 | def test_action_download_source(self): | ||||
79 | '''Test download of source packages''' | ||||
80 | self.ref_input['arch'] = 'src' | ||||
81 | | ||||
82 | stub_bodhi = Dingus(query_update__returns = self.ref_update) | ||||
83 | stub_koji = Dingus(get_nvr_rpms__returns = [self.ref_rpmfile]) | ||||
84 | | ||||
85 | test_bodhi = bodhi_directive.BodhiDirective(stub_bodhi, stub_koji) | ||||
86 | | ||||
87 | test_bodhi.process(self.ref_input, self.ref_envdata) | ||||
88 | | ||||
89 | getrpms_calls = stub_koji.calls() | ||||
90 | req_arches = map(lambda x: x[1][2], getrpms_calls) | ||||
91 | | ||||
92 | # checks whether all get_nvr_rpms calls demanded only source pkgs | ||||
93 | assert all(map(lambda x: x == ['src'], req_arches)) | ||||
94 | | ||||
95 | def test_action_download_added_noarch(self): | ||||
96 | '''Test whether noarch is automaticaly added''' | ||||
97 | self.ref_input['arch'] = 'i386' | ||||
98 | | ||||
99 | stub_bodhi = Dingus(query_update__returns = self.ref_update) | ||||
100 | stub_koji = Dingus(get_nvr_rpms__returns = [self.ref_rpmfile]) | ||||
101 | | ||||
102 | test_bodhi = bodhi_directive.BodhiDirective(stub_bodhi, stub_koji) | ||||
103 | | ||||
104 | test_bodhi.process(self.ref_input, self.ref_envdata) | ||||
105 | | ||||
106 | ref_arches = set(['i386', 'noarch']) | ||||
107 | getrpms_calls = stub_koji.calls() | ||||
108 | req_arches = map(lambda x: x[1][2], getrpms_calls) | ||||
109 | | ||||
110 | # checks whether noarch is demanded in all get_nvr_rpms calls | ||||
111 | assert all(map(lambda x: 'noarch' in x, req_arches)) | ||||
112 | | ||||
113 | | ||||
114 | def test_action_download_nonexisting_update(self): | ||||
115 | '''Test whether exception is raised when no update is found''' | ||||
116 | stub_bodhi = Dingus(query_update__returns = None) | ||||
117 | stub_koji = Dingus(get_nvr_rpms__returns = "It shouldn't go this far") | ||||
118 | | ||||
119 | test_bodhi = bodhi_directive.BodhiDirective(stub_bodhi, stub_koji) | ||||
120 | | ||||
121 | # No update is found, b. directive should raise an Exception | ||||
122 | with pytest.raises(TaskotronDirectiveError): | ||||
123 | test_bodhi.process(self.ref_input, self.ref_envdata) | ||||
124 | | ||||
125 | | ||||
126 | def test_invalid_action(self): | ||||
127 | '''Test response on non-existing action''' | ||||
128 | | ||||
129 | self.ref_input['action'] = 'foo' | ||||
130 | | ||||
131 | stub_bodhi = Dingus() | ||||
132 | stub_koji = Dingus() | ||||
133 | | ||||
134 | test_bodhi = bodhi_directive.BodhiDirective(stub_bodhi, stub_koji) | ||||
135 | | ||||
136 | # Unknown action should raise an Exception | ||||
137 | with pytest.raises(TaskotronDirectiveError): | ||||
138 | test_bodhi.process(self.ref_input, self.ref_envdata) | ||||
139 | | ||||
140 | | ||||
141 | def test_action_download_insufficient_input_data(self): | ||||
142 | '''Test response on unsufficient input for download action''' | ||||
143 | ref_input = {'action': 'download'} | ||||
144 | | ||||
145 | stub_bodhi = Dingus() | ||||
146 | stub_koji = Dingus() | ||||
147 | | ||||
148 | test_bodhi = bodhi_directive.BodhiDirective(stub_bodhi, stub_koji) | ||||
149 | | ||||
150 | # Tests with no information | ||||
151 | with pytest.raises(TaskotronDirectiveError): | ||||
152 | test_bodhi.process(ref_input, self.ref_envdata) | ||||
153 | | ||||
154 | ref_input['arch'] = self.ref_arch | ||||
155 | | ||||
156 | # Tests with only arch, or bodhi_id given | ||||
157 | with pytest.raises(TaskotronDirectiveError): | ||||
158 | test_bodhi.process(ref_input, self.ref_envdata) | ||||
159 | | ||||
160 | ref_input.pop('arch') | ||||
161 | ref_input['bodhi_id'] = self.ref_bodhi_id | ||||
162 | | ||||
163 | with pytest.raises(TaskotronDirectiveError): | ||||
164 | test_bodhi.process(ref_input, self.ref_envdata) |
1 | # -*- coding: utf-8 -*- | ||||
---|---|---|---|---|---|
2 | # Copyright 2014, Red Hat, Inc. | ||||
3 | # License: GNU General Public License version 2 or later | ||||
4 | | ||||
5 | '''Unit tests for libtaskotron/bodhi_utils.py''' | ||||
6 | | ||||
7 | import pytest | ||||
8 | from dingus import Dingus | ||||
9 | | ||||
10 | from libtaskotron import bodhi_utils | ||||
11 | | ||||
12 | class TestKojiDownloads(): | ||||
13 | | ||||
14 | def setup_method(self, method): | ||||
15 | '''Run this before every test invocation''' | ||||
16 | self.ref_bodhi_id = 'FEDORA-1234-56789' | ||||
17 | self.ref_update = {'title': 'Random update', | ||||
18 | 'builds': [{'nvr': 'foo-1.2-3.fc99', | ||||
19 | 'package': {}}], | ||||
20 | 'other_keys': {} | ||||
21 | } | ||||
22 | | ||||
23 | | ||||
24 | def test_query_existing_update(self, monkeypatch): | ||||
25 | '''Test query for existing update''' | ||||
26 | ref_query_answer = {'tg_flash': None, | ||||
27 | 'num_items': 1, | ||||
28 | 'title': '1 update found', | ||||
29 | 'updates': [self.ref_update] | ||||
30 | } | ||||
31 | stub_bodhi = Dingus(query__returns = ref_query_answer) | ||||
32 | | ||||
33 | monkeypatch.setattr(bodhi_utils, 'bodhi', stub_bodhi) | ||||
34 | | ||||
35 | update = bodhi_utils.query_update(self.ref_bodhi_id) | ||||
36 | | ||||
37 | assert update == self.ref_update | ||||
38 | | ||||
39 | def test_query_non_existing_update(self, monkeypatch): | ||||
40 | '''Test query for non-existing update''' | ||||
41 | ref_query_answer = {'tg_flash': None, | ||||
42 | 'num_items': 0, | ||||
43 | 'title': '0 updates found', | ||||
44 | 'updates': [] | ||||
45 | } | ||||
46 | stub_bodhi = Dingus(query__returns = ref_query_answer) | ||||
47 | | ||||
48 | monkeypatch.setattr(bodhi_utils, 'bodhi', stub_bodhi) | ||||
49 | | ||||
50 | update = bodhi_utils.query_update(self.ref_bodhi_id) | ||||
51 | | ||||
52 | assert update is None | ||||
53 | | ||||
54 | |
I'd like to see this extracted into a standalone method since we'll be adding more code for bodhi comments etc.