Merge ~jfguedez/charm-ubuntu-advantage:bug/1944591 into charm-ubuntu-advantage:master
- Git
- lp:~jfguedez/charm-ubuntu-advantage
- bug/1944591
- Merge into master
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Tom Haddon | ||||
Approved revision: | e8265de8062093dbb6764748172e236ab43ccdc9 | ||||
Merged at revision: | 2ff56b61e24664d01cb2ddd843474706b8ce2e31 | ||||
Proposed branch: | ~jfguedez/charm-ubuntu-advantage:bug/1944591 | ||||
Merge into: | charm-ubuntu-advantage:master | ||||
Diff against target: |
357 lines (+127/-44) 2 files modified
src/charm.py (+44/-6) tests/test_charm.py (+83/-38) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tom Haddon | Approve | ||
Canonical IS Reviewers | Pending | ||
Review via email: mp+415946@code.launchpad.net |
Commit message
Add support for proxy settings from the model
Description of the change
Supersedes this MP: https:/
Includes the original code/commit and additionally:
* Fixes the broken unit tests
* Add a new test for the new functionality
* Removes the proxy variables for the apt calls, as it's already supported by apt-http[s]-proxy model variables
* Adds the proxy config to the UA client itself, instead of installing it with proxy environment variables. This ensures it's functional after the charm installs it.
* Keeps the environment variables for the `add-apt-
Tested in Focal/Bionic/Xenial with and without proxy config in the juju model.
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Tom Haddon (mthaddon) wrote : | # |
A few comments/questions inline, otherwise looks good to me. Thanks.
Jose Guedez (jfguedez) wrote : | # |
Thanks for the review. I have replied to your comments and hopefully addressed the issues in comments/logging. Please take a look again when you have a chance.
Tom Haddon (mthaddon) wrote : | # |
I'm still a bit confused as to why we're setting this in `_setup_proxy_env` if UA client doesn't have an option for that:
self.env[
Can you clarify?
Jose Guedez (jfguedez) wrote (last edit ): | # |
@Tom, happy to clarify:
The charm needs connectivity via proxy for 4 different things:
1-To install the ppa (function `install_ppa`), which relies on the command `add-apt-
* juju-http-proxy => JUJU_CHARM_
* juju-https-proxy => JUJU_CHARM_
* juju-no-proxy => JUJU_CHARM_NO_PROXY => no_proxy
the values extracted from the environment in `_setup_proxy_env`
2-To install packages (function `install_
3-To install snaps (e.g. canonical-
4-To configure the UA client (functions `detach_
* juju-http-proxy => JUJU_CHARM_
* juju-https-proxy => JUJU_CHARM_
Hope that clarifies the different requirements of connectivity within the charm as I understand it. In the end it's trying to use the settings in the juju model, and either pass them to the functionality that is proxy aware: apt/snap are built into the model, UA has it's own configuration, and the PPA tool requires the information via the standard environment variables.
Tom Haddon (mthaddon) wrote : | # |
Thanks for the explanation.
Could you add a comment in _setup_proxy_env to explain that the http_proxy and https_proxy env variables will be used for package installs and ua, while no_proxy will only be used be used for package installs as it's not a supported variable of ua itself?
Jose Guedez (jfguedez) wrote : | # |
Thanks for the review. Added the comment as requested.
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision 2ff56b61e24664d
Preview Diff
1 | diff --git a/src/charm.py b/src/charm.py |
2 | index 25d7d0a..34878c0 100755 |
3 | --- a/src/charm.py |
4 | +++ b/src/charm.py |
5 | @@ -6,6 +6,7 @@ |
6 | import hashlib |
7 | import json |
8 | import logging |
9 | +import os |
10 | import subprocess |
11 | import yaml |
12 | |
13 | @@ -18,14 +19,14 @@ from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus |
14 | logger = logging.getLogger(__name__) |
15 | |
16 | |
17 | -def install_ppa(ppa): |
18 | +def install_ppa(ppa, env): |
19 | """Install specified ppa""" |
20 | - subprocess.check_call(["add-apt-repository", "--yes", ppa]) |
21 | + subprocess.check_call(["add-apt-repository", "--yes", ppa], env=env) |
22 | |
23 | |
24 | -def remove_ppa(ppa): |
25 | +def remove_ppa(ppa, env): |
26 | """Remove specified ppa""" |
27 | - subprocess.check_call(["add-apt-repository", "--remove", "--yes", ppa]) |
28 | + subprocess.check_call(["add-apt-repository", "--remove", "--yes", ppa], env=env) |
29 | |
30 | |
31 | def install_package(package): |
32 | @@ -74,13 +75,33 @@ class UbuntuAdvantageCharm(CharmBase): |
33 | |
34 | def __init__(self, *args): |
35 | super().__init__(*args) |
36 | + self._setup_proxy_env() |
37 | self._state.set_default( |
38 | contract_url=None, |
39 | hashed_token=None, |
40 | package_needs_installing=True, |
41 | ppa=None) |
42 | + |
43 | self.framework.observe(self.on.config_changed, self.config_changed) |
44 | |
45 | + def _setup_proxy_env(self): |
46 | + """Setup proxy variables from model""" |
47 | + self.env = dict(os.environ) |
48 | + self.env['http_proxy'] = self.env.get('JUJU_CHARM_HTTP_PROXY', '') |
49 | + self.env['https_proxy'] = self.env.get('JUJU_CHARM_HTTPS_PROXY', '') |
50 | + self.env['no_proxy'] = self.env.get('JUJU_CHARM_NO_PROXY', '') |
51 | + |
52 | + # The values for 'http_proxy' and 'https_proxy' will be used for the |
53 | + # PPA install/remove operations (passed as environment variables), as |
54 | + # well as for configuring the UA client. |
55 | + # The value for 'no_proxy' is only used by the PPA install/remove |
56 | + # operations (passed as an environment variable). |
57 | + |
58 | + # log proxy environment variables for debugging |
59 | + for envvar, value in self.env.items(): |
60 | + if envvar.endswith("proxy".lower()): |
61 | + logger.debug("Envvar '%s' => '%s'", envvar, value) |
62 | + |
63 | def config_changed(self, event): |
64 | """Install and configure ubuntu-advantage tools and attachment""" |
65 | logger.info("Beginning config_changed") |
66 | @@ -100,14 +121,14 @@ class UbuntuAdvantageCharm(CharmBase): |
67 | |
68 | if old_ppa and old_ppa != ppa: |
69 | logger.info("Removing previously installed ppa (%s)", old_ppa) |
70 | - remove_ppa(old_ppa) |
71 | + remove_ppa(old_ppa, self.env) |
72 | self._state.ppa = None |
73 | # If ppa is changed, want to remove the previous version of the package for consistency |
74 | self._state.package_needs_installing = True |
75 | |
76 | if ppa and ppa != old_ppa: |
77 | logger.info("Installing ppa: %s", ppa) |
78 | - install_ppa(ppa) |
79 | + install_ppa(ppa, self.env) |
80 | self._state.ppa = ppa |
81 | # If ppa is changed, want to force an install of the package for potential updates |
82 | self._state.package_needs_installing = True |
83 | @@ -132,6 +153,11 @@ class UbuntuAdvantageCharm(CharmBase): |
84 | old_contract_url = self._state.contract_url |
85 | config_changed = contract_url != old_contract_url |
86 | |
87 | + # Add the proxy configuration to the UA client. |
88 | + # This is needed for attach/detach/status commands used by the charm, |
89 | + # as well as regular tool operations. |
90 | + self._configure_ua_proxy() |
91 | + |
92 | status = get_status_output() |
93 | if status["attached"] and (config_changed or token_changed): |
94 | logger.info("Detaching ubuntu-advantage subscription") |
95 | @@ -165,6 +191,18 @@ class UbuntuAdvantageCharm(CharmBase): |
96 | message = "Attached (" + ",".join(services) + ")" |
97 | self.unit.status = ActiveStatus(message) |
98 | |
99 | + def _configure_ua_proxy(self): |
100 | + """Configure the proxy options for the ubuntu-advantage client""" |
101 | + for config_key in ("http_proxy", "https_proxy"): |
102 | + subprocess.check_call( |
103 | + [ |
104 | + "ubuntu-advantage", |
105 | + "config", |
106 | + "set", |
107 | + "{}={}".format(config_key, self.env[config_key]), |
108 | + ] |
109 | + ) |
110 | + |
111 | |
112 | if __name__ == "__main__": # pragma: nocover |
113 | main(UbuntuAdvantageCharm) |
114 | diff --git a/tests/test_charm.py b/tests/test_charm.py |
115 | index 1415f7d..f55d844 100644 |
116 | --- a/tests/test_charm.py |
117 | +++ b/tests/test_charm.py |
118 | @@ -63,6 +63,9 @@ log_level: debug |
119 | log_file: /var/log/ubuntu-advantage.log |
120 | """ |
121 | |
122 | +TEST_PROXY_URL = "http://squid.internal:3128" |
123 | +TEST_NO_PROXY = "127.0.0.1,localhost,::1" |
124 | + |
125 | |
126 | def _written(handle): |
127 | contents = "".join(["".join(a.args) for a in handle.write.call_args_list]) |
128 | @@ -76,7 +79,8 @@ class TestCharm(TestCase): |
129 | "call": patch("subprocess.call").start(), |
130 | "check_call": patch("subprocess.check_call").start(), |
131 | "check_output": patch("subprocess.check_output").start(), |
132 | - "open": patch("builtins.open").start() |
133 | + "open": patch("builtins.open").start(), |
134 | + "environ": patch.dict("os.environ", clear=True).start() |
135 | } |
136 | self.mocks["check_output"].side_effect = [ |
137 | STATUS_DETACHED |
138 | @@ -86,6 +90,7 @@ class TestCharm(TestCase): |
139 | self.harness = Harness(UbuntuAdvantageCharm) |
140 | self.addCleanup(self.harness.cleanup) |
141 | self.harness.begin() |
142 | + self.env = self.harness.charm.env |
143 | |
144 | def test_config_defaults(self): |
145 | self.assertEqual(self.harness.charm.config.get("contract_url"), |
146 | @@ -95,25 +100,25 @@ class TestCharm(TestCase): |
147 | |
148 | def test_config_changed_ppa_new(self): |
149 | self.harness.update_config({"ppa": "ppa:ua-client/stable"}) |
150 | - self.assertEqual(self.mocks["check_call"].call_count, 4) |
151 | - self.mocks["check_call"].assert_has_calls([ |
152 | - call(["add-apt-repository", "--yes", "ppa:ua-client/stable"]), |
153 | + self.assertEqual(self.mocks["check_call"].call_count, 6) |
154 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
155 | + call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env), |
156 | call(["apt", "remove", "--yes", "--quiet", "ubuntu-advantage-tools"]), |
157 | call(["apt", "update"]), |
158 | call(["apt", "install", "--yes", "--quiet", "ubuntu-advantage-tools"]) |
159 | - ]) |
160 | + ])) |
161 | self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable") |
162 | self.assertFalse(self.harness.charm._state.package_needs_installing) |
163 | |
164 | def test_config_changed_ppa_updated(self): |
165 | self.harness.update_config({"ppa": "ppa:ua-client/stable"}) |
166 | - self.assertEqual(self.mocks["check_call"].call_count, 4) |
167 | - self.mocks["check_call"].assert_has_calls([ |
168 | - call(["add-apt-repository", "--yes", "ppa:ua-client/stable"]), |
169 | + self.assertEqual(self.mocks["check_call"].call_count, 6) |
170 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
171 | + call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env), |
172 | call(["apt", "remove", "--yes", "--quiet", "ubuntu-advantage-tools"]), |
173 | call(["apt", "update"]), |
174 | call(["apt", "install", "--yes", "--quiet", "ubuntu-advantage-tools"]) |
175 | - ]) |
176 | + ])) |
177 | self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable") |
178 | self.assertFalse(self.harness.charm._state.package_needs_installing) |
179 | |
180 | @@ -122,26 +127,28 @@ class TestCharm(TestCase): |
181 | ] |
182 | self.mocks["check_call"].reset_mock() |
183 | self.harness.update_config({"ppa": "ppa:different-client/unstable"}) |
184 | - self.assertEqual(self.mocks["check_call"].call_count, 5) |
185 | - self.mocks["check_call"].assert_has_calls([ |
186 | - call(["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"]), |
187 | - call(["add-apt-repository", "--yes", "ppa:different-client/unstable"]), |
188 | + self.assertEqual(self.mocks["check_call"].call_count, 7) |
189 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
190 | + call(["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"], |
191 | + env=self.env), |
192 | + call(["add-apt-repository", "--yes", "ppa:different-client/unstable"], |
193 | + env=self.env), |
194 | call(["apt", "remove", "--yes", "--quiet", "ubuntu-advantage-tools"]), |
195 | call(["apt", "update"]), |
196 | call(["apt", "install", "--yes", "--quiet", "ubuntu-advantage-tools"]) |
197 | - ]) |
198 | + ])) |
199 | self.assertEqual(self.harness.charm._state.ppa, "ppa:different-client/unstable") |
200 | self.assertFalse(self.harness.charm._state.package_needs_installing) |
201 | |
202 | def test_config_changed_ppa_unmodified(self): |
203 | self.harness.update_config({"ppa": "ppa:ua-client/stable"}) |
204 | - self.assertEqual(self.mocks["check_call"].call_count, 4) |
205 | - self.mocks["check_call"].assert_has_calls([ |
206 | - call(["add-apt-repository", "--yes", "ppa:ua-client/stable"]), |
207 | + self.assertEqual(self.mocks["check_call"].call_count, 6) |
208 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
209 | + call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env), |
210 | call(["apt", "remove", "--yes", "--quiet", "ubuntu-advantage-tools"]), |
211 | call(["apt", "update"]), |
212 | call(["apt", "install", "--yes", "--quiet", "ubuntu-advantage-tools"]) |
213 | - ]) |
214 | + ])) |
215 | self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable") |
216 | self.assertFalse(self.harness.charm._state.package_needs_installing) |
217 | |
218 | @@ -151,19 +158,20 @@ class TestCharm(TestCase): |
219 | STATUS_DETACHED |
220 | ] |
221 | self.harness.update_config({"ppa": "ppa:ua-client/stable"}) |
222 | - self.assertEqual(self.mocks["check_call"].call_count, 0) |
223 | + self.assertEqual(self.mocks["check_call"].call_count, 2) |
224 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([])) |
225 | self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable") |
226 | self.assertFalse(self.harness.charm._state.package_needs_installing) |
227 | |
228 | def test_config_changed_ppa_unset(self): |
229 | self.harness.update_config({"ppa": "ppa:ua-client/stable"}) |
230 | - self.assertEqual(self.mocks["check_call"].call_count, 4) |
231 | - self.mocks["check_call"].assert_has_calls([ |
232 | - call(["add-apt-repository", "--yes", "ppa:ua-client/stable"]), |
233 | + self.assertEqual(self.mocks["check_call"].call_count, 6) |
234 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
235 | + call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env), |
236 | call(["apt", "remove", "--yes", "--quiet", "ubuntu-advantage-tools"]), |
237 | call(["apt", "update"]), |
238 | call(["apt", "install", "--yes", "--quiet", "ubuntu-advantage-tools"]) |
239 | - ]) |
240 | + ])) |
241 | self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable") |
242 | self.assertFalse(self.harness.charm._state.package_needs_installing) |
243 | |
244 | @@ -173,13 +181,14 @@ class TestCharm(TestCase): |
245 | STATUS_DETACHED |
246 | ] |
247 | self.harness.update_config({"ppa": ""}) |
248 | - self.assertEqual(self.mocks["check_call"].call_count, 4) |
249 | - self.mocks["check_call"].assert_has_calls([ |
250 | - call(["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"]), |
251 | + self.assertEqual(self.mocks["check_call"].call_count, 6) |
252 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
253 | + call(["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"], |
254 | + env=self.env), |
255 | call(["apt", "remove", "--yes", "--quiet", "ubuntu-advantage-tools"]), |
256 | call(["apt", "update"]), |
257 | call(["apt", "install", "--yes", "--quiet", "ubuntu-advantage-tools"]) |
258 | - ]) |
259 | + ])) |
260 | self.assertIsNone(self.harness.charm._state.ppa) |
261 | self.assertFalse(self.harness.charm._state.package_needs_installing) |
262 | |
263 | @@ -223,13 +232,13 @@ class TestCharm(TestCase): |
264 | STATUS_ATTACHED |
265 | ] |
266 | self.harness.update_config({"token": "test-token"}) |
267 | - self.assertEqual(self.mocks["check_call"].call_count, 4) |
268 | - self.mocks["check_call"].assert_has_calls([ |
269 | - call(["add-apt-repository", "--yes", "ppa:ua-client/stable"]), |
270 | + self.assertEqual(self.mocks["check_call"].call_count, 6) |
271 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
272 | + call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env), |
273 | call(["apt", "remove", "--yes", "--quiet", "ubuntu-advantage-tools"]), |
274 | call(["apt", "update"]), |
275 | call(["apt", "install", "--yes", "--quiet", "ubuntu-advantage-tools"]) |
276 | - ]) |
277 | + ])) |
278 | self.mocks["open"].assert_called_with("/etc/ubuntu-advantage/uaclient.conf", "r+") |
279 | handle = self.mocks["open"]() |
280 | expected = dedent("""\ |
281 | @@ -255,10 +264,10 @@ class TestCharm(TestCase): |
282 | STATUS_ATTACHED |
283 | ] |
284 | self.harness.update_config({"token": "test-token-2"}) |
285 | - self.assertEqual(self.mocks["check_call"].call_count, 1) |
286 | - self.mocks["check_call"].assert_has_calls([ |
287 | + self.assertEqual(self.mocks["check_call"].call_count, 3) |
288 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
289 | call(["ubuntu-advantage", "detach", "--assume-yes"]) |
290 | - ]) |
291 | + ], append=False)) |
292 | self.assertEqual(self.mocks["call"].call_count, 1) |
293 | self.mocks["call"].assert_has_calls([ |
294 | call(["ubuntu-advantage", "attach", "test-token-2"]) |
295 | @@ -295,10 +304,10 @@ class TestCharm(TestCase): |
296 | STATUS_DETACHED |
297 | ] |
298 | self.harness.update_config({"token": ""}) |
299 | - self.assertEqual(self.mocks["check_call"].call_count, 1) |
300 | - self.mocks["check_call"].assert_has_calls([ |
301 | + self.assertEqual(self.mocks["check_call"].call_count, 3) |
302 | + self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([ |
303 | call(["ubuntu-advantage", "detach", "--assume-yes"]) |
304 | - ]) |
305 | + ], append=False)) |
306 | self.assertEqual(self.mocks["call"].call_count, 0) |
307 | self.assertIsNone(self.harness.charm._state.hashed_token) |
308 | self.assertEqual(self.harness.model.unit.status, BlockedStatus("No token configured")) |
309 | @@ -335,7 +344,7 @@ class TestCharm(TestCase): |
310 | def test_config_changed_ppa_contains_newline(self): |
311 | self.harness.update_config({"ppa": "ppa:ua-client/stable\n"}) |
312 | self.mocks["check_call"].assert_has_calls([ |
313 | - call(["add-apt-repository", "--yes", "ppa:ua-client/stable"]), |
314 | + call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env), |
315 | ]) |
316 | self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable") |
317 | |
318 | @@ -460,3 +469,39 @@ class TestCharm(TestCase): |
319 | handle.truncate.assert_called_once() |
320 | self.mocks["call"].assert_not_called() |
321 | self.assertEqual(self.harness.model.unit.status, BlockedStatus("No token configured")) |
322 | + |
323 | + def test_setup_proxy_env(self): |
324 | + self.mocks["environ"].update( |
325 | + { |
326 | + "JUJU_CHARM_HTTP_PROXY": TEST_PROXY_URL, |
327 | + "JUJU_CHARM_HTTPS_PROXY": TEST_PROXY_URL, |
328 | + "JUJU_CHARM_NO_PROXY": TEST_NO_PROXY, |
329 | + } |
330 | + ) |
331 | + |
332 | + self.harness.charm._setup_proxy_env() |
333 | + self.assertEqual(self.harness.charm.env["http_proxy"], TEST_PROXY_URL) |
334 | + self.assertEqual(self.harness.charm.env["https_proxy"], TEST_PROXY_URL) |
335 | + self.assertEqual(self.harness.charm.env["no_proxy"], TEST_NO_PROXY) |
336 | + |
337 | + def _add_ua_proxy_setup_calls(self, call_list, append=True): |
338 | + """Helper to generate the calls used for UA proxy setup""" |
339 | + proxy_calls = [ |
340 | + call( |
341 | + [ |
342 | + "ubuntu-advantage", |
343 | + "config", |
344 | + "set", |
345 | + "http_proxy={}".format(self.env["http_proxy"]), |
346 | + ] |
347 | + ), |
348 | + call( |
349 | + [ |
350 | + "ubuntu-advantage", |
351 | + "config", |
352 | + "set", |
353 | + "https_proxy={}".format(self.env["https_proxy"]), |
354 | + ] |
355 | + ), |
356 | + ] |
357 | + return call_list + proxy_calls if append else proxy_calls + call_list |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.