[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:
Hemanth Nakkina 2024-03-12 14:46:54 +05:30
parent 496d48a237
commit ada047a90f
No known key found for this signature in database
GPG Key ID: 2E4970F7B143168E
3 changed files with 149 additions and 3 deletions

View File

@ -6,4 +6,12 @@ options:
debug:
default: False
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

View File

@ -33,6 +33,7 @@ from ops.main import (
main,
)
ETC_ENVIRONMENT = "/etc/environment"
logger = logging.getLogger(__name__)
@ -41,6 +42,7 @@ class SunbeamMachineCharm(sunbeam_charm.OSBaseOperatorCharm):
_state = ops.framework.StoredState()
service_name = "sunbeam-machine"
proxy_configs = ["http_proxy", "https_proxy", "no_proxy"]
def __init__(self, framework: ops.Framework) -> None:
super().__init__(framework)
@ -64,6 +66,34 @@ class SunbeamMachineCharm(sunbeam_charm.OSBaseOperatorCharm):
logger.error("Error executing sysctl", exc_info=True)
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):
self.sysctl.remove()

View File

@ -14,6 +14,11 @@
"""Tests for Sunbeam Machine charm."""
from unittest.mock import (
mock_open,
patch,
)
import charm
import ops_sunbeam.test_utils as test_utils
@ -30,7 +35,7 @@ class _SunbeamMachineCharm(charm.SunbeamMachineCharm):
class TestCharm(test_utils.CharmTestCase):
"""Classes for testing Sunbeam Machine charm."""
PATCHES = []
PATCHES = ["sysctl"]
def setUp(self):
"""Setup Sunbeam machine tests."""
@ -46,5 +51,108 @@ class TestCharm(test_utils.CharmTestCase):
def test_initial(self):
"""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())
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)