Merge lp:~ahasenack/landscape-charm/create-account-first-admin into lp:~landscape/landscape-charm/trunk
- create-account-first-admin
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Andreas Hasenack | ||||
Approved revision: | 181 | ||||
Merged at revision: | 207 | ||||
Proposed branch: | lp:~ahasenack/landscape-charm/create-account-first-admin | ||||
Merge into: | lp:~landscape/landscape-charm/trunk | ||||
Diff against target: |
542 lines (+406/-24) 5 files modified
config.yaml (+21/-4) config/landscape-deployments.yaml (+4/-0) hooks/hooks.py (+54/-18) hooks/lib/util.py (+61/-0) hooks/test_hooks.py (+266/-2) |
||||
To merge this branch: | bzr merge lp:~ahasenack/landscape-charm/create-account-first-admin | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Björn Tillenius (community) | Approve | ||
Adam Collard (community) | Abstain | ||
Chad Smith | Approve | ||
Review via email:
|
Commit message
New charm config options for the creation of the first landscape administrator.
Description of the change
Three new config options that allow for the creation of the first landscape administrator. They can also be set after a deployment is done, but unless the database is empty, it's a noop.
I used ./schema instead of the BootstrapLDS API call because this call is not available in LDS 13.09, which is our currently released version.
I kept with the tradition of the existing code and used mocker a lot in the tests. I didn't like to have to use the juju log messages to determine why a method exited. I was tempted to set specific return values, or raise exceptions, but they would only be used by the tests, so I didn't do it.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andreas Hasenack (ahasenack) wrote : | # |
Bits are so nice! Machine language! :)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andreas Hasenack (ahasenack) wrote : | # |
I used ./schema because 13.09, the currently released LDS, does not have that new api call.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Adam Collard (adam-collard) wrote : | # |
> I used ./schema because 13.09, the currently released LDS, does not have that
> new api call.
Ah ok. Fair enough
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chad Smith (chad.smith) wrote : | # |
Initial set of comments. Looks good so far. I want to try a deploy first and then I'll approve. I'm going to look over those unit tests again, it seems like it's sort of painful how we set it up. Not sure if we care about tackling that in this branch but it's something we need to refine at some point.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chad Smith (chad.smith) wrote : | # |
Looks good. works fine.
One thing we might want to consider at some point, since you are using schema directly. We aren't doing any validation on email address format specified in sample/sample.py. So it might be good to safeguard that configuration value with an email format validation so we can throw a config hook error exit(1) if that value is not correct so we don't configure the DB with an invalid email. Just a thought
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Adam Collard (adam-collard) wrote : | # |
Sorry for blocking this for so long, I don't have time to review it properly, so Abstaining.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Björn Tillenius (bjornt) wrote : | # |
I think this can be merged. I have some minor comments, but nothing blocking, some are just suggestions, and I'm not expecting them to be fixed here.
I also don't like the mocking in the tests, but I think it's quite a lot of work getting rid of it.
- 179. By Andreas Hasenack
-
Encode admin details at the last possible moment.
- 180. By Andreas Hasenack
-
Simplify test_first_
admin_not_ created_ without_ name_email_ password( ) a bit - 181. By Andreas Hasenack
-
create_
landscape_ admin() and _create_ first_admin( ) now return True or
False.
Preview Diff
1 | === modified file 'config.yaml' |
2 | --- config.yaml 2014-04-24 20:25:07 +0000 |
3 | +++ config.yaml 2014-10-01 20:17:48 +0000 |
4 | @@ -50,7 +50,24 @@ |
5 | schema (instead of just validating that it is correct). This |
6 | should only typically be enabled as part of a landscape maintenance |
7 | cycle. |
8 | - |
9 | - |
10 | - |
11 | - |
12 | + admin-name: |
13 | + description: | |
14 | + Name of the account administrator. If this, admin-email and |
15 | + admin-password are specified, then the standalone account will be |
16 | + created as part of the deployment, and this person will be its |
17 | + administrator. If not specified, Landscape will prompt for these |
18 | + details when it is first accessed with a web browser. Has no |
19 | + effect if changed after a deployment. |
20 | + type: string |
21 | + admin-email: |
22 | + description: | |
23 | + Email of the account administrator. To have any effect, needs to |
24 | + specified in conjunction with admin-name and admin-password. Has |
25 | + no effect if changed after a deployment. |
26 | + type: string |
27 | + admin-password: |
28 | + description: | |
29 | + Password of the account administrator. To have any effect, needs |
30 | + to be specified in conjunction with admin-name and admin-email. |
31 | + Has no effect if changed after a deployment. |
32 | + type: string |
33 | |
34 | === modified file 'config/landscape-deployments.yaml' |
35 | --- config/landscape-deployments.yaml 2014-09-15 20:26:59 +0000 |
36 | +++ config/landscape-deployments.yaml 2014-10-01 20:17:48 +0000 |
37 | @@ -39,6 +39,10 @@ |
38 | services: static appserver pingserver combo-loader async-frontend apiserver package-upload jobhandler package-search cron juju-sync |
39 | repository: include-file://repo-file |
40 | license-file: include-file://license-file |
41 | + # Optionally create the first admin: |
42 | + #admin-name: Stan Peters |
43 | + #admin-email: stan@example.com |
44 | + #admin-password: pwd |
45 | landscape-msg: |
46 | branch: lp:~landscape/landscape-charm/stable |
47 | charm: landscape |
48 | |
49 | === modified file 'hooks/hooks.py' |
50 | --- hooks/hooks.py 2014-07-09 21:11:25 +0000 |
51 | +++ hooks/hooks.py 2014-10-01 20:17:48 +0000 |
52 | @@ -193,6 +193,52 @@ |
53 | juju.juju_log(str(e), level="DEBUG") |
54 | |
55 | |
56 | +def _get_db_access_details(): |
57 | + """ |
58 | + Returns the main database access details as they are set in the landscape |
59 | + service configuration file. |
60 | + """ |
61 | + config_obj = _get_config_obj(LANDSCAPE_SERVICE_CONF) |
62 | + try: |
63 | + section = config_obj["stores"] |
64 | + database = section["main"] |
65 | + db_host = section["host"] |
66 | + db_user = section["user"] |
67 | + db_password = section["password"] |
68 | + except KeyError: |
69 | + return None |
70 | + return (database, db_host, db_user, db_password) |
71 | + |
72 | + |
73 | +def _create_first_admin(): |
74 | + """ |
75 | + If so requested by the presence of the right configuration keys, |
76 | + tries to create the landscape first administrator and, as a consequence, |
77 | + the standalone account too. |
78 | + """ |
79 | + first_admin_email = juju.config_get("admin-email") |
80 | + first_admin_name = juju.config_get("admin-name") |
81 | + first_admin_password = juju.config_get("admin-password") |
82 | + if not all((first_admin_email, first_admin_name, first_admin_password)): |
83 | + juju.juju_log("Not creating a Landscape administrator: need " |
84 | + "admin-email, admin-name and admin-password.") |
85 | + return False |
86 | + juju.juju_log("First admin creation requested") |
87 | + access_details = _get_db_access_details() |
88 | + if not access_details: |
89 | + juju.juju_log("No DB configuration yet, bailing.") |
90 | + return False |
91 | + database, db_host, db_user, db_password = access_details |
92 | + if util.is_db_up(database, db_host, db_user, db_password): |
93 | + with closing(util.connect_exclusive(db_host, db_user, db_password)): |
94 | + return util.create_landscape_admin( |
95 | + db_user, db_password, db_host, first_admin_name, |
96 | + first_admin_email, first_admin_password) |
97 | + else: |
98 | + juju.juju_log("Can't talk to the DB yet, bailing.") |
99 | + return False |
100 | + |
101 | + |
102 | def amqp_relation_joined(): |
103 | juju.relation_set("username=landscape") |
104 | juju.relation_set("vhost=landscape") |
105 | @@ -350,16 +396,11 @@ |
106 | |
107 | notify_vhost_config_relation(os.environ.get("JUJU_RELATION_ID", None)) |
108 | |
109 | - config_obj = _get_config_obj(LANDSCAPE_SERVICE_CONF) |
110 | - try: |
111 | - section = config_obj["stores"] |
112 | - database = section["main"] |
113 | - host = section["host"] |
114 | - user = section["user"] |
115 | - password = section["password"] |
116 | - except KeyError: |
117 | + access_details = _get_db_access_details() |
118 | + if not access_details: |
119 | juju.juju_log("Database not ready yet, deferring call") |
120 | sys.exit(0) |
121 | + database, host, user, password = access_details |
122 | |
123 | relids = juju.relation_ids("vhost-config") |
124 | if relids: |
125 | @@ -413,6 +454,7 @@ |
126 | _set_maintenance() |
127 | _enable_services() |
128 | _set_upgrade_schema() |
129 | + _create_first_admin() |
130 | |
131 | if _is_db_up() and _is_amqp_up(): |
132 | _lsctl("start") |
133 | @@ -726,17 +768,11 @@ |
134 | """ |
135 | Return True if the database is accessible and read/write, False otherwise. |
136 | """ |
137 | - config_obj = _get_config_obj(LANDSCAPE_SERVICE_CONF) |
138 | - try: |
139 | - section = config_obj["stores"] |
140 | - database = section["main"] |
141 | - host = section["host"] |
142 | - user = section["user"] |
143 | - password = section["password"] |
144 | - except KeyError: |
145 | + access_details = _get_db_access_details() |
146 | + if not access_details: |
147 | return False |
148 | - else: |
149 | - return util.is_db_up(database, host, user, password) |
150 | + database, host, user, password = access_details |
151 | + return util.is_db_up(database, host, user, password) |
152 | |
153 | |
154 | ERROR_PATH = "/opt/canonical/landscape/canonical/landscape/static/offline/" |
155 | |
156 | === modified file 'hooks/lib/util.py' |
157 | --- hooks/lib/util.py 2014-06-14 03:49:47 +0000 |
158 | +++ hooks/lib/util.py 2014-10-01 20:17:48 +0000 |
159 | @@ -5,10 +5,26 @@ |
160 | from psycopg2 import connect, Error as psycopg2Error |
161 | from juju import Juju |
162 | from contextlib import closing |
163 | +from subprocess import check_output |
164 | + |
165 | +import re |
166 | +import os |
167 | |
168 | juju = Juju() |
169 | |
170 | |
171 | +def is_email_valid(email): |
172 | + """ |
173 | + Returns true if the given email is safe to use and has no "funny" |
174 | + characters. We don't go overboard and look for an RFC compliant email |
175 | + here. |
176 | + |
177 | + @param email: string containing the email to be validated |
178 | + """ |
179 | + valid_email_re = r"^[\w.+-]+@[\w-]+\.[\w.]+$" |
180 | + return re.search(valid_email_re, email) is not None |
181 | + |
182 | + |
183 | def connect_exclusive(host, admin_user, admin_password): |
184 | """ |
185 | database user creation and setup-landscape-server need to be done in a |
186 | @@ -49,6 +65,51 @@ |
187 | conn.close() |
188 | |
189 | |
190 | +def account_is_empty(db_user, db_password, db_host): |
191 | + """ |
192 | + Returns true if the person and account tables from the |
193 | + landscape-standalone-main database are empty. |
194 | + """ |
195 | + with closing(connect(database="landscape-standalone-main", host=db_host, |
196 | + user=db_user, password=db_password)) as conn: |
197 | + cur = conn.cursor() |
198 | + cur.execute("SELECT COUNT(person.id),COUNT(account.id) FROM " |
199 | + "person,account") |
200 | + result = cur.fetchall()[0] |
201 | + return int(result[0]) == 0 and int(result[1]) == 0 |
202 | + |
203 | + |
204 | +def create_landscape_admin(db_user, db_password, db_host, admin_name, |
205 | + admin_email, admin_password): |
206 | + """ |
207 | + Create the first Landscape administrator with the given credentials. |
208 | + Returns True if the administrator was created, False otherwise. |
209 | + """ |
210 | + if account_is_empty(db_user, db_password, db_host): |
211 | + if not is_email_valid(admin_email): |
212 | + raise ValueError("Invalid administrator email %s" % admin_email) |
213 | + juju.juju_log("Creating first administrator") |
214 | + env = os.environ.copy() |
215 | + env["LANDSCAPE_CONFIG"] = "standalone" |
216 | + admin_name = admin_name.encode("utf-8") |
217 | + admin_email = admin_email.encode("utf-8") |
218 | + admin_password = admin_password.encode("utf-8") |
219 | + cmd = ["./schema", "--create-lds-account-only", "--admin-name", |
220 | + admin_name, "--admin-email", admin_email, |
221 | + "--admin-password", admin_password] |
222 | + # Throw stdout away, bceause when the call works, stdout will have API |
223 | + # credentials, which we don't want in the juju logs. When the call |
224 | + # fails, however, we want stderr because it will say why it failed, so |
225 | + # let the exception be raised and stderr go through as usual. |
226 | + check_output(cmd, cwd="/opt/canonical/landscape", env=env) |
227 | + juju.juju_log("Administrator called %s with email %s created" % |
228 | + (admin_name, admin_email)) |
229 | + return True |
230 | + else: |
231 | + juju.juju_log("DB not empty, skipping first admin creation") |
232 | + return False |
233 | + |
234 | + |
235 | def change_root_url(database, user, password, host, url): |
236 | """Change the root url in the database.""" |
237 | url = "u%s:%s" % (len(url), url) |
238 | |
239 | === modified file 'hooks/test_hooks.py' |
240 | --- hooks/test_hooks.py 2014-07-08 19:21:33 +0000 |
241 | +++ hooks/test_hooks.py 2014-10-01 20:17:48 +0000 |
242 | @@ -1,5 +1,6 @@ |
243 | +from configobj import ConfigObj |
244 | +from itertools import product |
245 | import base64 |
246 | -from configobj import ConfigObj |
247 | import hooks |
248 | import mocker |
249 | import os |
250 | @@ -8,6 +9,7 @@ |
251 | import stat |
252 | import subprocess |
253 | import tempfile |
254 | +import unittest |
255 | import yaml |
256 | |
257 | |
258 | @@ -28,7 +30,10 @@ |
259 | "license-file": "LICENSE_FILE_TEXT", |
260 | "service-count": "msgserver:2 pingserver:1", |
261 | "upgrade-schema": False, |
262 | - "maintenance": False} |
263 | + "maintenance": False, |
264 | + "admin-name": None, |
265 | + "admin-email": None, |
266 | + "admin-password": None} |
267 | |
268 | def relation_set(self, *args, **kwargs): |
269 | """ |
270 | @@ -92,6 +97,7 @@ |
271 | def setUp(self): |
272 | hooks._lsctl = lambda x: True |
273 | hooks.juju = TestJuju() |
274 | + hooks.util.juju = hooks.juju |
275 | hooks.LANDSCAPE_LICENSE_DEST = self.makeFile() |
276 | hooks.LANDSCAPE_DEFAULT_FILE = self.makeFile() |
277 | self._default_file = open(hooks.LANDSCAPE_DEFAULT_FILE, "w") |
278 | @@ -137,6 +143,264 @@ |
279 | |
280 | class TestHooksService(TestHooks): |
281 | |
282 | + def test_first_admin_not_created_without_name_email_password(self): |
283 | + """ |
284 | + The first admin is not created when only one or two of name, email and |
285 | + password are given. |
286 | + """ |
287 | + admins = [("Foo Bar", "foo@example.com", None), |
288 | + ("Foo Bar", None, "secret"), |
289 | + (None, "foo@example.com", "secret")] |
290 | + for name, email, password in admins: |
291 | + hooks.juju.config["admin-name"] = name |
292 | + hooks.juju.config["admin-email"] = email |
293 | + hooks.juju.config["admin-password"] = password |
294 | + self.assertFalse(hooks._create_first_admin()) |
295 | + hooks.juju._logs = () |
296 | + |
297 | + def test_first_admin_not_created_if_no_db_config(self): |
298 | + """ |
299 | + The first administrator is not created if there is no database |
300 | + configuration in the service.conf file. |
301 | + """ |
302 | + messages = ("First admin creation requested", |
303 | + "No DB configuration yet, bailing.") |
304 | + hooks.juju.config["admin-name"] = "Foo Bar" |
305 | + hooks.juju.config["admin-email"] = "foo@example.com" |
306 | + hooks.juju.config["admin-password"] = "secret" |
307 | + config_obj = ConfigObj(hooks.LANDSCAPE_SERVICE_CONF) |
308 | + config_obj["stores"] = {} |
309 | + config_obj.write() |
310 | + self.assertFalse(hooks._create_first_admin()) |
311 | + self.assertEqual(messages, hooks.juju._logs) |
312 | + |
313 | + def test_email_syntax_check(self): |
314 | + """ |
315 | + Invalid email addresses are flagged as such. |
316 | + """ |
317 | + # some invalid choices |
318 | + emails = ["invalidemail", "invalid@", "a@b", "a@b.", |
319 | + "`cat /etc/password`@example.com", "#foobar@example.com", |
320 | + "(foo)@example.com", "foo@(example).com", |
321 | + "'foo@example.com", "\"foo@example.com"] |
322 | + for email in emails: |
323 | + self.assertIs(False, hooks.util.is_email_valid(email)) |
324 | + |
325 | + def test_create_landscape_admin_checks_email_syntax(self): |
326 | + """ |
327 | + The util.create_landscape_admin() method verifies if the email is |
328 | + valid before attempting to create the admin. |
329 | + """ |
330 | + db_user = "user" |
331 | + db_password = "password" |
332 | + db_host = "example.com" |
333 | + admin_name = "Foo Bar" |
334 | + admin_email = "foo'@bar" |
335 | + admin_password = "secret" |
336 | + |
337 | + account_is_empty = self.mocker.replace(hooks.util.account_is_empty) |
338 | + account_is_empty(db_user, db_password, db_host) |
339 | + self.mocker.result(True) |
340 | + self.mocker.replay() |
341 | + with unittest.TestCase.assertRaises(self, ValueError) as invalid_email: |
342 | + hooks.util.create_landscape_admin(db_user, db_password, db_host, |
343 | + admin_name, admin_email, admin_password) |
344 | + self.assertEqual("Invalid administrator email %s" % admin_email, |
345 | + invalid_email.exception.message) |
346 | + |
347 | + def test_first_admin_not_created_if_account_not_empty(self): |
348 | + """ |
349 | + The first administrator is not created if the account is not |
350 | + empty. |
351 | + """ |
352 | + db_user = "user" |
353 | + db_password = "password" |
354 | + db_host = "example.com" |
355 | + admin_name = "Foo Bar" |
356 | + admin_email = "foo@example.com" |
357 | + admin_password = "secret" |
358 | + message = "DB not empty, skipping first admin creation" |
359 | + |
360 | + account_is_empty = self.mocker.replace(hooks.util.account_is_empty) |
361 | + account_is_empty(db_user, db_password, db_host) |
362 | + self.mocker.result(False) |
363 | + self.mocker.replay() |
364 | + admin_created = hooks.util.create_landscape_admin( |
365 | + db_user, db_password, db_host, admin_name, admin_email, |
366 | + admin_password) |
367 | + self.assertFalse(admin_created) |
368 | + self.assertEqual((message,), hooks.juju._logs) |
369 | + |
370 | + def test__create_first_admin_calls_create_landscape_admin(self): |
371 | + """ |
372 | + When all conditions are met, _create_first_admin() calls |
373 | + create_landscape_admin. |
374 | + """ |
375 | + # juju log message we expect |
376 | + message = "First admin creation requested" |
377 | + |
378 | + # we have an admin user defined |
379 | + admin_name = "Foo Bar" |
380 | + admin_email = "foo@example.com" |
381 | + admin_password = "secret" |
382 | + hooks.juju.config["admin-name"] = admin_name |
383 | + hooks.juju.config["admin-email"] = admin_email |
384 | + hooks.juju.config["admin-password"] = admin_password |
385 | + |
386 | + # we have the database access details |
387 | + config_obj = ConfigObj(hooks.LANDSCAPE_SERVICE_CONF) |
388 | + database = "mydb" |
389 | + db_host = "myhost" |
390 | + db_user = "myuser" |
391 | + db_password = "mypassword" |
392 | + stores = {"main": database, "host": db_host, "user": db_user, |
393 | + "password": db_password} |
394 | + config_obj["stores"] = stores |
395 | + config_obj.write() |
396 | + |
397 | + # the db is up |
398 | + is_db_up = self.mocker.replace(hooks.util.is_db_up) |
399 | + is_db_up(database, db_host, db_user, db_password) |
400 | + self.mocker.result(True) |
401 | + |
402 | + # we can connect |
403 | + connect_exclusive = self.mocker.replace(hooks.util.connect_exclusive) |
404 | + connect_exclusive(db_host, db_user, db_password) |
405 | + connection = self.mocker.mock() |
406 | + self.mocker.result(connection) |
407 | + |
408 | + # util.create_landscape_admin is called |
409 | + create_landscape_admin = self.mocker.replace( |
410 | + hooks.util.create_landscape_admin) |
411 | + create_landscape_admin( |
412 | + db_user, db_password, db_host, admin_name, admin_email, |
413 | + admin_password) |
414 | + connection.close() |
415 | + self.mocker.replay() |
416 | + |
417 | + hooks._create_first_admin() |
418 | + self.assertEqual((message,), hooks.juju._logs) |
419 | + |
420 | + def test__create_first_admin_bails_if_db_is_not_up(self): |
421 | + """ |
422 | + The _create_first_admin() method gives up if the DB is not |
423 | + accessible. |
424 | + """ |
425 | + # juju log messages we expect |
426 | + messages = ("First admin creation requested", |
427 | + "Can't talk to the DB yet, bailing.") |
428 | + # we have an admin user defined |
429 | + admin_name = "Foo Bar" |
430 | + admin_email = "foo@example.com" |
431 | + admin_password = "secret" |
432 | + hooks.juju.config["admin-name"] = admin_name |
433 | + hooks.juju.config["admin-email"] = admin_email |
434 | + hooks.juju.config["admin-password"] = admin_password |
435 | + |
436 | + # we have the database access details |
437 | + config_obj = ConfigObj(hooks.LANDSCAPE_SERVICE_CONF) |
438 | + database = "mydb" |
439 | + db_host = "myhost" |
440 | + db_user = "myuser" |
441 | + db_password = "mypassword" |
442 | + stores = {"main": database, "host": db_host, "user": db_user, |
443 | + "password": db_password} |
444 | + config_obj["stores"] = stores |
445 | + config_obj.write() |
446 | + |
447 | + # the db is down, though |
448 | + is_db_up = self.mocker.replace(hooks.util.is_db_up) |
449 | + is_db_up(database, db_host, db_user, db_password) |
450 | + self.mocker.result(False) |
451 | + |
452 | + self.mocker.replay() |
453 | + admin_created = hooks._create_first_admin() |
454 | + self.assertFalse(admin_created) |
455 | + self.assertEqual(messages, hooks.juju._logs) |
456 | + |
457 | + def test__get_db_access_details(self): |
458 | + """ |
459 | + The _get_db_access_details() function returns the database name and |
460 | + access details when all these keys exist in the landscape service |
461 | + configuration file. |
462 | + """ |
463 | + config_obj = ConfigObj(hooks.LANDSCAPE_SERVICE_CONF) |
464 | + stores = {"main": "mydb", "host": "myhost", "user": "myuser", |
465 | + "password": "mypassword"} |
466 | + config_obj["stores"] = stores |
467 | + config_obj.write() |
468 | + |
469 | + result = hooks._get_db_access_details() |
470 | + database, db_host, db_user, db_password = result |
471 | + self.assertEqual(database, stores["main"]) |
472 | + self.assertEqual(db_host, stores["host"]) |
473 | + self.assertEqual(db_user, stores["user"]) |
474 | + self.assertEqual(db_password, stores["password"]) |
475 | + |
476 | + def test_no_db_access_details_if_missing_config_key(self): |
477 | + """ |
478 | + The _get_db_access_details() function returns None if any of |
479 | + the needed configuration keys is missing. |
480 | + """ |
481 | + full_config = {"main": "mydb", "host": "myhost", "user": "myuser", |
482 | + "password": "mypassword"} |
483 | + config_obj = ConfigObj(hooks.LANDSCAPE_SERVICE_CONF) |
484 | + # remove one key each time |
485 | + for key in full_config: |
486 | + data = full_config.copy() |
487 | + data.pop(key) |
488 | + config_obj["stores"] = data |
489 | + config_obj.write() |
490 | + result = hooks._get_db_access_details() |
491 | + self.assertIsNone(result) |
492 | + # last try, with no data at all |
493 | + config_obj.clear() |
494 | + config_obj.write() |
495 | + self.assertIsNone(hooks._get_db_access_details()) |
496 | + |
497 | + def test_create_landscape_admin_calls_schema_script(self): |
498 | + """ |
499 | + The create_landscape_admin() method calls the landscape schema |
500 | + script with the right parameters. |
501 | + """ |
502 | + # we have an admin user defined |
503 | + admin_name = "Foo Bar" |
504 | + admin_email = "foo@example.com" |
505 | + admin_password = "secret" |
506 | + |
507 | + # juju log messages we expect |
508 | + messages = ("Creating first administrator", |
509 | + "Administrator called %s with email %s created" % |
510 | + (admin_name, admin_email)) |
511 | + |
512 | + # we have the database access details |
513 | + db_host = "myhost" |
514 | + db_user = "myuser" |
515 | + db_password = "mypassword" |
516 | + |
517 | + # account is empty |
518 | + account_is_empty = self.mocker.replace(hooks.util.account_is_empty) |
519 | + account_is_empty(db_user, db_password, db_host) |
520 | + self.mocker.result(True) |
521 | + |
522 | + # schema script is called with the right parameters |
523 | + self.addCleanup(setattr, hooks.util.os, "environ", |
524 | + hooks.util.os.environ) |
525 | + hooks.util.os.environ = {} |
526 | + env = {"LANDSCAPE_CONFIG": "standalone"} |
527 | + schema_call = self.mocker.replace(hooks.util.check_output) |
528 | + cmd = ["./schema", "--create-lds-account-only", "--admin-name", |
529 | + admin_name, "--admin-email", admin_email, "--admin-password", |
530 | + admin_password] |
531 | + schema_call(cmd, cwd="/opt/canonical/landscape", env=env) |
532 | + |
533 | + self.mocker.replay() |
534 | + |
535 | + hooks.util.create_landscape_admin( |
536 | + db_user, db_password, db_host, admin_name, admin_email, |
537 | + admin_password) |
538 | + self.assertEqual(messages, hooks.juju._logs) |
539 | + |
540 | def test_get_services_non_proxied(self): |
541 | """ |
542 | helper method should not break if non-proxied services are called for |
Needs Fixing because of the horrible bit twiddling. Would otherwise be Needs Information as to why we use ./schema and not the API.