Skip to content

Commit

Permalink
feat: proxy (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanksyoon authored Jan 24, 2024
1 parent 62b84cf commit b801b09
Show file tree
Hide file tree
Showing 13 changed files with 470 additions and 41 deletions.
1 change: 1 addition & 0 deletions .github/workflows/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ jobs:
./tests/integration/pre_run_script.sh"
self-hosted-runner: true
self-hosted-runner-label: "edge"
modules: '["test_charm", "test_proxy"]'
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ops>=2,<3
jinja2>=3,<4
pydantic>=2,<3
104 changes: 102 additions & 2 deletions src-docs/state.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,37 @@ tmate-ssh-server states.
- **DEBUG_SSH_INTEGRATION_NAME**


---

## <kbd>class</kbd> `CharmConfigInvalidError`
Exception raised when a charm configuration is found to be invalid.



**Attributes:**

- <b>`msg`</b>: Explanation of the error.

<a href="../src/state.py#L42"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

```python
__init__(msg: str)
```

Initialize a new instance of the CharmConfigInvalidError exception.



**Args:**

- <b>`msg`</b>: Explanation of the error.





---

## <kbd>class</kbd> `CharmStateBaseError`
Expand All @@ -24,7 +55,7 @@ Represents an error with charm state.
## <kbd>class</kbd> `InvalidCharmStateError`
Represents an invalid charm state.

<a href="../src/state.py#L21"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/state.py#L26"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

Expand All @@ -44,6 +75,73 @@ Initialize the error.



---

## <kbd>class</kbd> `ProxyConfig`
Configuration for accessing Jenkins through proxy.



**Attributes:**

- <b>`http_proxy`</b>: The http proxy URL.
- <b>`https_proxy`</b>: The https proxy URL.
- <b>`no_proxy`</b>: Comma separated list of hostnames to bypass proxy.


---

#### <kbd>property</kbd> model_computed_fields

Get the computed fields of this model instance.



**Returns:**
A dictionary of computed field names and their corresponding `ComputedFieldInfo` objects.

---

#### <kbd>property</kbd> model_extra

Get extra fields set during validation.



**Returns:**
A dictionary of extra fields, or `None` if `config.extra` is not set to `"allow"`.

---

#### <kbd>property</kbd> model_fields_set

Returns the set of fields that have been explicitly set on this model instance.



**Returns:**
A set of strings representing the fields that have been set, i.e. that were not filled from defaults.



---

<a href="../src/state.py#L64"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>classmethod</kbd> `from_env`

```python
from_env() → Optional[ForwardRef('ProxyConfig')]
```

Instantiate ProxyConfig from juju charm environment.



**Returns:**
ProxyConfig if proxy configuration is provided, None otherwise.


---

## <kbd>class</kbd> `State`
Expand All @@ -54,13 +152,14 @@ The tmate-ssh-server operator charm state.
**Attributes:**

- <b>`ip_addr`</b>: The host IP address of the given tmate-ssh-server unit.
- <b>`proxy_config`</b>: The proxy configuration to apply to services used by tmate.




---

<a href="../src/state.py#L40"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/state.py#L94"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>classmethod</kbd> `from_charm`

Expand All @@ -86,5 +185,6 @@ Initialize the state from charm.
**Raises:**

- <b>`InvalidCharmStateError`</b>: if the network bind address was not of IPv4/IPv6.
- <b>`CharmConfigInvalidError`</b>: if there was something wrong with charm configuration values.


19 changes: 13 additions & 6 deletions src-docs/tmate.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,39 @@ Configurations and functions to operate tmate-ssh-server.
---------------
- **APT_DEPENDENCIES**
- **GIT_REPOSITORY_URL**
- **TMATE_SERVICE_NAME**
- **USER**
- **GROUP**
- **PORT**

---

<a href="../src/tmate.py#L59"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/tmate.py#L99"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `install_dependencies`

```python
install_dependencies() → None
install_dependencies(proxy_config: Optional[ProxyConfig] = None) → None
```

Install dependenciese required to start tmate-ssh-server container.



**Args:**

- <b>`proxy_config`</b>: The proxy configuration to enable for dockerd.



**Raises:**

- <b>`DependencySetupError`</b>: if there was something wrong installing the apt package dependencies.


---

<a href="../src/tmate.py#L78"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/tmate.py#L116"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `install_keys`

Expand All @@ -59,7 +66,7 @@ Install key creation script and generate keys.

---

<a href="../src/tmate.py#L103"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/tmate.py#L141"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `start_daemon`

Expand All @@ -84,7 +91,7 @@ Install unit files and start daemon.

---

<a href="../src/tmate.py#L157"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/tmate.py#L192"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `get_fingerprints`

Expand All @@ -108,7 +115,7 @@ Get fingerprint from generated keys.

---

<a href="../src/tmate.py#L181"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/tmate.py#L216"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `generate_tmate_conf`

Expand Down
2 changes: 1 addition & 1 deletion src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _on_install(self, event: ops.InstallEvent) -> None:

try:
self.unit.status = ops.MaintenanceStatus("Installing packages.")
tmate.install_dependencies()
tmate.install_dependencies(self.state.proxy_config)
except tmate.DependencySetupError as exc:
logger.error("Failed to install docker package, %s.", exc)
raise
Expand Down
70 changes: 67 additions & 3 deletions src/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
"""tmate-ssh-server states."""
import dataclasses
import ipaddress
import logging
import os
import typing

import ops
from pydantic import BaseModel, HttpUrl, ValidationError

logger = logging.getLogger(__name__)

DEBUG_SSH_INTEGRATION_NAME = "debug-ssh"

Expand All @@ -27,15 +32,64 @@ def __init__(self, reason: str):
self.reason = reason


class CharmConfigInvalidError(CharmStateBaseError):
"""Exception raised when a charm configuration is found to be invalid.
Attributes:
msg: Explanation of the error.
"""

def __init__(self, msg: str):
"""Initialize a new instance of the CharmConfigInvalidError exception.
Args:
msg: Explanation of the error.
"""
self.msg = msg


class ProxyConfig(BaseModel):
"""Configuration for accessing Jenkins through proxy.
Attributes:
http_proxy: The http proxy URL.
https_proxy: The https proxy URL.
no_proxy: Comma separated list of hostnames to bypass proxy.
"""

http_proxy: typing.Optional[HttpUrl]
https_proxy: typing.Optional[HttpUrl]
no_proxy: typing.Optional[str]

@classmethod
def from_env(cls) -> typing.Optional["ProxyConfig"]:
"""Instantiate ProxyConfig from juju charm environment.
Returns:
ProxyConfig if proxy configuration is provided, None otherwise.
"""
http_proxy = os.environ.get("JUJU_CHARM_HTTP_PROXY")
https_proxy = os.environ.get("JUJU_CHARM_HTTPS_PROXY")
no_proxy = os.environ.get("JUJU_CHARM_NO_PROXY")
if not http_proxy and not https_proxy:
return None
# Mypy doesn't understand str is supposed to be converted to HttpUrl by Pydantic.
return cls(
http_proxy=http_proxy, https_proxy=https_proxy, no_proxy=no_proxy # type: ignore
)


@dataclasses.dataclass(frozen=True)
class State:
"""The tmate-ssh-server operator charm state.
Attributes:
ip_addr: The host IP address of the given tmate-ssh-server unit.
proxy_config: The proxy configuration to apply to services used by tmate.
"""

ip_addr: typing.Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str, None]
ip_addr: typing.Optional[typing.Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str]]
proxy_config: typing.Optional[ProxyConfig]

@classmethod
def from_charm(cls, charm: ops.CharmBase) -> "State":
Expand All @@ -49,14 +103,24 @@ def from_charm(cls, charm: ops.CharmBase) -> "State":
Raises:
InvalidCharmStateError: if the network bind address was not of IPv4/IPv6.
CharmConfigInvalidError: if there was something wrong with charm configuration values.
"""
try:
proxy_config = ProxyConfig.from_env()
except ValidationError as exc:
logger.error("Invalid juju model proxy configuration, %s", exc)
raise CharmConfigInvalidError("Invalid model proxy configuration.") from exc

binding = charm.model.get_binding("juju-info")
if not binding:
return cls(ip_addr=None)
return cls(ip_addr=None, proxy_config=proxy_config)
# If unable to get a casted IPvX address, it is not useful.
# https://github.com/canonical/operator/blob/8a08e8e1b389fce4e7b54663863c4b2d06e72224/ops/model.py#L939-L947
if isinstance(binding.network.bind_address, str):
raise InvalidCharmStateError(
f"Invalid network bind address {binding.network.bind_address}."
)
return cls(ip_addr=binding.network.bind_address if binding else None)

return cls(
ip_addr=binding.network.bind_address if binding else None, proxy_config=proxy_config
)
Loading

0 comments on commit b801b09

Please sign in to comment.