Merge lp:~simpoir/landscape-charm/1682105_revisit_config into lp:~landscape/landscape-charm/trunk

Proposed by Simon Poirier
Status: Merged
Approved by: Simon Poirier
Approved revision: 406
Merged at revision: 408
Proposed branch: lp:~simpoir/landscape-charm/1682105_revisit_config
Merge into: lp:~landscape/landscape-charm/trunk
Diff against target: 374 lines (+225/-21)
5 files modified
config.yaml (+23/-0)
lib/callbacks/scripts.py (+43/-12)
lib/callbacks/tests/test_scripts.py (+144/-7)
lib/services.py (+7/-2)
lib/tests/stubs.py (+8/-0)
To merge this branch: bzr merge lp:~simpoir/landscape-charm/1682105_revisit_config
Reviewer Review Type Date Requested Status
🤖 Landscape Builder test results Approve
Kevin Nasto Approve
Review via email: mp+412618@code.launchpad.net

Commit message

Make proxy, system-email and root-url configurations updatable through the charm config.

Description of the change

Adds a few configuration to the charm.
Those were previously only updatable from the UI (proxy, system-email, root-url) or on bootstrap.

Requires changes which are fix-commited to landscape/trunk, for it to do anything.

Testing:

I have repackaged 19.10 with the trunk patch to actually be able to test this branch.
Trying to change those conf with the current 19.10 ppa will do nothing (the branch checks if the flag exists)

download https://pastebin.canonical.com/p/hk84D8kqtg/ as bundle.yml
juju deploy ./bundle.yml
juju config landscape-server system-email=noreply@localhost

log to the ui, check the settings tab.

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Needs Fixing (test results)
405. By Simon Poirier

lint

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Approve (test results)
Revision history for this message
Kevin Nasto (silverdrake11) wrote :

LGTM except for the typo in the yaml

review: Approve
406. By Simon Poirier

typo

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Approve (test results)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2021-11-09 09:20:31 +0000
3+++ config.yaml 2021-12-08 22:56:37 +0000
4@@ -99,6 +99,11 @@
5 IP of the first related HAproxy unit will be used instead.
6 type: string
7 default: ""
8+ system-email:
9+ type: string
10+ description: |
11+ The address emails from Landscape will appear to come from.
12+ default: ""
13 ssl-cert:
14 type: string
15 description: |
16@@ -117,6 +122,24 @@
17 description: |
18 The SMTP server to use to deliver outgoing mail.
19 default: ""
20+ http-proxy:
21+ default:
22+ type: string
23+ description: |
24+ The http proxy URL Landscape will use. If left blank, the
25+ model-config value will be used.
26+ https-proxy:
27+ default:
28+ type: string
29+ description: |
30+ The https proxy Landscape will use. If left blank, the model-config
31+ value will be used.
32+ no-proxy:
33+ default:
34+ type: string
35+ description: |
36+ Comma separated list of hosts for which no proxy should be used.
37+ If left blank, the model-config value will be used.
38 site-name:
39 type: string
40 default: ''
41
42=== modified file 'lib/callbacks/scripts.py'
43--- lib/callbacks/scripts.py 2019-07-10 19:02:28 +0000
44+++ lib/callbacks/scripts.py 2021-12-08 22:56:37 +0000
45@@ -1,4 +1,3 @@
46-import os
47 import subprocess
48
49 from charmhelpers.core import hookenv
50@@ -14,12 +13,15 @@
51 # Database relation keys for which, in case of change, a restart is not needed.
52 NO_RESTART_DB_RELATION_KEYS = {"allowed-units"}
53
54+CONFIG_ONLY_FLAG = "--configure-lds-only"
55+
56
57 class ScriptCallback(ManagerCallback):
58 """Callback class for invoking Landscape scripts."""
59
60- def __init__(self, subprocess=subprocess):
61+ def __init__(self, subprocess=subprocess, hookenv=hookenv):
62 self._subprocess = subprocess
63+ self._hookenv = hookenv
64
65 def _run(self, name, options=()):
66 """Run the script with the given name and options."""
67@@ -34,12 +36,21 @@
68 This will invoke the schema script with the --bootstrap flag, if it hasn't
69 been called yet.
70 """
71+
72 def __call__(self, manager, service_name, event_name):
73+ self._schema_doc = self._subprocess.check_output([SCHEMA_SCRIPT, "-h"])
74 if not manager.was_ready(service_name):
75 options = ["--bootstrap"]
76 options.extend(self._get_proxy_options())
77 self._run(SCHEMA_SCRIPT, options)
78
79+ if CONFIG_ONLY_FLAG in self._schema_doc:
80+ options = [CONFIG_ONLY_FLAG]
81+ options.extend(self._get_proxy_options())
82+ options.extend(self._get_settings_options())
83+ if len(options) > 1:
84+ self._run(SCHEMA_SCRIPT, options)
85+
86 def _get_proxy_options(self):
87 """Return the HTTP proxy options to set.
88
89@@ -48,15 +59,39 @@
90 at the environment variables that Juju sets for us.
91 """
92 options = []
93+ config = self._hookenv.config()
94
95- help_output = self._subprocess.check_output([SCHEMA_SCRIPT, "-h"])
96- if "--with-http-proxy" in help_output:
97+ if "--with-http-proxy" in self._schema_doc:
98 # Forward any proxy configuration set in the environment
99+ model_proxy = self._hookenv.env_proxy_settings() or {}
100 for proxy_variable in ("http_proxy", "https_proxy", "no_proxy"):
101- if proxy_variable in os.environ:
102- options.append("--with-%s=%s" % (
103- proxy_variable.replace("_", "-"),
104- os.environ[proxy_variable]))
105+ model_proxy_value = model_proxy.get(proxy_variable, "")
106+
107+ # XXX There is no charm hook for model-config changes,
108+ # so updating proxies has to be done using charm config.
109+ # See juju issue (LP: #1835050).
110+ proxy_variable = proxy_variable.replace("_", "-")
111+ proxy_value = config.get(proxy_variable)
112+ if proxy_value is None:
113+ proxy_value = model_proxy_value
114+ options.append("--with-{}".format(proxy_variable))
115+ options.append(proxy_value)
116+
117+ return options
118+
119+ def _get_settings_options(self):
120+ """Return options for updating charm-managed settings."""
121+ config = self._hookenv.config()
122+ options = []
123+ root_url = config.get("root-url")
124+ if root_url:
125+ options.append("--with-root-url")
126+ options.append(root_url)
127+
128+ system_email = config.get("system-email")
129+ if system_email:
130+ options.append("--with-system-email")
131+ options.append(system_email)
132
133 return options
134
135@@ -64,10 +99,6 @@
136 class LSCtl(ScriptCallback):
137 """Call the lsctl script to start or stop services."""
138
139- def __init__(self, subprocess=subprocess, hookenv=hookenv):
140- super(LSCtl, self).__init__(subprocess=subprocess)
141- self._hookenv = hookenv
142-
143 def __call__(self, manager, service_name, event_name):
144 current_status, current_status_message = self._hookenv.status_get()
145 action_status_message = ""
146
147=== modified file 'lib/callbacks/tests/test_scripts.py'
148--- lib/callbacks/tests/test_scripts.py 2019-07-15 20:01:06 +0000
149+++ lib/callbacks/tests/test_scripts.py 2021-12-08 22:56:37 +0000
150@@ -3,7 +3,7 @@
151 from charmhelpers.core.services.base import ServiceManager
152
153 from lib.paths import LSCTL, SCHEMA_SCRIPT
154-from lib.callbacks.scripts import SchemaBootstrap, LSCtl
155+from lib.callbacks.scripts import SchemaBootstrap, LSCtl, CONFIG_ONLY_FLAG
156 from lib.utils import update_persisted_data
157 from lib.tests.helpers import HookenvTest
158 from lib.tests.stubs import SubprocessStub
159@@ -19,7 +19,8 @@
160 self.subprocess = SubprocessStub()
161 self.subprocess.add_fake_executable(SCHEMA_SCRIPT)
162 self.manager = ServiceManager(services=[{"service": "landscape"}])
163- self.callback = SchemaBootstrap(subprocess=self.subprocess)
164+ self.callback = SchemaBootstrap(
165+ subprocess=self.subprocess, hookenv=self.hookenv)
166
167 def test_options(self):
168 """
169@@ -56,18 +57,154 @@
170 self.callback(self.manager, "landscape", None)
171 self.assertEqual(
172 ["/usr/bin/landscape-schema", "--bootstrap",
173- "--with-http-proxy=http://foo:3128",
174- "--with-https-proxy=http://bar:3128",
175- "--with-no-proxy=localhost"],
176+ "--with-http-proxy", "http://foo:3128",
177+ "--with-https-proxy", "http://bar:3128",
178+ "--with-no-proxy", "localhost"],
179 self.subprocess.calls[1][0])
180
181 def test_was_ready(self):
182 """
183- If the services was ready, the schema script is not invoked again.
184+ If the services was ready, the schema bootstrap is not invoked again.
185 """
186 self.manager.save_ready("landscape")
187 self.callback(self.manager, "landscape", None)
188- self.assertEqual([], self.subprocess.calls)
189+ self.assertEqual([
190+ (['/usr/bin/landscape-schema', '-h'], {})], self.subprocess.calls)
191+
192+ def test_update_config(self):
193+ """Updating config are updated in Landscape."""
194+ self.subprocess.add_fake_executable(
195+ SCHEMA_SCRIPT, args=["-h"], stdout="Usage: " + CONFIG_ONLY_FLAG)
196+
197+ self.hookenv.hook = "config-changed"
198+ config = self.hookenv.config()
199+ config["root-url"] = "https://old.sad/"
200+ config.save()
201+ self.manager.save_ready("landscape")
202+
203+ config["root-url"] = "https://happy.new/"
204+ self.callback(self.manager, "landscape", None)
205+ self.assertEqual([
206+ (["/usr/bin/landscape-schema", "-h"], {}),
207+ (["/usr/bin/landscape-schema", CONFIG_ONLY_FLAG, "--with-root-url",
208+ "https://happy.new/"], {}),
209+ ], self.subprocess.calls)
210+
211+ def test_update_config_unsupported_flag(self):
212+ """Configuration is NOOP if the configure-lds flag is not supported."""
213+ self.subprocess.add_fake_executable(
214+ SCHEMA_SCRIPT, args=["-h"], stdout="Usage: --spam")
215+
216+ self.hookenv.hook = "config-changed"
217+ config = self.hookenv.config()
218+ config["root-url"] = "https://old.sad/"
219+ config.save()
220+ self.manager.save_ready("landscape")
221+ config["root-url"] = "https://happy.new/"
222+
223+ self.callback(self.manager, "landscape", None)
224+ self.assertEqual([
225+ (["/usr/bin/landscape-schema", "-h"], {}),
226+ ], self.subprocess.calls)
227+
228+ def test_bootstrap_and_configure(self):
229+ """Configuration is done after bootstrap, if config values exist."""
230+ self.subprocess.add_fake_executable(
231+ SCHEMA_SCRIPT, args=["-h"], stdout="Usage: " + CONFIG_ONLY_FLAG)
232+
233+ self.hookenv.hook = "config-changed"
234+ config = self.hookenv.config()
235+ config["system-email"] = "noreply@spam"
236+ config.save()
237+ config["system-email"] = "noreply@scape"
238+
239+ self.callback(self.manager, "landscape", None)
240+ self.assertEqual([
241+ (["/usr/bin/landscape-schema", "-h"], {}),
242+ (["/usr/bin/landscape-schema", "--bootstrap"], {}),
243+ (["/usr/bin/landscape-schema", CONFIG_ONLY_FLAG,
244+ "--with-system-email", "noreply@scape"], {}),
245+ ], self.subprocess.calls)
246+
247+ def test_inherit_model_proxy(self):
248+ """Proxy config is inherited from the model if unset on charm."""
249+ self.subprocess.add_fake_executable(
250+ SCHEMA_SCRIPT, args=["-h"],
251+ stdout="Usage: --with-http-proxy " + CONFIG_ONLY_FLAG)
252+ self.hookenv.hook = "config-changed"
253+ config = self.hookenv.config()
254+ config["http-proxy"] = "spam"
255+ config["https-proxy"] = "spam"
256+ config["no-proxy"] = "spam"
257+ config.save()
258+ del config["http-proxy"]
259+ del config["https-proxy"]
260+ del config["no-proxy"]
261+ self.useFixture(EnvironmentVariable("http_proxy", "http://foo:3128"))
262+ self.useFixture(EnvironmentVariable("https_proxy", "http://bar:3128"))
263+ self.useFixture(EnvironmentVariable("no_proxy", "localhost"))
264+ self.manager.save_ready("landscape")
265+
266+ self.callback(self.manager, "landscape", None)
267+ self.assertEqual([
268+ (["/usr/bin/landscape-schema", "-h"], {}),
269+ (["/usr/bin/landscape-schema", CONFIG_ONLY_FLAG,
270+ "--with-http-proxy", "http://foo:3128",
271+ "--with-https-proxy", "http://bar:3128",
272+ "--with-no-proxy", "localhost"], {}),
273+ ], self.subprocess.calls)
274+
275+ def test_change_model_proxy(self):
276+ """Proxy config override model proxy if configured on charm."""
277+ self.subprocess.add_fake_executable(
278+ SCHEMA_SCRIPT, args=["-h"],
279+ stdout="Usage: --with-http-proxy " + CONFIG_ONLY_FLAG)
280+ self.hookenv.hook = "config-changed"
281+ config = self.hookenv.config()
282+ self.useFixture(EnvironmentVariable("http_proxy", "spam"))
283+ self.useFixture(EnvironmentVariable("https_proxy", "spam"))
284+ self.useFixture(EnvironmentVariable("no_proxy", "spam"))
285+ self.manager.save_ready("landscape")
286+ config["http-proxy"] = "http://foo:3128"
287+ config["https-proxy"] = "http://bar:3128"
288+ config["no-proxy"] = "localhost"
289+
290+ self.callback(self.manager, "landscape", None)
291+ self.assertEqual([
292+ (["/usr/bin/landscape-schema", "-h"], {}),
293+ (["/usr/bin/landscape-schema", CONFIG_ONLY_FLAG,
294+ "--with-http-proxy", "http://foo:3128",
295+ "--with-https-proxy", "http://bar:3128",
296+ "--with-no-proxy", "localhost"], {}),
297+ ], self.subprocess.calls)
298+
299+ def test_unset_model_proxy(self):
300+ """Proxy config can explicitly unset model proxy."""
301+ self.subprocess.add_fake_executable(
302+ SCHEMA_SCRIPT, args=["-h"],
303+ stdout="Usage: --with-http-proxy " + CONFIG_ONLY_FLAG)
304+ self.hookenv.hook = "config-changed"
305+ config = self.hookenv.config()
306+ self.useFixture(EnvironmentVariable("http_proxy", "spam"))
307+ self.useFixture(EnvironmentVariable("https_proxy", "spam"))
308+ self.useFixture(EnvironmentVariable("no_proxy", "spam"))
309+ self.manager.save_ready("landscape")
310+ config["http-proxy"] = ""
311+ config["https-proxy"] = ""
312+ config["no-proxy"] = ""
313+
314+ self.callback(self.manager, "landscape", None)
315+ self.assertEqual([
316+ (["/usr/bin/landscape-schema", "-h"], {}),
317+ (["/usr/bin/landscape-schema", CONFIG_ONLY_FLAG,
318+ "--with-http-proxy", "",
319+ "--with-https-proxy", "",
320+ "--with-no-proxy", ""], {}),
321+ ], self.subprocess.calls)
322+
323+ def test_reconfigure_noop(self):
324+ """Nothing happens if there is no proxy and no config change."""
325+ # TODO
326
327
328 class LSCtlTest(HookenvTest):
329
330=== modified file 'lib/services.py'
331--- lib/services.py 2021-11-09 09:20:31 +0000
332+++ lib/services.py 2021-12-08 22:56:37 +0000
333@@ -86,7 +86,8 @@
334 subprocess=self._subprocess),
335 EnsureConfigDir(paths=self._paths),
336 WriteCustomSSLCertificate(paths=self._paths),
337- SchemaBootstrap(subprocess=self._subprocess),
338+ SchemaBootstrap(
339+ subprocess=self._subprocess, hookenv=self._hookenv),
340 WriteLicenseFile(host=self._host, paths=self._paths),
341 ConfigureSMTP(
342 hookenv=self._hookenv, subprocess=self._subprocess),
343@@ -95,4 +96,8 @@
344 ],
345 "start": LSCtl(subprocess=self._subprocess, hookenv=self._hookenv),
346 }])
347- manager.manage()
348+ try:
349+ manager.manage()
350+ except Exception as e:
351+ self._hookenv.log(str(e), level=hookenv.ERROR)
352+ raise
353
354=== modified file 'lib/tests/stubs.py'
355--- lib/tests/stubs.py 2021-11-10 05:34:36 +0000
356+++ lib/tests/stubs.py 2021-12-08 22:56:37 +0000
357@@ -1,3 +1,4 @@
358+import os
359 import subprocess
360
361 from charmhelpers.core.hookenv import Config
362@@ -33,6 +34,13 @@
363 def config(self):
364 return self._config
365
366+ def env_proxy_settings(self):
367+ return {
368+ k: os.environ[k]
369+ for k in ("http_proxy", "https_proxy", "no_proxy")
370+ if k in os.environ
371+ } or None
372+
373 def relations_of_type(self, reltype):
374 return self.relations.get(reltype, None)
375

Subscribers

People subscribed via source and target branches