[sunbeam-machine] Add proxy configs
Add http_proxy, https_proxy, no_proxy as configuration to charm. Update the content of /etc/environment file on setting the proxy configs. If the proxy configuration is not set on sunbeam-machine, clear the corresponding configs from /etc/environment. Change-Id: I5ba444d87e199a4314439eddf513729138c40c3f
This commit is contained in:
parent
496d48a237
commit
ada047a90f
@ -6,4 +6,12 @@ options:
|
|||||||
debug:
|
debug:
|
||||||
default: False
|
default: False
|
||||||
type: boolean
|
type: boolean
|
||||||
|
http_proxy:
|
||||||
|
description: Set HTTP_PROXY in /etc/environment
|
||||||
|
type: string
|
||||||
|
https_proxy:
|
||||||
|
description: Set HTTPS_PROXY in /etc/environment
|
||||||
|
type: string
|
||||||
|
no_proxy:
|
||||||
|
description: Set NO_PROXY in /etc/environment
|
||||||
|
type: string
|
||||||
|
@ -33,6 +33,7 @@ from ops.main import (
|
|||||||
main,
|
main,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ETC_ENVIRONMENT = "/etc/environment"
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ class SunbeamMachineCharm(sunbeam_charm.OSBaseOperatorCharm):
|
|||||||
|
|
||||||
_state = ops.framework.StoredState()
|
_state = ops.framework.StoredState()
|
||||||
service_name = "sunbeam-machine"
|
service_name = "sunbeam-machine"
|
||||||
|
proxy_configs = ["http_proxy", "https_proxy", "no_proxy"]
|
||||||
|
|
||||||
def __init__(self, framework: ops.Framework) -> None:
|
def __init__(self, framework: ops.Framework) -> None:
|
||||||
super().__init__(framework)
|
super().__init__(framework)
|
||||||
@ -64,6 +66,34 @@ class SunbeamMachineCharm(sunbeam_charm.OSBaseOperatorCharm):
|
|||||||
logger.error("Error executing sysctl", exc_info=True)
|
logger.error("Error executing sysctl", exc_info=True)
|
||||||
raise sunbeam_guard.BlockedExceptionError("Sysctl command failed")
|
raise sunbeam_guard.BlockedExceptionError("Sysctl command failed")
|
||||||
|
|
||||||
|
def _on_config_changed(self, event: ops.ConfigChangedEvent):
|
||||||
|
self.configure_charm(event)
|
||||||
|
with open(ETC_ENVIRONMENT, mode="r", encoding="utf-8") as file:
|
||||||
|
current_env = dict(
|
||||||
|
line.strip().split("=", 1) for line in file if "=" in line
|
||||||
|
)
|
||||||
|
logger.info(f"Existing content of /etc/environment: {current_env}")
|
||||||
|
|
||||||
|
proxy = {p: v for p in self.proxy_configs if (v := self.config.get(p))}
|
||||||
|
if all(
|
||||||
|
proxy.get(p) == current_env.get(p.upper())
|
||||||
|
for p in self.proxy_configs
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove proxies not set
|
||||||
|
not_set_proxies = self.proxy_configs - proxy.keys()
|
||||||
|
for p in not_set_proxies:
|
||||||
|
if (p_upper := p.upper()) in current_env:
|
||||||
|
del current_env[p_upper]
|
||||||
|
|
||||||
|
# Capitalise proxy keys and update env
|
||||||
|
proxy_in_caps = {k.upper(): v for k, v in proxy.items()}
|
||||||
|
current_env.update(proxy_in_caps)
|
||||||
|
|
||||||
|
with open(ETC_ENVIRONMENT, mode="w", encoding="utf-8") as file:
|
||||||
|
file.write("\n".join([f"{k}={v}" for k, v in current_env.items()]))
|
||||||
|
|
||||||
def _on_remove(self, event: ops.RemoveEvent):
|
def _on_remove(self, event: ops.RemoveEvent):
|
||||||
self.sysctl.remove()
|
self.sysctl.remove()
|
||||||
|
|
||||||
|
@ -14,6 +14,11 @@
|
|||||||
|
|
||||||
"""Tests for Sunbeam Machine charm."""
|
"""Tests for Sunbeam Machine charm."""
|
||||||
|
|
||||||
|
from unittest.mock import (
|
||||||
|
mock_open,
|
||||||
|
patch,
|
||||||
|
)
|
||||||
|
|
||||||
import charm
|
import charm
|
||||||
import ops_sunbeam.test_utils as test_utils
|
import ops_sunbeam.test_utils as test_utils
|
||||||
|
|
||||||
@ -30,7 +35,7 @@ class _SunbeamMachineCharm(charm.SunbeamMachineCharm):
|
|||||||
class TestCharm(test_utils.CharmTestCase):
|
class TestCharm(test_utils.CharmTestCase):
|
||||||
"""Classes for testing Sunbeam Machine charm."""
|
"""Classes for testing Sunbeam Machine charm."""
|
||||||
|
|
||||||
PATCHES = []
|
PATCHES = ["sysctl"]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup Sunbeam machine tests."""
|
"""Setup Sunbeam machine tests."""
|
||||||
@ -46,5 +51,108 @@ class TestCharm(test_utils.CharmTestCase):
|
|||||||
|
|
||||||
def test_initial(self):
|
def test_initial(self):
|
||||||
"""Bootstrap test initial."""
|
"""Bootstrap test initial."""
|
||||||
self.harness.begin_with_initial_hooks()
|
file_content_dict = {"PATH": "FAKEPATH"}
|
||||||
|
env_file_content = "\n".join(
|
||||||
|
f"{k}={v}" for k, v in file_content_dict.items()
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"builtins.open", new_callable=mock_open, read_data=env_file_content
|
||||||
|
) as mock_file:
|
||||||
|
self.harness.begin_with_initial_hooks()
|
||||||
|
mock_file().write.assert_not_called()
|
||||||
|
|
||||||
self.assertTrue(self.harness.charm.bootstrapped())
|
self.assertTrue(self.harness.charm.bootstrapped())
|
||||||
|
|
||||||
|
def test_proxy_settings(self):
|
||||||
|
"""Test setting proxies."""
|
||||||
|
# test_data is a tuple of /etc/environment file content as dict, proxy config as dict,
|
||||||
|
# expected content as dict
|
||||||
|
# As the below tests are run in loop as subtests, they act as juju config commands.
|
||||||
|
# Means the configs set in the previous test data remains until it is reset by
|
||||||
|
# specifying config as empty string.
|
||||||
|
test_data = [
|
||||||
|
# Case 1: No proxy in environment file, set http_proxy, https_proxy
|
||||||
|
(
|
||||||
|
{"PATH": "FAKEPATH"},
|
||||||
|
{
|
||||||
|
"http_proxy": "http://proxyserver:3128",
|
||||||
|
"https_proxy": "http://proxyserver:3128",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PATH": "FAKEPATH",
|
||||||
|
"HTTP_PROXY": "http://proxyserver:3128",
|
||||||
|
"HTTPS_PROXY": "http://proxyserver:3128",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# Case 2: Add no_proxy to above configuration
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"PATH": "FAKEPATH",
|
||||||
|
"HTTP_PROXY": "http://proxyserver:3128",
|
||||||
|
"HTTPS_PROXY": "http://proxyserver:3128",
|
||||||
|
},
|
||||||
|
{"no_proxy": "localhost,127.0.0.1"},
|
||||||
|
{
|
||||||
|
"PATH": "FAKEPATH",
|
||||||
|
"HTTP_PROXY": "http://proxyserver:3128",
|
||||||
|
"HTTPS_PROXY": "http://proxyserver:3128",
|
||||||
|
"NO_PROXY": "localhost,127.0.0.1",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# Case 3: Update http proxy to different value
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"PATH": "FAKEPATH",
|
||||||
|
"HTTP_PROXY": "http://proxyserver:3128",
|
||||||
|
"HTTPS_PROXY": "http://proxyserver:3128",
|
||||||
|
"NO_PROXY": "localhost,127.0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"http_proxy": "http://proxyserver:3120",
|
||||||
|
"https_proxy": "http://proxyserver:3128",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PATH": "FAKEPATH",
|
||||||
|
"HTTP_PROXY": "http://proxyserver:3120",
|
||||||
|
"HTTPS_PROXY": "http://proxyserver:3128",
|
||||||
|
"NO_PROXY": "localhost,127.0.0.1",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# Case 4: Reset the no_proxy config
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"PATH": "FAKEPATH",
|
||||||
|
"HTTP_PROXY": "http://proxyserver:3120",
|
||||||
|
"HTTPS_PROXY": "http://proxyserver:3128",
|
||||||
|
"NO_PROXY": "localhost,127.0.0.1",
|
||||||
|
},
|
||||||
|
{"no_proxy": ""},
|
||||||
|
{
|
||||||
|
"PATH": "FAKEPATH",
|
||||||
|
"HTTP_PROXY": "http://proxyserver:3120",
|
||||||
|
"HTTPS_PROXY": "http://proxyserver:3128",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"builtins.open", new_callable=mock_open, read_data=""
|
||||||
|
) as mock_file:
|
||||||
|
self.harness.begin_with_initial_hooks()
|
||||||
|
|
||||||
|
for index, d in enumerate(test_data):
|
||||||
|
with self.subTest(msg=f"test_proxy_settings-{index}", data=d):
|
||||||
|
env_file_content = "\n".join(
|
||||||
|
f"{k}={v}" for k, v in d[0].items()
|
||||||
|
)
|
||||||
|
expected_content = "\n".join(
|
||||||
|
f"{k}={v}" for k, v in d[2].items()
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"builtins.open",
|
||||||
|
new_callable=mock_open,
|
||||||
|
read_data=env_file_content,
|
||||||
|
) as mock_file:
|
||||||
|
self.harness.update_config(d[1])
|
||||||
|
mock_file().write.assert_called_with(expected_content)
|
||||||
|
Loading…
Reference in New Issue
Block a user