Merge ~peter-sabaini/charm-sudo-pair:add-charm-destructor into ~sudo-pair-charmers/charm-sudo-pair:master

Proposed by Peter Sabaini
Status: Merged
Approved by: Giuseppe Petralia
Approved revision: b0edeb8595177333c2b41a3cf85980c11224a691
Merged at revision: bbc52e018474e0c52f2bbee6e380e52adeebd453
Proposed branch: ~peter-sabaini/charm-sudo-pair:add-charm-destructor
Merge into: ~sudo-pair-charmers/charm-sudo-pair:master
Diff against target: 279 lines (+93/-32)
7 files modified
Makefile (+2/-2)
lib/libsudopair.py (+12/-4)
reactive/sudo_pair.py (+6/-0)
tests/functional/conftest.py (+9/-6)
tests/functional/test_deploy.py (+38/-8)
tests/unit/test_libsudopair.py (+11/-11)
tox.ini (+15/-1)
Reviewer Review Type Date Requested Status
Giuseppe Petralia Approve
Joel Sing (community) +1 Approve
Andrea Ieri Pending
Review via email: mp+361637@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Joel Sing (jsing) wrote :

LGTM for readability/standards - some minor comments inline.

Please also get a peer/domain expert review/approval before landing.

review: Approve (+1)
Revision history for this message
Giuseppe Petralia (peppepetra) wrote :

Deletion of sudo.conf and lib works fine for me.

In order to run the tests I had to do a couple fix to Makefile and test prep.

I had to add modify Makefile as following:
- remove tabs from line 3 and 4
- remove line 38

I had to add the following line to tox.ini at functional section:
setenv = PYTHONPATH={toxinidir}/lib

I had to add the following lines to tests/functional/requirements.txt:
charmhelpers
charms.reactive
mock

Then everything worked as expected.
Maybe I am missing something. In that case I would recommend to update Readme file properly.

review: Needs Information
Revision history for this message
Giuseppe Petralia (peppepetra) wrote :

Looks good to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/Makefile b/Makefile
2index 460970a..935db60 100644
3--- a/Makefile
4+++ b/Makefile
5@@ -1,7 +1,7 @@
6 PROJECTPATH = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
7 ifndef JUJU_REPOSITORY
8- JUJU_REPOSITORY := $(shell pwd)
9- $(warning Warning JUJU_REPOSITORY was not set, defaulting to $(JUJU_REPOSITORY))
10+ JUJU_REPOSITORY := $(shell pwd)
11+ $(warning Warning JUJU_REPOSITORY was not set, defaulting to $(JUJU_REPOSITORY))
12 endif
13
14 help:
15diff --git a/lib/libsudopair.py b/lib/libsudopair.py
16index 32cb6d4..3422521 100644
17--- a/lib/libsudopair.py
18+++ b/lib/libsudopair.py
19@@ -58,12 +58,12 @@ class SudoPairHelper(object):
20
21 def get_config(self):
22 config = {
23- 'binary_path' : self.binary_path,
24- 'user_prompt_path' : self.user_prompt_path,
25- 'pair_prompt_path' : self.pair_prompt_path,
26+ 'binary_path': self.binary_path,
27+ 'user_prompt_path': self.user_prompt_path,
28+ 'pair_prompt_path': self.pair_prompt_path,
29 'socket_dir': self.socket_dir,
30 'gids_enforced': group_names_to_group_ids(self.charm_config['groups_enforced']),
31- 'gids_exempted' : group_names_to_group_ids(self.charm_config['groups_exempted']),
32+ 'gids_exempted': group_names_to_group_ids(self.charm_config['groups_exempted']),
33 }
34
35 config.update(self.charm_config)
36@@ -96,11 +96,19 @@ class SudoPairHelper(object):
37 copy_file(sudoers_file, self.sudoers_path, self.owner, self.group, self.sudoers_perms)
38
39 def render_sudo_approve(self):
40+ hookenv.log("Rendering sudo_approve.tmpl to {}".format(self.binary_path))
41 return templating.render('sudo_approve.tmpl', self.binary_path, self.get_config(),
42 perms=self.sudo_approve_perms, owner=self.owner, group=self.group)
43
44 def render_bypass_cmds(self):
45 if self.get_config()['bypass_cmds'] != "" and self.get_config()['bypass_group'] != "":
46+ hookenv.log("Render bypass cmds to {}".format(self.sudoers_bypass_path))
47 return templating.render('91-bypass-sudopair-cmds.tmpl', self.sudoers_bypass_path,
48 self.get_config(), perms=0o440, owner=self.owner, group=self.group)
49 return None
50+
51+ def deconfigure(self):
52+ hookenv.log("Delete {}".format(self.sudo_conf_path))
53+ os.unlink(self.sudo_conf_path)
54+ sudo_pair_lib = os.path.join(hookenv.charm_dir(), 'files', 'sudo_pair.so')
55+ hookenv.log("Delete lib {}".format(sudo_pair_lib))
56diff --git a/reactive/sudo_pair.py b/reactive/sudo_pair.py
57index a5de1e5..13db3d9 100644
58--- a/reactive/sudo_pair.py
59+++ b/reactive/sudo_pair.py
60@@ -39,3 +39,9 @@ def install_sudo_pair():
61 def reconfigure_sudo_pair_charm():
62 sph.set_charm_config(hookenv.config())
63 remove_state('sudo-pair.configured')
64+
65+
66+@hook('stop')
67+def stop():
68+ sph.deconfigure()
69+ remove_state('sudo-pair.installed')
70diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py
71index d5de8f4..7279748 100644
72--- a/tests/functional/conftest.py
73+++ b/tests/functional/conftest.py
74@@ -1,13 +1,14 @@
75 #!/usr/bin/python3
76
77-import pytest
78-import json
79-import uuid
80 import asyncio
81+import json
82 import juju
83+import os
84+import pytest
85+import uuid
86 from juju.controller import Controller
87-from juju.model import Model
88 from juju.errors import JujuError
89+from juju.model import Model
90
91 STAT_FILE = "python3 -c \"import json; import os; s=os.stat('%s'); print(json.dumps({'uid': s.st_uid, 'gid': s.st_gid, 'mode': oct(s.st_mode), 'size': s.st_size}))\"" # noqa: E501
92
93@@ -36,10 +37,12 @@ async def controller():
94 @pytest.fixture(scope='module')
95 async def model(controller):
96 '''This model lives only for the duration of the test'''
97- model_name = str(uuid.uuid4())
98+ model_name = "functest-{}".format(uuid.uuid4())
99 model = await controller.add_model(model_name)
100 yield model
101 await model.disconnect()
102+ if os.getenv('test_preserve_model'):
103+ return
104 await controller.destroy_model(model_name)
105 while model_name in await controller.list_models():
106 await asyncio.sleep(1)
107@@ -86,7 +89,7 @@ async def get_entity(model, get_unit, get_app):
108 except JujuError:
109 try:
110 return await get_app(name)
111- except JujuError as e:
112+ except JujuError:
113 raise JujuError("Cannot find entity {}".format(name))
114 return _get_entity
115
116diff --git a/tests/functional/test_deploy.py b/tests/functional/test_deploy.py
117index 9b27ba2..5c8e690 100644
118--- a/tests/functional/test_deploy.py
119+++ b/tests/functional/test_deploy.py
120@@ -1,10 +1,14 @@
121 #!/usr/bin/python3.6
122-
123+import asyncio
124 import os
125 import pytest
126
127 pytestmark = pytest.mark.asyncio
128-SERIES = ['xenial', 'bionic']
129+
130+
131+def get_series():
132+ series = os.getenv('test_series', 'xenial bionic').strip()
133+ return series.split()
134
135 ############
136 # FIXTURES #
137@@ -14,7 +18,7 @@ SERIES = ['xenial', 'bionic']
138 # This fixture shouldn't really be in conftest.py since it's specific to this
139 # charm
140 @pytest.fixture(scope='module',
141- params=SERIES)
142+ params=get_series())
143 async def deploy_app(request, model):
144 '''Deploys the sudo_pair app as a subordinate of ubuntu'''
145 release = request.param
146@@ -37,9 +41,8 @@ async def deploy_app(request, model):
147 }
148 )
149 await model.add_relation(
150- 'ubuntu-' + release,
151- 'sudo-pair-' + release
152- )
153+ 'ubuntu-{}:juju-info'.format(release),
154+ 'sudo-pair-{}:juju-info'.format(release))
155
156 await model.block_until(lambda: sudo_pair_app.status == 'active')
157 yield sudo_pair_app
158@@ -81,8 +84,7 @@ async def test_deploy(deploy_app):
159 ('/var/run/sudo_pair', {
160 'gid': 0,
161 'uid': 0,
162- 'mode': '0o40644'})
163- ])
164+ 'mode': '0o40644'})])
165 async def test_stats(path, expected_stat, unit, file_stat):
166 test_stat = await file_stat(path, unit)
167 assert test_stat['size'] > 0
168@@ -114,3 +116,31 @@ async def test_reconfigure(reconfigure_app, file_contents, unit, deploy_app):
169 target=unit)
170 new_content = 'echo "You can\'t approve your own session."'
171 assert new_content in sudo_approve_content
172+
173+
174+async def test_remove_relation(deploy_app, model, run_command):
175+ series = deploy_app.units[0].data['series']
176+ app_name = 'sudo-pair-{}'.format(series)
177+ principalname = 'ubuntu-{}'.format(series)
178+ await deploy_app.remove_relation(
179+ '{}:juju-info'.format(app_name),
180+ '{}:juju-info'.format(principalname))
181+ await model.block_until(lambda: not deploy_app.relations)
182+ principal = model.applications[principalname].units[0]
183+ res = await run_command('test -f /etc/sudo.conf || echo gone', target=principal)
184+ assert res['Stdout'].strip() == 'gone'
185+ await model.add_relation(
186+ '{}:juju-info'.format(principalname),
187+ '{}:juju-info'.format(app_name))
188+ await model.block_until(lambda: deploy_app.relations)
189+
190+
191+async def test_remove_unit(deploy_app, model, run_command):
192+ series = deploy_app.units[0].data['series']
193+ app_name = 'sudo-pair-{}'.format(series)
194+ await deploy_app.destroy()
195+ while app_name in model.applications:
196+ await asyncio.sleep(2)
197+ principal = model.applications['ubuntu-{}'.format(series)].units[0]
198+ res = await run_command('test -f /etc/sudo.conf || echo gone', target=principal)
199+ assert res['Stdout'].strip() == 'gone'
200diff --git a/tests/unit/test_libsudopair.py b/tests/unit/test_libsudopair.py
201index f6482f8..ecd1e41 100644
202--- a/tests/unit/test_libsudopair.py
203+++ b/tests/unit/test_libsudopair.py
204@@ -57,15 +57,15 @@ class TestSudoPairHelper():
205 # Default config
206 content = sph.render_sudo_conf()
207 expected_content = 'Plugin sudo_pair sudo_pair.so binary_path={} ' \
208- 'user_prompt_path={} ' \
209- 'pair_prompt_path={} socket_dir={} gids_enforced={}'.format(tmpdir.join('/usr/bin/sudo_approve'),
210- tmpdir.join('/etc/sudo_pair.prompt.user'),
211- tmpdir.join('/etc/sudo_pair.prompt.pair'),
212- tmpdir.join('/var/run/sudo_pair'),
213- '0')
214+ 'user_prompt_path={} ' \
215+ 'pair_prompt_path={} socket_dir={} gids_enforced={}'.format(
216+ tmpdir.join('/usr/bin/sudo_approve'),
217+ tmpdir.join('/etc/sudo_pair.prompt.user'),
218+ tmpdir.join('/etc/sudo_pair.prompt.pair'),
219+ tmpdir.join('/var/run/sudo_pair'),
220+ '0')
221 assert expected_content in content
222
223-
224 # Gid exempted
225 groups_exempted = grp.getgrgid(os.getgid()).gr_name
226 charm_config = {
227@@ -100,10 +100,10 @@ class TestSudoPairHelper():
228 sph.set_charm_config(charm_config)
229 expected_content = 'Plugin sudo_pair sudo_pair.so binary_path={} user_prompt_path={} ' \
230 'pair_prompt_path={} socket_dir={} gids_enforced={}'.format(
231- tmpdir.join('/usr/bin/sudo_approve'),
232- tmpdir.join('/etc/sudo_pair.prompt.user'),
233- tmpdir.join('/etc/sudo_pair.prompt.pair'),
234- tmpdir.join('/var/run/sudo_pair'), '0,' + str(os.getgid()))
235+ tmpdir.join('/usr/bin/sudo_approve'),
236+ tmpdir.join('/etc/sudo_pair.prompt.user'),
237+ tmpdir.join('/etc/sudo_pair.prompt.pair'),
238+ tmpdir.join('/var/run/sudo_pair'), '0,{}'.format(os.getgid()))
239 content = sph.render_sudo_conf()
240 assert expected_content in content
241
242diff --git a/tox.ini b/tox.ini
243index 123e618..7d3565b 100644
244--- a/tox.ini
245+++ b/tox.ini
246@@ -1,6 +1,6 @@
247 [tox]
248 skipsdist=True
249-envlist = unit, functional
250+envlist = unit, functional, func_xenial
251 skip_missing_interpreters = True
252
253 [testenv]
254@@ -21,11 +21,25 @@ passenv =
255 commands = pytest -v --ignore {toxinidir}/tests/unit --ignore {toxinidir}/tests/amulet
256 deps = -r{toxinidir}/tests/functional/requirements.txt
257
258+
259+[testenv:func_xenial]
260+passenv =
261+ HOME
262+ JUJU_REPOSITORY
263+ PATH
264+ test_preserve_model
265+setenv = test_series=xenial
266+commands = pytest -v --ignore {toxinidir}/tests/unit --ignore {toxinidir}/tests/amulet
267+deps = -r{toxinidir}/tests/functional/requirements.txt
268+
269 [testenv:lint]
270 commands = flake8
271 deps = flake8
272
273 [flake8]
274+max-line-length = 120
275+max-complexity=10
276+ignore = W503
277 exclude =
278 .git,
279 __pycache__,

Subscribers

People subscribed via source and target branches