Merge tag '3.9.1' into debian/xena
castellan 3.9.1 release
meta:version: 3.9.1
meta:diff-start: -
meta:series: xena
meta:release-type: release
meta:pypi: no
meta:first: no
meta:release:Author: Hervé Beraud <hberaud@redhat.com>
meta:release:Commit: Hervé Beraud <hberaud@redhat.com>
meta:release:Change-Id: I63492f11606983f672011cafb03b21fe14fb0214
meta:release:Code-Review+2: Hervé Beraud <herveberaud.pro@gmail.com>
meta:release:Code-Review+2: Thierry Carrez <thierry@openstack.org>
meta:release:Workflow+1: Thierry Carrez <thierry@openstack.org>
Thomas Goirand
10 months ago
8 | 8 | |
9 | 9 | repos: |
10 | 10 | - repo: https://github.com/pre-commit/pre-commit-hooks |
11 | rev: ebc15addedad713c86ef18ae9632c88e187dd0af # v3.1.0 | |
11 | rev: 9136088a246768144165fcc3ecc3d31bb686920a # v3.3.0 | |
12 | 12 | hooks: |
13 | 13 | - id: trailing-whitespace |
14 | 14 | # Replaces or checks mixed line ending |
26 | 26 | - id: debug-statements |
27 | 27 | - id: check-yaml |
28 | 28 | files: .*\.(yaml|yml)$ |
29 | - repo: https://gitlab.com/pycqa/flake8 | |
30 | rev: 181bb46098dddf7e2d45319ea654b4b4d58c2840 # 3.8.3 | |
29 | - repo: local | |
31 | 30 | hooks: |
32 | 31 | - id: flake8 |
32 | name: flake8 | |
33 | 33 | additional_dependencies: |
34 | 34 | - hacking>=3.0.1,<3.1.0 |
35 | language: python | |
36 | entry: flake8 | |
37 | files: '^.*\.py$' | |
38 | exclude: '^(doc|releasenotes|tools)/.*$' |
58 | 58 | - barbican-tempest-plugin-simple-crypto-castellan-src |
59 | 59 | templates: |
60 | 60 | - check-requirements |
61 | - openstack-python3-wallaby-jobs | |
61 | - openstack-python3-xena-jobs | |
62 | 62 | - periodic-stable-jobs |
63 | 63 | - publish-openstack-docs-pti |
64 | 64 | - release-notes-jobs-python3 |
16 | 16 | |
17 | 17 | .. _`Openstack Developers Guide`: https://docs.openstack.org/infra/manual/developers.html |
18 | 18 | |
19 | Freenode IRC (Chat) | |
20 | ------------------- | |
21 | You can find Castellaneers in our publicly accessible channel on `freenode`_ | |
19 | OFTC IRC (Chat) | |
20 | --------------- | |
21 | You can find Castellaneers in our publicly accessible channel on `OFTC`_ | |
22 | 22 | ``#openstack-barbican``. All conversations are logged and stored for your |
23 | 23 | convenience at `eavesdrop.openstack.org`_. For more information regarding |
24 | 24 | OpenStack IRC channels please visit the `OpenStack IRC Wiki`_. |
25 | 25 | |
26 | .. _`freenode`: https://freenode.net | |
26 | .. _`OFTC`: https://oftc.net | |
27 | 27 | .. _`OpenStack IRC Wiki`: https://wiki.openstack.org/wiki/IRC |
28 | 28 | .. _`eavesdrop.openstack.org`: http://eavesdrop.openstack.org/irclogs/ |
29 | 29 | %23openstack-barbican/ |
1 | 1 | # see https://docs.openstack.org/infra/bindep/ for additional information. |
2 | 2 | |
3 | 3 | unzip |
4 | ||
5 | # PDF Docs package dependencies | |
6 | tex-gyre [doc platform:dpkg] |
24 | 24 | from cryptography import x509 as cryptography_x509 |
25 | 25 | from keystoneauth1 import identity |
26 | 26 | from keystoneauth1 import loading |
27 | from keystoneauth1 import service_token | |
27 | 28 | from keystoneauth1 import session |
28 | 29 | from oslo_config import cfg |
29 | 30 | from oslo_log import log as logging |
76 | 77 | choices=['public', 'internal', 'admin'], |
77 | 78 | help='Specifies the type of endpoint. Allowed values are: ' |
78 | 79 | 'public, private, and admin'), |
79 | ||
80 | cfg.StrOpt('barbican_region_name', | |
81 | default=None, | |
82 | help='Specifies the region of the chosen endpoint.'), | |
83 | cfg.BoolOpt('send_service_user_token', | |
84 | default=False, | |
85 | help=""" | |
86 | When True, if sending a user token to a REST API, also send a service token. | |
87 | ||
88 | Nova often reuses the user token provided to the nova-api to talk to other REST | |
89 | APIs, such as Cinder, Glance and Neutron. It is possible that while the user | |
90 | token was valid when the request was made to Nova, the token may expire before | |
91 | it reaches the other service. To avoid any failures, and to make it clear it is | |
92 | Nova calling the service on the user's behalf, we include a service token along | |
93 | with the user token. Should the user's token have expired, a valid service | |
94 | token ensures the REST API request will still be accepted by the keystone | |
95 | middleware. | |
96 | """), | |
80 | 97 | ] |
81 | 98 | |
99 | ||
82 | 100 | _BARBICAN_OPT_GROUP = 'barbican' |
101 | _BARBICAN_SERVICE_USER_OPT_GROUP = 'barbican_service_user' | |
83 | 102 | |
84 | 103 | LOG = logging.getLogger(__name__) |
85 | 104 | |
94 | 113 | self.conf.register_opts(_barbican_opts, group=_BARBICAN_OPT_GROUP) |
95 | 114 | loading.register_session_conf_options(self.conf, _BARBICAN_OPT_GROUP) |
96 | 115 | |
116 | loading.register_session_conf_options(self.conf, | |
117 | _BARBICAN_SERVICE_USER_OPT_GROUP) | |
118 | loading.register_auth_conf_options(self.conf, | |
119 | _BARBICAN_SERVICE_USER_OPT_GROUP) | |
120 | ||
97 | 121 | def _get_barbican_client(self, context): |
98 | 122 | """Creates a client to connect to the Barbican service. |
99 | 123 | |
140 | 164 | |
141 | 165 | def _get_keystone_auth(self, context): |
142 | 166 | if context.__class__.__name__ == 'KeystonePassword': |
143 | return identity.Password( | |
167 | auth = identity.Password( | |
144 | 168 | auth_url=context.auth_url, |
145 | 169 | username=context.username, |
146 | 170 | password=context.password, |
156 | 180 | project_domain_name=context.project_domain_name, |
157 | 181 | reauthenticate=context.reauthenticate) |
158 | 182 | elif context.__class__.__name__ == 'KeystoneToken': |
159 | return identity.Token( | |
183 | auth = identity.Token( | |
160 | 184 | auth_url=context.auth_url, |
161 | 185 | token=context.token, |
162 | 186 | trust_id=context.trust_id, |
171 | 195 | # projects begin to use utils.credential_factory |
172 | 196 | elif context.__class__.__name__ == 'RequestContext': |
173 | 197 | if getattr(context, 'get_auth_plugin', None): |
174 | return context.get_auth_plugin() | |
198 | auth = context.get_auth_plugin() | |
175 | 199 | else: |
176 | return identity.Token( | |
200 | auth = identity.Token( | |
177 | 201 | auth_url=self.conf.barbican.auth_endpoint, |
178 | 202 | token=context.auth_token, |
179 | 203 | project_id=context.project_id, |
186 | 210 | LOG.error(msg) |
187 | 211 | raise exception.Forbidden(reason=msg) |
188 | 212 | |
213 | if self.conf.barbican.send_service_user_token: | |
214 | service_auth = loading.load_auth_from_conf_options( | |
215 | self.conf, | |
216 | group=_BARBICAN_SERVICE_USER_OPT_GROUP) | |
217 | auth = service_token.ServiceTokenAuthWrapper( | |
218 | user_auth=auth, | |
219 | service_auth=service_auth) | |
220 | ||
221 | return auth | |
222 | ||
189 | 223 | def _get_barbican_endpoint(self, auth, sess): |
190 | barbican = self.conf.barbican | |
191 | if barbican.barbican_endpoint: | |
192 | return barbican.barbican_endpoint | |
224 | if self.conf.barbican.barbican_endpoint: | |
225 | return self.conf.barbican.barbican_endpoint | |
193 | 226 | elif getattr(auth, 'service_catalog', None): |
194 | 227 | endpoint_data = auth.service_catalog.endpoint_data_for( |
195 | 228 | service_type='key-manager', |
196 | interface=barbican.barbican_endpoint_type) | |
229 | interface=self.conf.barbican.barbican_endpoint_type, | |
230 | region_name=self.conf.barbican.barbican_region_name) | |
197 | 231 | return endpoint_data.url |
198 | 232 | else: |
199 | service_parameters = {'service_type': 'key-manager', | |
200 | 'interface': barbican.barbican_endpoint_type} | |
201 | return auth.get_endpoint(sess, **service_parameters) | |
233 | return auth.get_endpoint( | |
234 | sess, | |
235 | service_type='key-manager', | |
236 | interface=self.conf.barbican.barbican_endpoint_type, | |
237 | region_name=self.conf.barbican.barbican_region_name) | |
202 | 238 | |
203 | 239 | def _create_base_url(self, auth, sess, endpoint): |
204 | 240 | api_version = None |
206 | 242 | api_version = self.conf.barbican.barbican_api_version |
207 | 243 | elif getattr(auth, 'service_catalog', None): |
208 | 244 | endpoint_data = auth.service_catalog.endpoint_data_for( |
209 | service_type='key-manager') | |
245 | service_type='key-manager', | |
246 | interface=self.conf.barbican.barbican_endpoint_type, | |
247 | region_name=self.conf.barbican.barbican_region_name) | |
210 | 248 | api_version = endpoint_data.api_version |
211 | 249 | elif getattr(auth, 'get_discovery', None): |
212 | 250 | discovery = auth.get_discovery(sess, url=endpoint) |
645 | 683 | return objects |
646 | 684 | |
647 | 685 | def list_options_for_discovery(self): |
648 | return [(_BARBICAN_OPT_GROUP, _barbican_opts)] | |
686 | barbican_service_user_opts = loading.get_session_conf_options() | |
687 | barbican_service_user_opts += loading.get_auth_common_conf_options() | |
688 | ||
689 | return [ | |
690 | (_BARBICAN_OPT_GROUP, _barbican_opts), | |
691 | (_BARBICAN_SERVICE_USER_OPT_GROUP, barbican_service_user_opts), | |
692 | ] |
19 | 19 | from unittest import mock |
20 | 20 | |
21 | 21 | from barbicanclient import exceptions as barbican_exceptions |
22 | from keystoneauth1 import identity | |
23 | from keystoneauth1 import service_token | |
24 | from oslo_context import context | |
22 | 25 | from oslo_utils import timeutils |
23 | 26 | |
24 | 27 | from castellan.common import exception |
36 | 39 | super(BarbicanKeyManagerTestCase, self).setUp() |
37 | 40 | |
38 | 41 | # Create fake auth_token |
39 | self.ctxt = mock.Mock() | |
42 | self.ctxt = mock.Mock(spec=context.RequestContext) | |
40 | 43 | self.ctxt.auth_token = "fake_token" |
44 | self.ctxt.project_name = "foo" | |
45 | self.ctxt.project_domain_name = "foo" | |
41 | 46 | |
42 | 47 | # Create mock barbican client |
43 | 48 | self._build_mock_barbican() |
74 | 79 | |
75 | 80 | self.key_mgr._barbican_client = self.mock_barbican |
76 | 81 | self.key_mgr._current_context = self.ctxt |
82 | ||
83 | def test_barbican_endpoint(self): | |
84 | endpoint_data = mock.Mock() | |
85 | endpoint_data.url = 'http://localhost:9311' | |
86 | ||
87 | auth = mock.Mock(spec=['service_catalog']) | |
88 | auth.service_catalog.endpoint_data_for.return_value = endpoint_data | |
89 | ||
90 | endpoint = self.key_mgr._get_barbican_endpoint(auth, mock.Mock()) | |
91 | self.assertEqual(endpoint, 'http://localhost:9311') | |
92 | auth.service_catalog.endpoint_data_for.assert_called_once_with( | |
93 | service_type='key-manager', interface='public', | |
94 | region_name=None) | |
95 | ||
96 | def test_barbican_endpoint_with_endpoint_type(self): | |
97 | self.key_mgr.conf.barbican.barbican_endpoint_type = 'internal' | |
98 | ||
99 | endpoint_data = mock.Mock() | |
100 | endpoint_data.url = 'http://localhost:9311' | |
101 | ||
102 | auth = mock.Mock(spec=['service_catalog']) | |
103 | auth.service_catalog.endpoint_data_for.return_value = endpoint_data | |
104 | ||
105 | endpoint = self.key_mgr._get_barbican_endpoint(auth, mock.Mock()) | |
106 | self.assertEqual(endpoint, 'http://localhost:9311') | |
107 | auth.service_catalog.endpoint_data_for.assert_called_once_with( | |
108 | service_type='key-manager', interface='internal', | |
109 | region_name=None) | |
110 | ||
111 | def test_barbican_endpoint_with_region_name(self): | |
112 | self.key_mgr.conf.barbican.barbican_region_name = 'regionOne' | |
113 | ||
114 | endpoint_data = mock.Mock() | |
115 | endpoint_data.url = 'http://localhost:9311' | |
116 | ||
117 | auth = mock.Mock(spec=['service_catalog']) | |
118 | auth.service_catalog.endpoint_data_for.return_value = endpoint_data | |
119 | ||
120 | endpoint = self.key_mgr._get_barbican_endpoint(auth, mock.Mock()) | |
121 | self.assertEqual(endpoint, 'http://localhost:9311') | |
122 | auth.service_catalog.endpoint_data_for.assert_called_once_with( | |
123 | service_type='key-manager', interface='public', | |
124 | region_name='regionOne') | |
125 | ||
126 | def test_barbican_endpoint_from_config(self): | |
127 | self.key_mgr.conf.barbican.barbican_endpoint = 'http://localhost:9311' | |
128 | ||
129 | endpoint = self.key_mgr._get_barbican_endpoint( | |
130 | mock.Mock(), mock.Mock()) | |
131 | self.assertEqual(endpoint, 'http://localhost:9311') | |
132 | ||
133 | def test_barbican_endpoint_by_get_endpoint(self): | |
134 | auth = mock.Mock(spec=['get_endppint']) | |
135 | sess = mock.Mock() | |
136 | auth.get_endpoint = mock.Mock(return_value='http://localhost:9311') | |
137 | ||
138 | endpoint = self.key_mgr._get_barbican_endpoint(auth, sess) | |
139 | self.assertEqual(endpoint, 'http://localhost:9311') | |
140 | auth.get_endpoint.assert_called_once_with( | |
141 | sess, service_type='key-manager', interface='public', | |
142 | region_name=None) | |
143 | ||
144 | def test_barbican_endpoint_by_get_endpoint_with_endpoint_type(self): | |
145 | self.key_mgr.conf.barbican.barbican_endpoint_type = 'internal' | |
146 | ||
147 | auth = mock.Mock(spec=['get_endppint']) | |
148 | sess = mock.Mock() | |
149 | auth.get_endpoint = mock.Mock(return_value='http://localhost:9311') | |
150 | ||
151 | endpoint = self.key_mgr._get_barbican_endpoint(auth, sess) | |
152 | self.assertEqual(endpoint, 'http://localhost:9311') | |
153 | auth.get_endpoint.assert_called_once_with( | |
154 | sess, service_type='key-manager', interface='internal', | |
155 | region_name=None) | |
156 | ||
157 | def test_barbican_endpoint_by_get_endpoint_with_region_name(self): | |
158 | self.key_mgr.conf.barbican.barbican_region_name = 'regionOne' | |
159 | ||
160 | auth = mock.Mock(spec=['get_endppint']) | |
161 | sess = mock.Mock() | |
162 | auth.get_endpoint = mock.Mock(return_value='http://localhost:9311') | |
163 | ||
164 | endpoint = self.key_mgr._get_barbican_endpoint(auth, sess) | |
165 | self.assertEqual(endpoint, 'http://localhost:9311') | |
166 | auth.get_endpoint.assert_called_once_with( | |
167 | sess, service_type='key-manager', interface='public', | |
168 | region_name='regionOne') | |
169 | ||
170 | def test__get_keystone_auth(self): | |
171 | auth = self.key_mgr._get_keystone_auth(self.ctxt) | |
172 | self.assertIsInstance(auth, identity.Token) | |
173 | ||
174 | def test__get_keystone_auth_service_user(self): | |
175 | self.key_mgr.conf.barbican.send_service_user_token = True | |
176 | auth = self.key_mgr._get_keystone_auth(self.ctxt) | |
177 | self.assertIsInstance(auth, service_token.ServiceTokenAuthWrapper) | |
77 | 178 | |
78 | 179 | def test_base_url_old_version(self): |
79 | 180 | version = "v1" |
107 | 208 | endpoint) |
108 | 209 | self.assertEqual(endpoint + "/" + endpoint_data.api_version, base_url) |
109 | 210 | auth.service_catalog.endpoint_data_for.assert_called_once_with( |
110 | service_type='key-manager') | |
211 | service_type='key-manager', interface='public', | |
212 | region_name=None) | |
213 | ||
214 | def test_base_url_service_catalog_with_endpoint_type(self): | |
215 | self.key_mgr.conf.barbican.barbican_endpoint_type = 'internal' | |
216 | ||
217 | endpoint_data = mock.Mock() | |
218 | endpoint_data.api_version = 'v321' | |
219 | ||
220 | auth = mock.Mock(spec=['service_catalog']) | |
221 | auth.service_catalog.endpoint_data_for.return_value = endpoint_data | |
222 | ||
223 | endpoint = "http://localhost/key_manager" | |
224 | ||
225 | base_url = self.key_mgr._create_base_url(auth, | |
226 | mock.Mock(), | |
227 | endpoint) | |
228 | self.assertEqual(endpoint + "/" + endpoint_data.api_version, base_url) | |
229 | auth.service_catalog.endpoint_data_for.assert_called_once_with( | |
230 | service_type='key-manager', interface='internal', | |
231 | region_name=None) | |
232 | ||
233 | def test_base_url_service_catalog_with_region_name(self): | |
234 | self.key_mgr.conf.barbican.barbican_region_name = 'regionOne' | |
235 | ||
236 | endpoint_data = mock.Mock() | |
237 | endpoint_data.api_version = 'v321' | |
238 | ||
239 | auth = mock.Mock(spec=['service_catalog']) | |
240 | auth.service_catalog.endpoint_data_for.return_value = endpoint_data | |
241 | ||
242 | endpoint = "http://localhost/key_manager" | |
243 | ||
244 | base_url = self.key_mgr._create_base_url(auth, | |
245 | mock.Mock(), | |
246 | endpoint) | |
247 | self.assertEqual(endpoint + "/" + endpoint_data.api_version, base_url) | |
248 | auth.service_catalog.endpoint_data_for.assert_called_once_with( | |
249 | service_type='key-manager', interface='public', | |
250 | region_name='regionOne') | |
111 | 251 | |
112 | 252 | def test_base_url_raise_exception(self): |
113 | 253 | auth = mock.Mock(spec=['get_discovery']) |
480 | 620 | def test_list_with_invalid_object_type(self): |
481 | 621 | self.assertRaises(exception.KeyManagerError, |
482 | 622 | self.key_mgr.list, self.ctxt, "invalid_type") |
623 | ||
624 | def test_list_options_for_discovery(self): | |
625 | opts = self.key_mgr.list_options_for_discovery() | |
626 | expected_sections = ['barbican', 'barbican_service_user'] | |
627 | self.assertEqual(expected_sections, [section[0] for section in opts]) | |
628 | barbican_opts = [opt.name for opt in opts[0][1]] | |
629 | # From Castellan opts. | |
630 | self.assertIn('barbican_endpoint', barbican_opts) | |
631 | barbican_service_user_opts = [opt.name for opt in opts[1][1]] | |
632 | # From session opts. | |
633 | self.assertIn('cafile', barbican_service_user_opts) | |
634 | # From auth common opts. | |
635 | self.assertIn('auth_section', barbican_service_user_opts) |
0 | appdirs==1.3.0 | |
1 | asn1crypto==0.23.0 | |
2 | certifi==2020.4.5.2 | |
3 | cffi==1.14.0 | |
4 | chardet==3.0.4 | |
5 | cliff==2.8.0 | |
6 | cmd2==0.8.0 | |
7 | coverage==4.0 | |
8 | cryptography==2.7 | |
9 | debtcollector==1.2.0 | |
10 | entrypoints==0.3 | |
11 | extras==1.0.0 | |
12 | fixtures==3.0.0 | |
13 | future==0.18.2 | |
14 | gitdb==0.6.4 | |
15 | GitPython==1.0.1 | |
16 | idna==2.5 | |
17 | iso8601==0.1.11 | |
18 | keystoneauth1==3.4.0 | |
19 | linecache2==1.0.0 | |
20 | monotonic==0.6 | |
21 | mox3==0.20.0 | |
22 | msgpack-python==0.4.0 | |
23 | netaddr==0.7.18 | |
24 | netifaces==0.10.4 | |
25 | os-client-config==1.28.0 | |
26 | oslo.config==6.4.0 | |
27 | oslo.context==2.19.2 | |
28 | oslo.i18n==3.15.3 | |
29 | oslo.log==3.36.0 | |
30 | oslo.serialization==2.18.0 | |
31 | oslo.utils==3.33.0 | |
32 | oslotest==3.2.0 | |
33 | pbr==2.0.0 | |
34 | pifpaf==0.10.0 | |
35 | prettytable==0.7.2 | |
36 | pycparser==2.18 | |
37 | pyinotify==0.9.6 | |
38 | pyparsing==2.1.0 | |
39 | pyperclip==1.5.27 | |
40 | python-barbicanclient==4.5.2 | |
41 | python-dateutil==2.5.3 | |
42 | python-mimeparse==1.6.0 | |
43 | python-subunit==1.0.0 | |
44 | pytz==2013.6 | |
45 | PyYAML==3.13 | |
46 | requests==2.18.0 | |
47 | requestsexceptions==1.2.0 | |
48 | rfc3986==0.3.1 | |
49 | smmap==0.9.0 | |
50 | stestr==2.0.0 | |
51 | stevedore==1.20.0 | |
52 | testrepository==0.0.20 | |
53 | testscenarios==0.4 | |
54 | testtools==2.2.0 | |
55 | traceback2==1.4.0 | |
56 | unittest2==1.1.0 | |
57 | urllib3==1.21.1 | |
58 | voluptuous==0.11.7 | |
59 | wrapt==1.7.0 | |
60 | xattr==0.9.2 |
0 | --- | |
1 | features: | |
2 | - | | |
3 | Adds support for using a service user with the Barbican key manager. | |
4 | This is enabled via ``[barbican] send_service_user_token``, with | |
5 | credentials for the service user configured via keystoneauth options in the | |
6 | ``[barbican_service_user]`` group. |
0 | --- | |
1 | features: | |
2 | - | | |
3 | The new ``[barbican] barbican_region_name`` option has been added. | |
4 | This parameter is used to determine the proper Barbican endpoint in | |
5 | the multi-region deployment which has a different Barbican endpoint in | |
6 | each region. |
0 | ============================ | |
1 | Wallaby Series Release Notes | |
2 | ============================ | |
3 | ||
4 | .. release-notes:: | |
5 | :branch: stable/wallaby |
0 | 0 | [metadata] |
1 | 1 | name = castellan |
2 | 2 | summary = Generic Key Manager interface for OpenStack |
3 | description-file = | |
3 | description_file = | |
4 | 4 | README.rst |
5 | 5 | author = OpenStack |
6 | author-email = openstack-discuss@lists.openstack.org | |
7 | home-page = https://docs.openstack.org/castellan/latest/ | |
8 | python-requires = >=3.6 | |
6 | author_email = openstack-discuss@lists.openstack.org | |
7 | home_page = https://docs.openstack.org/castellan/latest/ | |
8 | python_requires = >=3.6 | |
9 | 9 | classifier = |
10 | 10 | Environment :: OpenStack |
11 | 11 | Intended Audience :: Information Technology |
0 | 0 | [tox] |
1 | minversion = 3.1.1 | |
1 | minversion = 3.18.0 | |
2 | 2 | envlist = py3,pep8 |
3 | 3 | ignore_basepython_conflict = True |
4 | 4 | skipsdist = True |
57 | 57 | commands= |
58 | 58 | rm -rf doc/build doc/build/doctrees |
59 | 59 | sphinx-build -W -b html -d doc/build/doctrees doc/source doc/build/html |
60 | whitelist_externals = rm | |
60 | allowlist_externals = rm | |
61 | 61 | |
62 | 62 | [testenv:pdf-docs] |
63 | 63 | deps = {[testenv:docs]deps} |
64 | 64 | envdir = {toxworkdir}/docs |
65 | whitelist_externals = | |
65 | allowlist_externals = | |
66 | 66 | rm |
67 | 67 | make |
68 | 68 | commands = |
106 | 106 | [hacking] |
107 | 107 | import_exceptions = castellan.i18n |
108 | 108 | |
109 | [testenv:lower-constraints] | |
110 | deps = | |
111 | -c{toxinidir}/lower-constraints.txt | |
112 | -r{toxinidir}/test-requirements.txt | |
113 | -r{toxinidir}/requirements.txt | |
114 | ||
115 | 109 | [testenv:bindep] |
116 | 110 | # Do not install any requirements. We want this to be fast and work even if |
117 | 111 | # system dependencies are missing, since it's used to tell you what system |