Merge ~peppepetra/charm-sudo-pair:blacken-20.08 into charm-sudo-pair:master

Proposed by Giuseppe Petralia
Status: Merged
Approved by: Xav Paice
Approved revision: 224d95033be074db000f82e2c66fbcede961d58c
Merged at revision: 224d95033be074db000f82e2c66fbcede961d58c
Proposed branch: ~peppepetra/charm-sudo-pair:blacken-20.08
Merge into: charm-sudo-pair:master
Prerequisite: ~peppepetra/charm-sudo-pair:makefile-20.08
Diff against target: 801 lines (+242/-151)
9 files modified
src/actions/actions.py (+1/-1)
src/lib/libsudopair.py (+92/-38)
src/reactive/sudo_pair.py (+17/-10)
src/tests/functional/conftest.py (+27/-15)
src/tests/functional/test_deploy.py (+1/-1)
src/tests/unit/conftest.py (+6/-5)
src/tests/unit/test_actions.py (+9/-8)
src/tests/unit/test_libsudopair.py (+87/-70)
src/tox.ini (+2/-3)
Reviewer Review Type Date Requested Status
Xav Paice (community) Approve
Paul Goins Approve
Review via email: mp+388998@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Paul Goins (vultaire) wrote :

LGTM.

review: Approve
Revision history for this message
Xav Paice (xavpaice) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/actions/actions.py b/src/actions/actions.py
index 1cb455c..2416eb3 100755
--- a/src/actions/actions.py
+++ b/src/actions/actions.py
@@ -30,7 +30,7 @@ def remove():
30 sph.deconfigure()30 sph.deconfigure()
31 msg = "Successfully removed sudo-pair config and binaries"31 msg = "Successfully removed sudo-pair config and binaries"
32 action_set({"message": msg})32 action_set({"message": msg})
33 status_set('active', msg)33 status_set("active", msg)
3434
3535
36# A dictionary of all the defined actions to callables (which take36# A dictionary of all the defined actions to callables (which take
diff --git a/src/lib/libsudopair.py b/src/lib/libsudopair.py
index 05d1f9d..cbaf467 100644
--- a/src/lib/libsudopair.py
+++ b/src/lib/libsudopair.py
@@ -25,8 +25,8 @@ def group_names_to_group_ids(group_names):
25 :param group_names: i.e. "root,user1,user2"25 :param group_names: i.e. "root,user1,user2"
26 :return gids: i.e. "0,1001,1002"26 :return gids: i.e. "0,1001,1002"
27 """27 """
28 group_names = list(filter(check_valid_group, group_names.split(',')))28 group_names = list(filter(check_valid_group, group_names.split(",")))
29 return ','.join(map(str, (map(group_id, group_names))))29 return ",".join(map(str, (map(group_id, group_names))))
3030
3131
32def copy_file(source, destination, owner, group, perms):32def copy_file(source, destination, owner, group, perms):
@@ -37,8 +37,10 @@ def copy_file(source, destination, owner, group, perms):
37 # This is a terrible default directory permission, as the file37 # This is a terrible default directory permission, as the file
38 # or its siblings will often contain secrets.38 # or its siblings will often contain secrets.
39 host.mkdir(os.path.dirname(destination), owner, group, perms=0o755)39 host.mkdir(os.path.dirname(destination), owner, group, perms=0o755)
40 with open(source, 'rb') as source_f:40 with open(source, "rb") as source_f:
41 host.write_file(destination, source_f.read(), perms=perms, owner=owner, group=group)41 host.write_file(
42 destination, source_f.read(), perms=perms, owner=owner, group=group
43 )
4244
4345
44class SudoPairHelper(object):46class SudoPairHelper(object):
@@ -47,17 +49,17 @@ class SudoPairHelper(object):
47 def __init__(self):49 def __init__(self):
48 """Retrieve charm config and set defaults."""50 """Retrieve charm config and set defaults."""
49 self.charm_config = hookenv.config()51 self.charm_config = hookenv.config()
50 self.binary_path = '/usr/bin/sudo_approve'52 self.binary_path = "/usr/bin/sudo_approve"
51 self.sudo_conf_path = '/etc/sudo.conf'53 self.sudo_conf_path = "/etc/sudo.conf"
52 self.sudoers_path = '/etc/sudoers'54 self.sudoers_path = "/etc/sudoers"
53 self.sudo_lib_path = '/usr/lib/sudo/sudo_pair.so'55 self.sudo_lib_path = "/usr/lib/sudo/sudo_pair.so"
54 self.sudoers_bypass_path = "/etc/sudoers.d/91-bypass-sudopair-cmds"56 self.sudoers_bypass_path = "/etc/sudoers.d/91-bypass-sudopair-cmds"
55 self.user_prompt_path = '/etc/sudo_pair.prompt.user'57 self.user_prompt_path = "/etc/sudo_pair.prompt.user"
56 self.pair_prompt_path = '/etc/sudo_pair.prompt.pair'58 self.pair_prompt_path = "/etc/sudo_pair.prompt.pair"
57 self.socket_dir = '/var/run/sudo_pair'59 self.socket_dir = "/var/run/sudo_pair"
58 self.tmpfiles_conf = '/usr/lib/tmpfiles.d/sudo_pair.conf'60 self.tmpfiles_conf = "/usr/lib/tmpfiles.d/sudo_pair.conf"
59 self.owner = 'root'61 self.owner = "root"
60 self.group = 'root'62 self.group = "root"
61 self.socket_dir_perms = 0o64463 self.socket_dir_perms = 0o644
62 self.sudo_pair_so_perms = 0o64464 self.sudo_pair_so_perms = 0o644
63 self.prompt_perms = 0o64465 self.prompt_perms = 0o644
@@ -68,12 +70,16 @@ class SudoPairHelper(object):
68 def get_config(self):70 def get_config(self):
69 """Return config as a dict."""71 """Return config as a dict."""
70 config = {72 config = {
71 'binary_path': self.binary_path,73 "binary_path": self.binary_path,
72 'user_prompt_path': self.user_prompt_path,74 "user_prompt_path": self.user_prompt_path,
73 'pair_prompt_path': self.pair_prompt_path,75 "pair_prompt_path": self.pair_prompt_path,
74 'socket_dir': self.socket_dir,76 "socket_dir": self.socket_dir,
75 'gids_enforced': group_names_to_group_ids(self.charm_config['groups_enforced']),77 "gids_enforced": group_names_to_group_ids(
76 'gids_exempted': group_names_to_group_ids(self.charm_config['groups_exempted']),78 self.charm_config["groups_enforced"]
79 ),
80 "gids_exempted": group_names_to_group_ids(
81 self.charm_config["groups_exempted"]
82 ),
77 }83 }
7884
79 config.update(self.charm_config)85 config.update(self.charm_config)
@@ -85,12 +91,23 @@ class SudoPairHelper(object):
8591
86 def render_sudo_conf(self):92 def render_sudo_conf(self):
87 """Render sudo.conf file."""93 """Render sudo.conf file."""
88 return templating.render('sudo.conf.tmpl', self.sudo_conf_path, self.get_config(),94 return templating.render(
89 perms=self.sudo_conf_perms, owner=self.owner, group=self.group)95 "sudo.conf.tmpl",
96 self.sudo_conf_path,
97 self.get_config(),
98 perms=self.sudo_conf_perms,
99 owner=self.owner,
100 group=self.group,
101 )
90102
91 def create_socket_dir(self):103 def create_socket_dir(self):
92 """Create socket dir."""104 """Create socket dir."""
93 host.mkdir(self.socket_dir, perms=self.socket_dir_perms, owner=self.owner, group=self.group)105 host.mkdir(
106 self.socket_dir,
107 perms=self.socket_dir_perms,
108 owner=self.owner,
109 group=self.group,
110 )
94111
95 def create_tmpfiles_conf(self):112 def create_tmpfiles_conf(self):
96 """Create temporary conf file."""113 """Create temporary conf file."""
@@ -99,36 +116,71 @@ class SudoPairHelper(object):
99116
100 def install_sudo_pair_so(self):117 def install_sudo_pair_so(self):
101 """Install sudo-pair lib."""118 """Install sudo-pair lib."""
102 sudo_pair_lib = os.path.join(hookenv.charm_dir(), 'files', 'sudo_pair.so')119 sudo_pair_lib = os.path.join(hookenv.charm_dir(), "files", "sudo_pair.so")
103 copy_file(sudo_pair_lib, self.sudo_lib_path, self.owner, self.group, self.sudo_pair_so_perms)120 copy_file(
121 sudo_pair_lib,
122 self.sudo_lib_path,
123 self.owner,
124 self.group,
125 self.sudo_pair_so_perms,
126 )
104127
105 def copy_user_prompt(self):128 def copy_user_prompt(self):
106 """Copy user prompt on the unit."""129 """Copy user prompt on the unit."""
107 prompt_file = os.path.join(hookenv.charm_dir(), 'files', 'sudo.prompt.user')130 prompt_file = os.path.join(hookenv.charm_dir(), "files", "sudo.prompt.user")
108 copy_file(prompt_file, self.user_prompt_path, self.owner, self.group, self.prompt_perms)131 copy_file(
132 prompt_file,
133 self.user_prompt_path,
134 self.owner,
135 self.group,
136 self.prompt_perms,
137 )
109138
110 def copy_pair_prompt(self):139 def copy_pair_prompt(self):
111 """Copy pair prompt on the unit."""140 """Copy pair prompt on the unit."""
112 prompt_file = os.path.join(hookenv.charm_dir(), 'files', 'sudo.prompt.pair')141 prompt_file = os.path.join(hookenv.charm_dir(), "files", "sudo.prompt.pair")
113 copy_file(prompt_file, self.pair_prompt_path, self.owner, self.group, self.prompt_perms)142 copy_file(
143 prompt_file,
144 self.pair_prompt_path,
145 self.owner,
146 self.group,
147 self.prompt_perms,
148 )
114149
115 def copy_sudoers(self):150 def copy_sudoers(self):
116 """Copy sudoers file on the unit."""151 """Copy sudoers file on the unit."""
117 sudoers_file = os.path.join(hookenv.charm_dir(), 'files', 'sudoers')152 sudoers_file = os.path.join(hookenv.charm_dir(), "files", "sudoers")
118 copy_file(sudoers_file, self.sudoers_path, self.owner, self.group, self.sudoers_perms)153 copy_file(
154 sudoers_file, self.sudoers_path, self.owner, self.group, self.sudoers_perms
155 )
119156
120 def render_sudo_approve(self):157 def render_sudo_approve(self):
121 """Render sudo-approve file."""158 """Render sudo-approve file."""
122 hookenv.log("Rendering sudo_approve.tmpl to {}".format(self.binary_path))159 hookenv.log("Rendering sudo_approve.tmpl to {}".format(self.binary_path))
123 return templating.render('sudo_approve.tmpl', self.binary_path, self.get_config(),160 return templating.render(
124 perms=self.sudo_approve_perms, owner=self.owner, group=self.group)161 "sudo_approve.tmpl",
162 self.binary_path,
163 self.get_config(),
164 perms=self.sudo_approve_perms,
165 owner=self.owner,
166 group=self.group,
167 )
125168
126 def render_bypass_cmds(self):169 def render_bypass_cmds(self):
127 """Render bypass command file."""170 """Render bypass command file."""
128 if self.get_config()['bypass_cmds'] != "" and self.get_config()['bypass_group'] != "":171 if (
172 self.get_config()["bypass_cmds"] != ""
173 and self.get_config()["bypass_group"] != ""
174 ):
129 hookenv.log("Render bypass cmds to {}".format(self.sudoers_bypass_path))175 hookenv.log("Render bypass cmds to {}".format(self.sudoers_bypass_path))
130 return templating.render('91-bypass-sudopair-cmds.tmpl', self.sudoers_bypass_path,176 return templating.render(
131 self.get_config(), perms=0o440, owner=self.owner, group=self.group)177 "91-bypass-sudopair-cmds.tmpl",
178 self.sudoers_bypass_path,
179 self.get_config(),
180 perms=0o440,
181 owner=self.owner,
182 group=self.group,
183 )
132 return None184 return None
133185
134 def deconfigure(self):186 def deconfigure(self):
@@ -140,7 +192,7 @@ class SudoPairHelper(object):
140 self.user_prompt_path,192 self.user_prompt_path,
141 self.pair_prompt_path,193 self.pair_prompt_path,
142 self.sudoers_bypass_path,194 self.sudoers_bypass_path,
143 self.tmpfiles_conf195 self.tmpfiles_conf,
144 ]196 ]
145 hookenv.log("Deleting: {}".format(paths))197 hookenv.log("Deleting: {}".format(paths))
146 for path in paths:198 for path in paths:
@@ -148,4 +200,6 @@ class SudoPairHelper(object):
148 os.unlink(path)200 os.unlink(path)
149 except Exception as e:201 except Exception as e:
150 # We're trying hard to delete all files, even if some might fail202 # We're trying hard to delete all files, even if some might fail
151 hookenv.log("Got exception unlinking {}: {}, continuing".format(path, e))203 hookenv.log(
204 "Got exception unlinking {}: {}, continuing".format(path, e)
205 )
diff --git a/src/reactive/sudo_pair.py b/src/reactive/sudo_pair.py
index 974dd66..ed1d7ba 100644
--- a/src/reactive/sudo_pair.py
+++ b/src/reactive/sudo_pair.py
@@ -7,10 +7,14 @@ from libsudopair import SudoPairHelper
7sph = SudoPairHelper()7sph = SudoPairHelper()
88
99
10@when('apt.installed.socat')10@when("apt.installed.socat")
11@when_not('sudo-pair.configured')11@when_not("sudo-pair.configured")
12def install_sudo_pair():12def install_sudo_pair():
13 # Install sudo_pair.so, create socket dir, copy sudo_approve to /usr/bin, copy prompts to /etc13 """Install sudo pair.
14
15 Install sudo_pair.so, create socket dir, copy sudo_approve to /usr/bin
16 and copy prompts to /etc.
17 """
14 sph.install_sudo_pair_so()18 sph.install_sudo_pair_so()
1519
16 sph.create_socket_dir()20 sph.create_socket_dir()
@@ -32,18 +36,21 @@ def install_sudo_pair():
32 # Add Plugin sudo_pair sudo_pair.so to sudo.conf36 # Add Plugin sudo_pair sudo_pair.so to sudo.conf
33 sph.render_sudo_conf()37 sph.render_sudo_conf()
3438
35 set_state('sudo-pair.installed')39 set_state("sudo-pair.installed")
36 set_state('sudo-pair.configured')40 set_state("sudo-pair.configured")
37 hookenv.status_set('active', 'sudo pairing for users groups: [{}]'.format(sph.get_config()['gids_enforced']))41 hookenv.status_set(
42 "active",
43 "sudo pairing for users groups: [{}]".format(sph.get_config()["gids_enforced"]),
44 )
3845
3946
40@hook('config-changed')47@hook("config-changed")
41def reconfigure_sudo_pair_charm():48def reconfigure_sudo_pair_charm():
42 sph.set_charm_config(hookenv.config())49 sph.set_charm_config(hookenv.config())
43 remove_state('sudo-pair.configured')50 remove_state("sudo-pair.configured")
4451
4552
46@hook('stop')53@hook("stop")
47def stop():54def stop():
48 sph.deconfigure()55 sph.deconfigure()
49 remove_state('sudo-pair.installed')56 remove_state("sudo-pair.installed")
diff --git a/src/tests/functional/conftest.py b/src/tests/functional/conftest.py
index 68f3ffe..7a8523e 100644
--- a/src/tests/functional/conftest.py
+++ b/src/tests/functional/conftest.py
@@ -16,7 +16,7 @@ import pytest
16STAT_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: E50116STAT_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
1717
1818
19@pytest.yield_fixture(scope='module')19@pytest.yield_fixture(scope="module")
20def event_loop(request):20def event_loop(request):
21 """Override the default pytest event loop to allow for broaded scopedv fixtures."""21 """Override the default pytest event loop to allow for broaded scopedv fixtures."""
22 loop = asyncio.get_event_loop_policy().new_event_loop()22 loop = asyncio.get_event_loop_policy().new_event_loop()
@@ -27,7 +27,7 @@ def event_loop(request):
27 asyncio.set_event_loop(None)27 asyncio.set_event_loop(None)
2828
2929
30@pytest.fixture(scope='module')30@pytest.fixture(scope="module")
31async def controller():31async def controller():
32 """Connect to the current controller."""32 """Connect to the current controller."""
33 controller = Controller()33 controller = Controller()
@@ -36,21 +36,21 @@ async def controller():
36 await controller.disconnect()36 await controller.disconnect()
3737
3838
39@pytest.fixture(scope='module')39@pytest.fixture(scope="module")
40async def model(controller):40async def model(controller):
41 """Create a model that lives only for the duration of the test."""41 """Create a model that lives only for the duration of the test."""
42 model_name = "functest-{}".format(uuid.uuid4())42 model_name = "functest-{}".format(uuid.uuid4())
43 model = await controller.add_model(model_name)43 model = await controller.add_model(model_name)
44 yield model44 yield model
45 await model.disconnect()45 await model.disconnect()
46 if os.getenv('PYTEST_KEEP_MODEL'):46 if os.getenv("PYTEST_KEEP_MODEL"):
47 return47 return
48 await controller.destroy_model(model_name)48 await controller.destroy_model(model_name)
49 while model_name in await controller.list_models():49 while model_name in await controller.list_models():
50 await asyncio.sleep(1)50 await asyncio.sleep(1)
5151
5252
53@pytest.fixture(scope='module')53@pytest.fixture(scope="module")
54async def current_model():54async def current_model():
55 """Return the current model, does not create or destroy it."""55 """Return the current model, does not create or destroy it."""
56 model = Model()56 model = Model()
@@ -62,29 +62,34 @@ async def current_model():
62@pytest.fixture62@pytest.fixture
63async def get_app(model):63async def get_app(model):
64 """Return the application requested."""64 """Return the application requested."""
65
65 async def _get_app(name):66 async def _get_app(name):
66 try:67 try:
67 return model.applications[name]68 return model.applications[name]
68 except KeyError:69 except KeyError:
69 raise JujuError("Cannot find application {}".format(name))70 raise JujuError("Cannot find application {}".format(name))
71
70 return _get_app72 return _get_app
7173
7274
73@pytest.fixture75@pytest.fixture
74async def get_unit(model):76async def get_unit(model):
75 """Return the requested <app_name>/<unit_number> unit."""77 """Return the requested <app_name>/<unit_number> unit."""
78
76 async def _get_unit(name):79 async def _get_unit(name):
77 try:80 try:
78 (app_name, unit_number) = name.split('/')81 (app_name, unit_number) = name.split("/")
79 return model.applications[app_name].units[unit_number]82 return model.applications[app_name].units[unit_number]
80 except (KeyError, ValueError):83 except (KeyError, ValueError):
81 raise JujuError("Cannot find unit {}".format(name))84 raise JujuError("Cannot find unit {}".format(name))
85
82 return _get_unit86 return _get_unit
8387
8488
85@pytest.fixture89@pytest.fixture
86async def get_entity(model, get_unit, get_app):90async def get_entity(model, get_unit, get_app):
87 """Return a unit or an application."""91 """Return a unit or an application."""
92
88 async def _get_entity(name):93 async def _get_entity(name):
89 try:94 try:
90 return await get_unit(name)95 return await get_unit(name)
@@ -93,6 +98,7 @@ async def get_entity(model, get_unit, get_app):
93 return await get_app(name)98 return await get_app(name)
94 except JujuError:99 except JujuError:
95 raise JujuError("Cannot find entity {}".format(name))100 raise JujuError("Cannot find entity {}".format(name))
101
96 return _get_entity102 return _get_entity
97103
98104
@@ -104,14 +110,12 @@ async def run_command(get_unit):
104 :param cmd: Command to be run110 :param cmd: Command to be run
105 :param target: Unit object or unit name string111 :param target: Unit object or unit name string
106 """112 """
113
107 async def _run_command(cmd, target):114 async def _run_command(cmd, target):
108 unit = (115 unit = target if type(target) is juju.unit.Unit else await get_unit(target)
109 target
110 if type(target) is juju.unit.Unit
111 else await get_unit(target)
112 )
113 action = await unit.run(cmd)116 action = await unit.run(cmd)
114 return action.results117 return action.results
118
115 return _run_command119 return _run_command
116120
117121
@@ -123,10 +127,12 @@ async def file_stat(run_command):
123 :param path: File path127 :param path: File path
124 :param target: Unit object or unit name string128 :param target: Unit object or unit name string
125 """129 """
130
126 async def _file_stat(path, target):131 async def _file_stat(path, target):
127 cmd = STAT_FILE % path132 cmd = STAT_FILE % path
128 results = await run_command(cmd, target)133 results = await run_command(cmd, target)
129 return json.loads(results['Stdout'])134 return json.loads(results["Stdout"])
135
130 return _file_stat136 return _file_stat
131137
132138
@@ -138,16 +144,19 @@ async def file_contents(run_command):
138 :param path: File path144 :param path: File path
139 :param target: Unit object or unit name string145 :param target: Unit object or unit name string
140 """146 """
147
141 async def _file_contents(path, target):148 async def _file_contents(path, target):
142 cmd = 'cat {}'.format(path)149 cmd = "cat {}".format(path)
143 results = await run_command(cmd, target)150 results = await run_command(cmd, target)
144 return results['Stdout']151 return results["Stdout"]
152
145 return _file_contents153 return _file_contents
146154
147155
148@pytest.fixture156@pytest.fixture
149async def reconfigure_app(get_app, model):157async def reconfigure_app(get_app, model):
150 """Apply a different config to the requested app."""158 """Apply a different config to the requested app."""
159
151 async def _reconfigure_app(cfg, target):160 async def _reconfigure_app(cfg, target):
152 application = (161 application = (
153 target162 target
@@ -156,14 +165,17 @@ async def reconfigure_app(get_app, model):
156 )165 )
157 await application.set_config(cfg)166 await application.set_config(cfg)
158 await application.get_config()167 await application.get_config()
159 await model.block_until(lambda: application.status == 'active')168 await model.block_until(lambda: application.status == "active")
169
160 return _reconfigure_app170 return _reconfigure_app
161171
162172
163@pytest.fixture173@pytest.fixture
164async def create_group(run_command):174async def create_group(run_command):
165 """Create the UNIX group specified."""175 """Create the UNIX group specified."""
176
166 async def _create_group(group_name, target):177 async def _create_group(group_name, target):
167 cmd = "sudo groupadd %s" % group_name178 cmd = "sudo groupadd %s" % group_name
168 await run_command(cmd, target)179 await run_command(cmd, target)
180
169 return _create_group181 return _create_group
diff --git a/src/tests/functional/test_deploy.py b/src/tests/functional/test_deploy.py
index 1d531d1..7f24224 100644
--- a/src/tests/functional/test_deploy.py
+++ b/src/tests/functional/test_deploy.py
@@ -117,7 +117,7 @@ async def test_sudoers_bypass_conf(file_contents, unit):
117117
118118
119async def test_reconfigure(reconfigure_app, file_contents, unit, app):119async def test_reconfigure(reconfigure_app, file_contents, unit, app):
120 """Change a charm config parameter and verify that it has been propagated to the unit."""120 """Change a charm config parameter and verify it is applied."""
121 sudo_approve_path = "/usr/bin/sudo_approve"121 sudo_approve_path = "/usr/bin/sudo_approve"
122 await reconfigure_app(cfg={"auto_approve": "false"}, target=app)122 await reconfigure_app(cfg={"auto_approve": "false"}, target=app)
123 sudo_approve_content = await file_contents(path=sudo_approve_path, target=unit)123 sudo_approve_content = await file_contents(path=sudo_approve_path, target=unit)
diff --git a/src/tests/unit/conftest.py b/src/tests/unit/conftest.py
index c0800ea..9ca50ec 100644
--- a/src/tests/unit/conftest.py
+++ b/src/tests/unit/conftest.py
@@ -14,25 +14,26 @@ def mock_hookenv_config(monkeypatch):
1414
15 def mock_config():15 def mock_config():
16 cfg = {}16 cfg = {}
17 yml = yaml.load(open('./config.yaml'))17 yml = yaml.load(open("./config.yaml"))
1818
19 # Load all defaults19 # Load all defaults
20 for key, value in yml['options'].items():20 for key, value in yml["options"].items():
21 cfg[key] = value['default']21 cfg[key] = value["default"]
2222
23 return cfg23 return cfg
2424
25 monkeypatch.setattr('charmhelpers.core.hookenv.config', mock_config)25 monkeypatch.setattr("charmhelpers.core.hookenv.config", mock_config)
2626
2727
28@pytest.fixture28@pytest.fixture
29def mock_charm_dir(monkeypatch):29def mock_charm_dir(monkeypatch):
30 monkeypatch.setattr('charmhelpers.core.hookenv.charm_dir', lambda: '.')30 monkeypatch.setattr("charmhelpers.core.hookenv.charm_dir", lambda: ".")
3131
3232
33@pytest.fixture33@pytest.fixture
34def sph(mock_hookenv_config, mock_charm_dir, tmpdir):34def sph(mock_hookenv_config, mock_charm_dir, tmpdir):
35 from libsudopair import SudoPairHelper35 from libsudopair import SudoPairHelper
36
36 sph = SudoPairHelper()37 sph = SudoPairHelper()
37 sph.owner = pwd.getpwuid(os.getuid()).pw_name38 sph.owner = pwd.getpwuid(os.getuid()).pw_name
38 sph.group = grp.getgrgid(os.getgid()).gr_name39 sph.group = grp.getgrgid(os.getgid()).gr_name
diff --git a/src/tests/unit/test_actions.py b/src/tests/unit/test_actions.py
index c2da297..d6d86d7 100644
--- a/src/tests/unit/test_actions.py
+++ b/src/tests/unit/test_actions.py
@@ -1,17 +1,18 @@
1import os1import os
2import sys2import sys
3import unittest.mock as mock3import unittest.mock as mock
4action_path = os.path.join(os.path.dirname(__file__), '..', '..', 'actions')4
5action_path = os.path.join(os.path.dirname(__file__), "..", "..", "actions")
5sys.path.append(action_path)6sys.path.append(action_path)
6import actions # NOQA7import actions # NOQA
78
89
9@mock.patch('libsudopair.SudoPairHelper')10@mock.patch("libsudopair.SudoPairHelper")
10@mock.patch('actions.action_set')11@mock.patch("actions.action_set")
11@mock.patch('actions.status_set')12@mock.patch("actions.status_set")
12def test_remove_action(status_set, action_set, sudo_pair_helper):13def test_remove_action(status_set, action_set, sudo_pair_helper):
13 actions.remove()14 actions.remove()
14 msg = 'Successfully removed sudo-pair config and binaries'15 msg = "Successfully removed sudo-pair config and binaries"
15 action_set.assert_called_with({'message': msg})16 action_set.assert_called_with({"message": msg})
16 status_set.assert_called_with('active', msg)17 status_set.assert_called_with("active", msg)
17 sudo_pair_helper().deconfigure.assert_called()18 sudo_pair_helper().deconfigure.assert_called()
diff --git a/src/tests/unit/test_libsudopair.py b/src/tests/unit/test_libsudopair.py
index 3a598af..874f0a3 100644
--- a/src/tests/unit/test_libsudopair.py
+++ b/src/tests/unit/test_libsudopair.py
@@ -2,14 +2,11 @@ import filecmp
2import grp2import grp
3import os3import os
44
5from libsudopair import (5from libsudopair import check_valid_group, group_id
6 check_valid_group,
7 group_id
8)
96
107
11def test_check_valid_group():8def test_check_valid_group():
12 assert not check_valid_group('fake_group')9 assert not check_valid_group("fake_group")
13 assert check_valid_group(grp.getgrgid(os.getgid()).gr_name)10 assert check_valid_group(grp.getgrgid(os.getgid()).gr_name)
1411
1512
@@ -17,7 +14,7 @@ def test_group_id():
17 assert group_id(grp.getgrgid(os.getgid()).gr_name) == os.getgid()14 assert group_id(grp.getgrgid(os.getgid()).gr_name) == os.getgid()
1815
1916
20class TestSudoPairHelper():17class TestSudoPairHelper:
21 """Module to test SudoPairHelper lib."""18 """Module to test SudoPairHelper lib."""
2219
23 def test_pytest(self):20 def test_pytest(self):
@@ -31,12 +28,12 @@ class TestSudoPairHelper():
31 def test_get_config(self, sph):28 def test_get_config(self, sph):
32 """Check if config contains all the required entries."""29 """Check if config contains all the required entries."""
33 default_keywords = [30 default_keywords = [
34 'binary_path',31 "binary_path",
35 'user_prompt_path',32 "user_prompt_path",
36 'pair_prompt_path',33 "pair_prompt_path",
37 'socket_dir',34 "socket_dir",
38 'gids_enforced',35 "gids_enforced",
39 'gids_exempted',36 "gids_exempted",
40 ]37 ]
41 config = sph.get_config()38 config = sph.get_config()
42 for option in default_keywords:39 for option in default_keywords:
@@ -45,11 +42,11 @@ class TestSudoPairHelper():
45 def test_set_charm_config(self, sph):42 def test_set_charm_config(self, sph):
46 """Set new config."""43 """Set new config."""
47 charm_config = {44 charm_config = {
48 'groups_enforced': 'root',45 "groups_enforced": "root",
49 'groups_exempted': '',46 "groups_exempted": "",
50 'bypass_cmds': '',47 "bypass_cmds": "",
51 'bypass_group': '',48 "bypass_group": "",
52 'auto_approve': True49 "auto_approve": True,
53 }50 }
5451
55 sph.set_charm_config(charm_config)52 sph.set_charm_config(charm_config)
@@ -62,67 +59,79 @@ class TestSudoPairHelper():
62 """Check that sudo.conf is rendered correctly."""59 """Check that sudo.conf is rendered correctly."""
63 # Default config60 # Default config
64 content = sph.render_sudo_conf()61 content = sph.render_sudo_conf()
65 expected_content = 'Plugin sudo_pair sudo_pair.so binary_path={} ' \62 expected_content = (
66 'user_prompt_path={} ' \63 "Plugin sudo_pair sudo_pair.so binary_path={} "
67 'pair_prompt_path={} socket_dir={} gids_enforced={}'.format(64 "user_prompt_path={} "
68 tmpdir.join('/usr/bin/sudo_approve'),65 "pair_prompt_path={} socket_dir={} gids_enforced={}".format(
69 tmpdir.join('/etc/sudo_pair.prompt.user'),66 tmpdir.join("/usr/bin/sudo_approve"),
70 tmpdir.join('/etc/sudo_pair.prompt.pair'),67 tmpdir.join("/etc/sudo_pair.prompt.user"),
71 tmpdir.join('/var/run/sudo_pair'),68 tmpdir.join("/etc/sudo_pair.prompt.pair"),
72 '0')69 tmpdir.join("/var/run/sudo_pair"),
70 "0",
71 )
72 )
73 assert expected_content in content73 assert expected_content in content
7474
75 # Gid exempted75 # Gid exempted
76 groups_exempted = grp.getgrgid(os.getgid()).gr_name76 groups_exempted = grp.getgrgid(os.getgid()).gr_name
77 charm_config = {77 charm_config = {
78 'groups_enforced': 'root',78 "groups_enforced": "root",
79 'groups_exempted': groups_exempted,79 "groups_exempted": groups_exempted,
80 'bypass_cmds': '',80 "bypass_cmds": "",
81 'bypass_group': '',81 "bypass_group": "",
82 'auto_approve': True82 "auto_approve": True,
83 }83 }
8484
85 sph.set_charm_config(charm_config)85 sph.set_charm_config(charm_config)
86 expected_content = \86 expected_content = (
87 'Plugin sudo_pair sudo_pair.so binary_path={} user_prompt_path={} ' \87 "Plugin sudo_pair sudo_pair.so binary_path={} user_prompt_path={} "
88 'pair_prompt_path={} socket_dir={} gids_enforced={} gids_exempted={}'.format(88 "pair_prompt_path={} socket_dir={} gids_enforced={} "
89 tmpdir.join('/usr/bin/sudo_approve'),89 "gids_exempted={}".format(
90 tmpdir.join('/etc/sudo_pair.prompt.user'),90 tmpdir.join("/usr/bin/sudo_approve"),
91 tmpdir.join('/etc/sudo_pair.prompt.pair'),91 tmpdir.join("/etc/sudo_pair.prompt.user"),
92 tmpdir.join('/var/run/sudo_pair'), '0', os.getgid())92 tmpdir.join("/etc/sudo_pair.prompt.pair"),
93 tmpdir.join("/var/run/sudo_pair"),
94 "0",
95 os.getgid(),
96 )
97 )
9398
94 content = sph.render_sudo_conf()99 content = sph.render_sudo_conf()
95 assert expected_content in content100 assert expected_content in content
96101
97 # Groups enforced102 # Groups enforced
98 groups_enforced = 'root,' + grp.getgrgid(os.getgid()).gr_name103 groups_enforced = "root," + grp.getgrgid(os.getgid()).gr_name
99 charm_config = {104 charm_config = {
100 'groups_enforced': groups_enforced,105 "groups_enforced": groups_enforced,
101 'groups_exempted': '',106 "groups_exempted": "",
102 'bypass_cmds': '',107 "bypass_cmds": "",
103 'bypass_group': '',108 "bypass_group": "",
104 'auto_approve': True109 "auto_approve": True,
105 }110 }
106 sph.set_charm_config(charm_config)111 sph.set_charm_config(charm_config)
107 expected_content = 'Plugin sudo_pair sudo_pair.so binary_path={} user_prompt_path={} ' \112 expected_content = (
108 'pair_prompt_path={} socket_dir={} gids_enforced={}'.format(113 "Plugin sudo_pair sudo_pair.so binary_path={} user_prompt_path={} "
109 tmpdir.join('/usr/bin/sudo_approve'),114 "pair_prompt_path={} socket_dir={} gids_enforced={}".format(
110 tmpdir.join('/etc/sudo_pair.prompt.user'),115 tmpdir.join("/usr/bin/sudo_approve"),
111 tmpdir.join('/etc/sudo_pair.prompt.pair'),116 tmpdir.join("/etc/sudo_pair.prompt.user"),
112 tmpdir.join('/var/run/sudo_pair'), '0,{}'.format(os.getgid()))117 tmpdir.join("/etc/sudo_pair.prompt.pair"),
118 tmpdir.join("/var/run/sudo_pair"),
119 "0,{}".format(os.getgid()),
120 )
121 )
113 content = sph.render_sudo_conf()122 content = sph.render_sudo_conf()
114 assert expected_content in content123 assert expected_content in content
115124
116 def test_render_bypass_cmds(self, sph):125 def test_render_bypass_cmds(self, sph):
117 """Check that sudoers file is rendered correctly."""126 """Check that sudoers file is rendered correctly."""
118 # Root bypass /bin/ls127 # Root bypass /bin/ls
119 expected_content = '%root ALL = (ALL) NOLOG_OUTPUT: /bin/ls'128 expected_content = "%root ALL = (ALL) NOLOG_OUTPUT: /bin/ls"
120 charm_config = {129 charm_config = {
121 'groups_enforced': 'root',130 "groups_enforced": "root",
122 'groups_exempted': '',131 "groups_exempted": "",
123 'bypass_cmds': '/bin/ls',132 "bypass_cmds": "/bin/ls",
124 'bypass_group': 'root',133 "bypass_group": "root",
125 'auto_approve': True134 "auto_approve": True,
126 }135 }
127 sph.set_charm_config(charm_config)136 sph.set_charm_config(charm_config)
128 content = sph.render_bypass_cmds()137 content = sph.render_bypass_cmds()
@@ -131,9 +140,11 @@ class TestSudoPairHelper():
131 def test_render_sudo_approve(self, sph, tmpdir):140 def test_render_sudo_approve(self, sph, tmpdir):
132 """Check that sudo_approve file is rendered correctly."""141 """Check that sudo_approve file is rendered correctly."""
133 # Auto Approve true142 # Auto Approve true
134 expected_content = 'echo ${log_line} >> /var/log/sudo_pair.log'143 expected_content = "echo ${log_line} >> /var/log/sudo_pair.log"
135 socket_dir = tmpdir.join('/var/run/sudo_pair')144 socket_dir = tmpdir.join("/var/run/sudo_pair")
136 expected_content_socket_dir = 'declare -r SUDO_SOCKET_PATH="{}"'.format(socket_dir)145 expected_content_socket_dir = 'declare -r SUDO_SOCKET_PATH="{}"'.format(
146 socket_dir
147 )
137 content = sph.render_sudo_approve()148 content = sph.render_sudo_approve()
138 assert expected_content in content149 assert expected_content in content
139 assert expected_content_socket_dir in content150 assert expected_content_socket_dir in content
@@ -141,11 +152,11 @@ class TestSudoPairHelper():
141 # Auto Approve false152 # Auto Approve false
142 expected_content = 'echo "You can\'t approve your own session."'153 expected_content = 'echo "You can\'t approve your own session."'
143 charm_config = {154 charm_config = {
144 'groups_enforced': 'root',155 "groups_enforced": "root",
145 'groups_exempted': '',156 "groups_exempted": "",
146 'bypass_cmds': '/bin/ls',157 "bypass_cmds": "/bin/ls",
147 'bypass_group': 'root',158 "bypass_group": "root",
148 'auto_approve': False159 "auto_approve": False,
149 }160 }
150 sph.set_charm_config(charm_config)161 sph.set_charm_config(charm_config)
151 content = sph.render_sudo_approve()162 content = sph.render_sudo_approve()
@@ -154,32 +165,38 @@ class TestSudoPairHelper():
154 def test_create_socket_dir(self, sph, tmpdir):165 def test_create_socket_dir(self, sph, tmpdir):
155 """Check that sudo_pair socket dir exists."""166 """Check that sudo_pair socket dir exists."""
156 sph.create_socket_dir()167 sph.create_socket_dir()
157 assert os.path.exists(tmpdir.join('/var/run/sudo_pair'))168 assert os.path.exists(tmpdir.join("/var/run/sudo_pair"))
158169
159 def test_create_tmpfiles_conf(self, sph, tmpdir):170 def test_create_tmpfiles_conf(self, sph, tmpdir):
160 """Check that sudo pair temporary conf is rendered correctly."""171 """Check that sudo pair temporary conf is rendered correctly."""
161 sph.create_tmpfiles_conf()172 sph.create_tmpfiles_conf()
162 expected_content = 'd {} 0755 - - -\n'.format(sph.socket_dir)173 expected_content = "d {} 0755 - - -\n".format(sph.socket_dir)
163 with open(tmpdir.join('/usr/lib/tmpfiles.d/sudo_pair.conf')) as f:174 with open(tmpdir.join("/usr/lib/tmpfiles.d/sudo_pair.conf")) as f:
164 content = f.read()175 content = f.read()
165 assert expected_content in content176 assert expected_content in content
166177
167 def test_install_sudo_pair_so(self, sph, tmpdir):178 def test_install_sudo_pair_so(self, sph, tmpdir):
168 """Check that sudo system lib exists."""179 """Check that sudo system lib exists."""
169 sph.install_sudo_pair_so()180 sph.install_sudo_pair_so()
170 assert filecmp.cmp('./files/sudo_pair.so', tmpdir.join('/usr/lib/sudo/sudo_pair.so'))181 assert filecmp.cmp(
182 "./files/sudo_pair.so", tmpdir.join("/usr/lib/sudo/sudo_pair.so")
183 )
171184
172 def test_copy_user_prompt(self, sph, tmpdir):185 def test_copy_user_prompt(self, sph, tmpdir):
173 """Check that user prompt exists."""186 """Check that user prompt exists."""
174 sph.copy_user_prompt()187 sph.copy_user_prompt()
175 assert filecmp.cmp('./files/sudo.prompt.user', tmpdir.join('/etc/sudo_pair.prompt.user'))188 assert filecmp.cmp(
189 "./files/sudo.prompt.user", tmpdir.join("/etc/sudo_pair.prompt.user")
190 )
176191
177 def test_copy_pair_prompt(self, sph, tmpdir):192 def test_copy_pair_prompt(self, sph, tmpdir):
178 """Check that pair prompt exists."""193 """Check that pair prompt exists."""
179 sph.copy_pair_prompt()194 sph.copy_pair_prompt()
180 assert filecmp.cmp('./files/sudo.prompt.pair', tmpdir.join('/etc/sudo_pair.prompt.pair'))195 assert filecmp.cmp(
196 "./files/sudo.prompt.pair", tmpdir.join("/etc/sudo_pair.prompt.pair")
197 )
181198
182 def test_copy_sudoers(self, sph, tmpdir):199 def test_copy_sudoers(self, sph, tmpdir):
183 """Check that sudoers file exists."""200 """Check that sudoers file exists."""
184 sph.copy_sudoers()201 sph.copy_sudoers()
185 assert filecmp.cmp('./files/sudoers', tmpdir.join('/etc/sudoers'))202 assert filecmp.cmp("./files/sudoers", tmpdir.join("/etc/sudoers"))
diff --git a/src/tox.ini b/src/tox.ini
index d07b692..2085ea9 100644
--- a/src/tox.ini
+++ b/src/tox.ini
@@ -25,7 +25,7 @@ passenv =
25[testenv:lint]25[testenv:lint]
26commands =26commands =
27 flake827 flake8
28#TODO black --check --exclude "/(\.eggs|\.git|\.tox|\.venv|\.build|dist|charmhelpers|mod)/" .28 black --check --exclude "/(\.eggs|\.git|\.tox|\.venv|\.build|dist|charmhelpers|mod)/" .
29deps =29deps =
30 black30 black
31 flake831 flake8
@@ -43,8 +43,7 @@ exclude =
43 mod,43 mod,
44 .build44 .build
4545
46max-line-length = 12046max-line-length = 88
47#TODO max-line-length = 88
48max-complexity = 1047max-complexity = 10
4948
50[testenv:black]49[testenv:black]

Subscribers

People subscribed via source and target branches