Merge ~ack/maas:1929552-controller-scripts-sudo into maas:master

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: d58d778f802b7956d1dd4710009a405aab64054c
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ack/maas:1929552-controller-scripts-sudo
Merge into: maas:master
Diff against target: 266 lines (+114/-26)
6 files modified
debian/extras/99-maas-sudoers (+10/-0)
src/provisioningserver/refresh/__init__.py (+9/-7)
src/provisioningserver/refresh/maas-lshw (+2/-7)
src/provisioningserver/refresh/tests/test_refresh.py (+75/-4)
src/provisioningserver/utils/ipaddr.py (+4/-1)
src/provisioningserver/utils/tests/test_ipaddr.py (+14/-7)
Reviewer Review Type Date Requested Status
Björn Tillenius Approve
MAAS Lander Approve
Review via email: mp+403268@code.launchpad.net

Commit message

LP: #1929552 - use sudo for running scripts and resources binary on controllers

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b 1929552-controller-scripts-sudo lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10132/console
COMMIT: ac15da820348f9c86c245bafa132eb4da4661ad7

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b 1929552-controller-scripts-sudo lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 5f09031252cd62d5835c8ef7742869bb694ff453

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b 1929552-controller-scripts-sudo lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: d58d778f802b7956d1dd4710009a405aab64054c

review: Approve
Revision history for this message
Björn Tillenius (bjornt) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/debian/extras/99-maas-sudoers b/debian/extras/99-maas-sudoers
index 56c4e94..9a53225 100644
--- a/debian/extras/99-maas-sudoers
+++ b/debian/extras/99-maas-sudoers
@@ -7,3 +7,13 @@ maas ALL= NOPASSWD: /bin/systemctl start maas-dhcpd6
7maas ALL= NOPASSWD: /bin/systemctl restart maas-dhcpd67maas ALL= NOPASSWD: /bin/systemctl restart maas-dhcpd6
8maas ALL= NOPASSWD: /bin/systemctl stop maas-dhcpd68maas ALL= NOPASSWD: /bin/systemctl stop maas-dhcpd6
9maas ALL= NOPASSWD: /bin/systemctl restart maas-rackd9maas ALL= NOPASSWD: /bin/systemctl restart maas-rackd
10maas ALL= NOPASSWD:SETENV: /usr/share/maas/machine-resources/amd64
11maas ALL= NOPASSWD:SETENV: /usr/share/maas/machine-resources/arm64
12maas ALL= NOPASSWD:SETENV: /usr/share/maas/machine-resources/armhf
13maas ALL= NOPASSWD:SETENV: /usr/share/maas/machine-resources/i386
14maas ALL= NOPASSWD:SETENV: /usr/share/maas/machine-resources/ppc64el
15maas ALL= NOPASSWD:SETENV: /usr/share/maas/machine-resources/s390x
16maas ALL= NOPASSWD:SETENV: /usr/lib/python3/dist-packages/provisioningserver/refresh/maas-list-modaliases
17maas ALL= NOPASSWD:SETENV: /usr/lib/python3/dist-packages/provisioningserver/refresh/maas-lshw
18maas ALL= NOPASSWD:SETENV: /usr/lib/python3/dist-packages/provisioningserver/refresh/maas-serial-ports
19maas ALL= NOPASSWD:SETENV: /usr/lib/python3/dist-packages/provisioningserver/refresh/maas-support-info
diff --git a/src/provisioningserver/refresh/__init__.py b/src/provisioningserver/refresh/__init__.py
index ea984b8..7e7a240 100644
--- a/src/provisioningserver/refresh/__init__.py
+++ b/src/provisioningserver/refresh/__init__.py
@@ -21,7 +21,7 @@ from provisioningserver.refresh.node_info_scripts import (
21 LXD_OUTPUT_NAME,21 LXD_OUTPUT_NAME,
22 NODE_INFO_SCRIPTS,22 NODE_INFO_SCRIPTS,
23)23)
24from provisioningserver.utils.snap import SnapPaths24from provisioningserver.utils.snap import running_in_snap, SnapPaths
25from provisioningserver.utils.twisted import synchronous25from provisioningserver.utils.twisted import synchronous
2626
27maaslog = get_maas_logger("refresh")27maaslog = get_maas_logger("refresh")
@@ -97,15 +97,15 @@ def refresh(
97 post_process_hook=post_process_hook,97 post_process_hook=post_process_hook,
98 )98 )
9999
100 if len(failed_scripts) == 0:100 if failed_scripts:
101 signal_wrapper(url, creds, "OK", "Finished refreshing %s" % system_id)101 signal_wrapper(url, creds, "FAILED", f"Failed refreshing {system_id}")
102 else:102 else:
103 signal_wrapper(103 signal_wrapper(url, creds, "OK", f"Finished refreshing {system_id}")
104 url, creds, "FAILED", "Failed refreshing %s" % system_id
105 )
106104
107105
108def runscripts(scripts, url, creds, tmpdir, post_process_hook=None):106def runscripts(scripts, url, creds, tmpdir, post_process_hook=None):
107 in_snap = running_in_snap()
108
109 total_scripts = len(scripts)109 total_scripts = len(scripts)
110 current_script = 1110 current_script = 1
111 failed_scripts = []111 failed_scripts = []
@@ -143,9 +143,11 @@ def runscripts(scripts, url, creds, tmpdir, post_process_hook=None):
143143
144 timeout = 60144 timeout = 60
145145
146 command = [script_path] if in_snap else ["sudo", "-E", script_path]
147
146 try:148 try:
147 proc = Popen(149 proc = Popen(
148 script_path, stdin=DEVNULL, stdout=PIPE, stderr=PIPE, env=env150 command, stdin=DEVNULL, stdout=PIPE, stderr=PIPE, env=env
149 )151 )
150 capture_script_output(152 capture_script_output(
151 proc, combined_path, stdout_path, stderr_path, timeout153 proc, combined_path, stdout_path, stderr_path, timeout
diff --git a/src/provisioningserver/refresh/maas-lshw b/src/provisioningserver/refresh/maas-lshw
index ce84388..3779061 100755
--- a/src/provisioningserver/refresh/maas-lshw
+++ b/src/provisioningserver/refresh/maas-lshw
@@ -26,10 +26,5 @@
26# timeout: 00:05:0026# timeout: 00:05:00
27# --- End MAAS 1.0 script metadata ---27# --- End MAAS 1.0 script metadata ---
2828
29# The script is run both on controllers (which can be run inside a snap) and29# the SNAP var is empty if not running in a snap
30# commissioned machines.30"$SNAP/usr/bin/lshw" -xml
31if [ -z "$SNAP" ]; then
32 sudo -n /usr/bin/lshw -xml
33else
34 "$SNAP/usr/bin/lshw" -xml
35fi
diff --git a/src/provisioningserver/refresh/tests/test_refresh.py b/src/provisioningserver/refresh/tests/test_refresh.py
index 8dcec18..7f841f0 100644
--- a/src/provisioningserver/refresh/tests/test_refresh.py
+++ b/src/provisioningserver/refresh/tests/test_refresh.py
@@ -1,18 +1,16 @@
1# Copyright 2016-2020 Canonical Ltd. This software is licensed under the1# Copyright 2016-2020 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test refresh functions."""
5
6
7from collections import OrderedDict4from collections import OrderedDict
8import os5import os
9from pathlib import Path6from pathlib import Path
10import random7import random
11import stat8import stat
9import subprocess
12import sys10import sys
13import tempfile11import tempfile
14from textwrap import dedent12from textwrap import dedent
15from unittest.mock import patch, sentinel13from unittest.mock import ANY, patch, sentinel
1614
17from testtools.matchers import Contains, DirExists, Not15from testtools.matchers import Contains, DirExists, Not
1816
@@ -55,6 +53,9 @@ class TestRefresh(MAASTestCase):
55 # When running scripts in a tty MAAS outputs the results to help with53 # When running scripts in a tty MAAS outputs the results to help with
56 # debug. Quiet the output when running in tests.54 # debug. Quiet the output when running in tests.
57 self.patch(sys, "stdout")55 self.patch(sys, "stdout")
56 # by default, fake running in snap so sudo is not used
57 self.mock_running_in_snap = self.patch(refresh, "running_in_snap")
58 self.mock_running_in_snap.return_value = True
5859
59 def _cleanup(self, path):60 def _cleanup(self, path):
60 if os.path.exists(path):61 if os.path.exists(path):
@@ -581,3 +582,73 @@ class TestRefresh(MAASTestCase):
581 self.assertThat(582 self.assertThat(
582 maaslog, MockAnyCall("Error during controller refresh: %s" % error)583 maaslog, MockAnyCall("Error during controller refresh: %s" % error)
583 )584 )
585
586 def test_refresh_runs_script_no_sudo_snap(self):
587 mock_popen = self.patch(refresh, "Popen")
588 mock_popen.side_effect = OSError()
589 self.patch(refresh, "signal")
590 script_name = factory.make_name("script_name")
591 script_content = dedent(
592 """\
593 #!/bin/bash
594 echo 'test script'
595 """
596 )
597 info_scripts = self.create_scripts_success(
598 script_name, script_content=script_content
599 )
600
601 system_id = factory.make_name("system_id")
602 consumer_key = factory.make_name("consumer_key")
603 token_key = factory.make_name("token_key")
604 token_secret = factory.make_name("token_secret")
605 url = factory.make_url()
606
607 with patch.dict(refresh.NODE_INFO_SCRIPTS, info_scripts, clear=True):
608 refresh.refresh(
609 system_id, consumer_key, token_key, token_secret, url
610 )
611 script_path = (Path(refresh.__file__) / ".." / script_name).resolve()
612 mock_popen.assert_called_once_with(
613 [str(script_path)],
614 stdin=subprocess.DEVNULL,
615 stdout=subprocess.PIPE,
616 stderr=subprocess.PIPE,
617 env=ANY,
618 )
619
620 def test_refresh_runs_script_sudo_if_no_snap(self):
621 self.mock_running_in_snap.return_value = False
622 mock_popen = self.patch(refresh, "Popen")
623 mock_popen.side_effect = OSError()
624 self.patch(refresh, "signal")
625 script_name = factory.make_name("script_name")
626 script_content = dedent(
627 """\
628 #!/bin/bash
629 echo 'test script'
630 echo '{status: skipped}' > $RESULT_PATH
631 """
632 )
633 info_scripts = self.create_scripts_success(
634 script_name, script_content=script_content
635 )
636
637 system_id = factory.make_name("system_id")
638 consumer_key = factory.make_name("consumer_key")
639 token_key = factory.make_name("token_key")
640 token_secret = factory.make_name("token_secret")
641 url = factory.make_url()
642
643 with patch.dict(refresh.NODE_INFO_SCRIPTS, info_scripts, clear=True):
644 refresh.refresh(
645 system_id, consumer_key, token_key, token_secret, url
646 )
647 script_path = (Path(refresh.__file__) / ".." / script_name).resolve()
648 mock_popen.assert_called_once_with(
649 ["sudo", "-E", str(script_path)],
650 stdin=subprocess.DEVNULL,
651 stdout=subprocess.PIPE,
652 stderr=subprocess.PIPE,
653 env=ANY,
654 )
diff --git a/src/provisioningserver/utils/ipaddr.py b/src/provisioningserver/utils/ipaddr.py
index f6f4689..a24d644 100644
--- a/src/provisioningserver/utils/ipaddr.py
+++ b/src/provisioningserver/utils/ipaddr.py
@@ -12,6 +12,7 @@ import netifaces
12from provisioningserver.refresh import get_resources_bin_path12from provisioningserver.refresh import get_resources_bin_path
13from provisioningserver.utils.lxd import parse_lxd_networks13from provisioningserver.utils.lxd import parse_lxd_networks
14from provisioningserver.utils.shell import call_and_check14from provisioningserver.utils.shell import call_and_check
15from provisioningserver.utils.snap import running_in_snap
1516
1617
17def get_ip_addr():18def get_ip_addr():
@@ -20,7 +21,9 @@ def get_ip_addr():
20 :raises:ExternalProcessError: if IP address information could not be21 :raises:ExternalProcessError: if IP address information could not be
21 gathered.22 gathered.
22 """23 """
23 output = call_and_check([get_resources_bin_path()])24 cmd_path = get_resources_bin_path()
25 command = [cmd_path] if running_in_snap() else ["sudo", cmd_path]
26 output = call_and_check(command)
24 ifaces = parse_lxd_networks(json.loads(output)["networks"])27 ifaces = parse_lxd_networks(json.loads(output)["networks"])
25 _update_interface_type(ifaces)28 _update_interface_type(ifaces)
26 _annotate_with_proc_net_bonding_original_macs(ifaces)29 _annotate_with_proc_net_bonding_original_macs(ifaces)
diff --git a/src/provisioningserver/utils/tests/test_ipaddr.py b/src/provisioningserver/utils/tests/test_ipaddr.py
index 5907c4a..02663c9 100644
--- a/src/provisioningserver/utils/tests/test_ipaddr.py
+++ b/src/provisioningserver/utils/tests/test_ipaddr.py
@@ -1,9 +1,6 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test parser for 'ip addr show'."""
5
6
7import json4import json
8import os5import os
9from pathlib import Path6from pathlib import Path
@@ -14,7 +11,6 @@ from textwrap import dedent
14import netifaces11import netifaces
1512
16from maastesting.factory import factory13from maastesting.factory import factory
17from maastesting.matchers import MockCalledOnceWith
18from maastesting.testcase import MAASTestCase14from maastesting.testcase import MAASTestCase
19from provisioningserver.refresh import get_resources_bin_path15from provisioningserver.refresh import get_resources_bin_path
20from provisioningserver.utils import ipaddr as ipaddr_module16from provisioningserver.utils import ipaddr as ipaddr_module
@@ -133,15 +129,26 @@ class TestGetIPAddr(MAASTestCase):
133 """Tests for `get_ip_addr`, `get_mac_addresses`."""129 """Tests for `get_ip_addr`, `get_mac_addresses`."""
134130
135 def test_get_ip_addr_calls_methods(self):131 def test_get_ip_addr_calls_methods(self):
132 self.patch(ipaddr_module, "running_in_snap").return_value = False
136 patch_call_and_check = self.patch(ipaddr_module, "call_and_check")133 patch_call_and_check = self.patch(ipaddr_module, "call_and_check")
137 patch_call_and_check.return_value = json.dumps(134 patch_call_and_check.return_value = json.dumps(
138 {"networks": SAMPLE_LXD_NETWORKS}135 {"networks": SAMPLE_LXD_NETWORKS}
139 )136 )
140 # all interfaces from binary output are included in result137 # all interfaces from binary output are included in result
141 self.assertCountEqual(SAMPLE_LXD_NETWORKS, get_ip_addr())138 self.assertCountEqual(SAMPLE_LXD_NETWORKS, get_ip_addr())
142 self.assertThat(139 patch_call_and_check.assert_called_once_with(
143 patch_call_and_check,140 ["sudo", get_resources_bin_path()]
144 MockCalledOnceWith([get_resources_bin_path()]),141 )
142
143 def test_no_use_sudo_in_snap(self):
144 patch_call_and_check = self.patch(ipaddr_module, "call_and_check")
145 patch_call_and_check.return_value = json.dumps(
146 {"networks": SAMPLE_LXD_NETWORKS}
147 )
148 self.patch(ipaddr_module, "running_in_snap").return_value = True
149 get_ip_addr()
150 patch_call_and_check.assert_called_once_with(
151 [get_resources_bin_path()]
145 )152 )
146153
147 def test_get_mac_addresses_returns_all_mac_addresses(self):154 def test_get_mac_addresses_returns_all_mac_addresses(self):

Subscribers

People subscribed via source and target branches