Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Bug Report
description: File a bug report
labels: ["bug"]
body:
- type: markdown
attributes:
value: >
Thanks for taking the time to fill out this bug report! Before submitting your issue, please make
sure you are using the latest version of the charm. If not, please switch to this image prior to
posting your report to make sure it's not already solved.
- type: textarea
id: bug-description
attributes:
label: Bug Description
description: >
If applicable, add screenshots to help explain the problem you are facing.
validations:
required: true
- type: dropdown
id: impact
attributes:
label: Impact
description: How severe is the business impact of this bug?
options:
- Low (minor issue or cosmetic problem)
- Medium (functionality degraded, workaround exists)
- High (major functionality broken, no workaround)
- Critical (system down, data loss, affecting deployment in production)
validations:
required: true
- type: textarea
id: impact-rationale
attributes:
label: Impact Rationale
description: >
If impact is high or critical, please provide the rationale behind your assessment, impacted
project reference and any relevant project deadline dates which will be affected by this bug.
- type: textarea
id: reproduction
attributes:
label: To Reproduce
description: >
Please provide a step-by-step instruction of how to reproduce the behavior.
placeholder: |
1. `juju deploy ...`
2. `juju relate ...`
3. `juju status --relations`
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: >
We need to know a bit more about the context in which you run the charm.
- Are you running Juju locally, on lxd, in multipass or on some other platform?
- What track and channel you deployed the charm from (i.e. `latest/edge` or similar).
- Version of any applicable components, like the juju snap, the model controller, lxd, microk8s, and/or multipass.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: >
Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
Fetch the logs using `juju debug-log --replay` and `kubectl logs ...`. Additional details available in the juju docs
at https://documentation.ubuntu.com/juju/3.6/howto/manage-logs/#manage-logs
render: shell
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
36 changes: 36 additions & 0 deletions .github/ISSUE_TEMPLATE/enhancement_proposal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Enhancement Proposal
description: File an enhancement proposal
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: >
Thanks for taking the time to fill out this enhancement proposal! Before submitting your issue, please make
sure there isn't already a prior issue concerning this. If there is, please join that discussion instead.
- type: textarea
id: enhancement-proposal
attributes:
label: Enhancement Proposal
description: >
Describe the enhancement you would like to see in as much detail as needed.
validations:
required: true
- type: dropdown
id: impact
attributes:
label: Impact
description: What is the impact of this feature?
options:
- Low (The feature is nice to have)
- Medium (The feature may be helpful in the future)
- High (The feature has short-term technical value)
- Critical (The feature has big short-term business value)
validations:
required: true
- type: textarea
id: impact-rationale
attributes:
label: Impact Rationale
description: >
If impact is high or critical, please provide the rationale behind your assessment with as
much context as possible.
1 change: 1 addition & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ CVE-2025-58189
CVE-2025-61723
CVE-2025-61724
CVE-2025-61725
CVE-2025-68121
1 change: 1 addition & 0 deletions src/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""tmate-ssh-server charm actions."""

import logging

import ops
Expand Down
1 change: 1 addition & 0 deletions src/ssh_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""Observer module for ssh-debug integration."""

import logging
import typing

Expand Down
1 change: 1 addition & 0 deletions src/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""tmate-ssh-server states."""

import dataclasses
import ipaddress
import logging
Expand Down
6 changes: 2 additions & 4 deletions src/tmate.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,14 +331,12 @@ def generate_tmate_conf(host: str) -> str:
except (IncompleteInitError, KeyInstallError) as exc:
raise FingerprintError("Error generating fingerprints.") from exc

return textwrap.dedent(
f"""
return textwrap.dedent(f"""
set -g tmate-server-host {host}
set -g tmate-server-port {PORT}
set -g tmate-server-rsa-fingerprint {fingerprints.rsa}
set -g tmate-server-ed25519-fingerprint {fingerprints.ed25519}
"""
)
""")


def remove_stopped_containers() -> None:
Expand Down
13 changes: 7 additions & 6 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""Fixtures for tmate-ssh-server charm integration tests."""

import logging
import secrets

Expand Down Expand Up @@ -126,7 +127,7 @@ async def wait_machine():
await wait_for(wait_machine, timeout=60 * 5)

logger.info("Running update.")
(retcode, _, stderr) = await ops_test.juju("ssh", str(machine.entity_id), "sudo apt update -y")
retcode, _, stderr = await ops_test.juju("ssh", str(machine.entity_id), "sudo apt update -y")
assert retcode == 0, f"Failed to run apt update, {stderr}"
return machine

Expand All @@ -135,7 +136,7 @@ async def wait_machine():
async def ssh_machine_fixture(ops_test: OpsTest, machine: Machine):
"""A machine to test tmate ssh connection."""
logger.info("Installing tmate.")
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh",
str(machine.entity_id),
"DEBIAN_FRONTEND=noninteractive sudo apt-get install -y tmate",
Expand All @@ -148,7 +149,7 @@ async def ssh_machine_fixture(ops_test: OpsTest, machine: Machine):
async def proxy_machine_fixture(ops_test: OpsTest, machine: Machine):
"""A machine to host squid proxy."""
logger.info("Installing squid.")
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh",
str(machine.entity_id),
"DEBIAN_FRONTEND=noninteractive sudo apt-get install -y squid",
Expand All @@ -171,17 +172,17 @@ async def proxy_machine_fixture(ops_test: OpsTest, machine: Machine):
http_access deny all"""
temp_config_file_path = Path(f"./{secrets.token_hex(8)}")
temp_config_file_path.write_text(squid_config, encoding="utf-8")
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"scp", temp_config_file_path.name, f"{machine.entity_id}:~/squid.conf"
)
assert retcode == 0, f"Failed to scp squid conf file {stdout} {stderr}"
temp_config_file_path.unlink()
# cannot scp directly to /etc due to permission error
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh", str(machine.entity_id), "sudo mv ~/squid.conf /etc/squid/squid.conf"
)
assert retcode == 0, f"Failed to move squid conf file {stdout} {stderr}"
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh", str(machine.entity_id), "sudo systemctl restart squid.service"
)
assert retcode == 0, f"Failed to restart squid service, {stdout} {stderr}"
Expand Down
21 changes: 10 additions & 11 deletions tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""Integration tests for tmate-ssh-server charm."""

import logging
import secrets
from pathlib import Path
Expand Down Expand Up @@ -30,7 +31,7 @@ async def test_ssh_connection(
"""
temp_config_file_path = Path(f"./{secrets.token_hex(8)}")
temp_config_file_path.write_text(tmate_config, encoding="utf-8")
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"scp", temp_config_file_path.name, f"{tmate_machine.entity_id}:~/.tmate.conf"
)
assert retcode == 0, f"Failed to scp tmate conf file {stdout} {stderr}"
Expand All @@ -41,20 +42,20 @@ async def test_ssh_connection(
)

logger.info("Starting tmate session")
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh",
tmate_machine.entity_id,
"--",
"tmate -a ~/.ssh/authorized_keys -S /tmp/tmate.sock new-session -d",
)
assert retcode == 0, f"Error running ssh display command, {stdout}, {stderr}"
logger.info("New session created %s %s %s", retcode, stdout, stderr)
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh", tmate_machine.entity_id, "--", "tmate -S /tmp/tmate.sock wait tmate-ready"
)
assert retcode == 0, f"Error running ssh display command, {stdout}, {stderr}"
logger.info("Tmate ready %s %s %s", retcode, stdout, stderr)
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh", tmate_machine.entity_id, "--", "tmate -S /tmp/tmate.sock display -p '#{tmate_ssh}'"
)
assert retcode == 0, f"Error running ssh display command, {stdout}, {stderr}"
Expand Down Expand Up @@ -88,9 +89,7 @@ async def test_ssh_connection(
session.send("echo test > ~/test.txt && cat ~/test.txt\n") # type: ignore
stdout = session.recv(10000)
logger.info(SHELL_STDOUT_LOG_STR, str(stdout))
(retcode, stdout, stderr) = await ops_test.juju(
"ssh", tmate_machine.entity_id, "cat ~/test.txt"
)
retcode, stdout, stderr = await ops_test.juju("ssh", tmate_machine.entity_id, "cat ~/test.txt")

assert retcode == 0, f"Error running ssh command, {stdout}, {stderr}"
assert "test" in stdout, f"Failed to write with ssh command, {stdout}"
Expand All @@ -106,22 +105,22 @@ async def test_restart_of_inactive_service(
assert: the service has been restarted successfully.
"""
await ops_test.juju("ssh", unit.entity_id, "--", "docker stop $(docker ps -q)")
(retcode, stdout, stderr) = await ops_test.juju("ssh", unit.entity_id, "--", "docker ps")
retcode, stdout, stderr = await ops_test.juju("ssh", unit.entity_id, "--", "docker ps")
assert retcode == 0, f"Error running docker ps command, {stdout}, {stderr}"
assert "tmate-ssh-server" not in stdout, "tmate-ssh-server service is still running"
(retcode, _, _) = await ops_test.juju(
retcode, _, _ = await ops_test.juju(
"ssh", unit.entity_id, "--", "systemctl --quiet is-active tmate-ssh-server"
)
assert retcode != 0, "tmate-ssh-server service is still running"

async with ops_test.fast_forward():
await unit.model.wait_for_idle(apps=[tmate_ssh_server.name], status=ActiveStatus.name)

(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh", unit.entity_id, "--", "systemctl --quiet is-active tmate-ssh-server"
)
assert retcode == 0, f"tmate-ssh-server service is not running, {stdout}, {stderr}"

(retcode, stdout, stderr) = await ops_test.juju("ssh", unit.entity_id, "--", "docker ps")
retcode, stdout, stderr = await ops_test.juju("ssh", unit.entity_id, "--", "docker ps")
assert retcode == 0, f"Error running docker ps command, {stdout}, {stderr}"
assert "tmate-ssh-server" in stdout, "tmate-ssh-server service is not running"
3 changes: 2 additions & 1 deletion tests/integration/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

"""Integration tests for tmate-ssh-server charm."""

import logging

from juju.machine import Machine
Expand Down Expand Up @@ -47,7 +48,7 @@ async def wait_for_access_log() -> bool:
Returns:
Whether ghcr.io access was found in proxy log.
"""
(retcode, stdout, stderr) = await ops_test.juju(
retcode, stdout, stderr = await ops_test.juju(
"ssh", proxy_machine.entity_id, "sudo cat /var/log/squid/access.log"
)
assert retcode == 0, f"Failed read squid access log, {stdout} {stderr}"
Expand Down
13 changes: 3 additions & 10 deletions tests/unit/test_tmate.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ def test_install_dependencies_proxy_config(monkeypatch: pytest.MonkeyPatch):
"https-proxy": "{proxy_config.https_proxy}",
"no-proxy": "{proxy_config.no_proxy}"
}}
}}""" == tmp_file_path.read_text(
encoding="utf-8"
)
}}""" == tmp_file_path.read_text(encoding="utf-8")


@pytest.mark.parametrize(
Expand Down Expand Up @@ -497,17 +495,12 @@ def test_generate_tmate_conf(fingerprints: tmate.Fingerprints):
"""
host = "test_host_value"

assert (
textwrap.dedent(
f"""
assert textwrap.dedent(f"""
set -g tmate-server-host {host}
set -g tmate-server-port {tmate.PORT}
set -g tmate-server-rsa-fingerprint {fingerprints.rsa}
set -g tmate-server-ed25519-fingerprint {fingerprints.ed25519}
"""
)
== tmate.generate_tmate_conf(host)
)
""") == tmate.generate_tmate_conf(host)


def test_remove_stopped_containers_error(monkeypatch: pytest.MonkeyPatch):
Expand Down
Loading