Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@ build/
# Editor/IDE
.idea/
.vscode/

# Git worktrees
.worktrees/
.claude
4 changes: 4 additions & 0 deletions charms/garm/charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ requires:
debug-ssh:
interface: debug-ssh
limit: 1

actions:
get-credentials:
description: Show the GARM admin credentials.
13 changes: 13 additions & 0 deletions charms/garm/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ def __init__(self, *args: typing.Any) -> None:
self.on[GARM_CONFIGURATOR_RELATION_NAME].relation_broken,
self._reconcile,
)
self.framework.observe(self.on.get_credentials_action, self._on_get_credentials_action)
self.framework.observe(
self.on[DEBUG_SSH_INTEGRATION_NAME].relation_joined,
self._reconcile,
Expand All @@ -383,6 +384,18 @@ def _reconcile(self, _: ops.EventBase) -> None:
"""Reconcile charm state."""
self.restart()

def _on_get_credentials_action(self, event: ops.ActionEvent) -> None:
"""Return the GARM admin credentials to the operator.

Args:
event: The action event.
"""
credentials = self._get_admin_credentials()
if credentials is None:
event.fail("GARM admin credentials are not yet available")
return
event.set_results(credentials)

@property
def _workload_config(self) -> WorkloadConfig:
"""Pin GARM to a fixed port and disable the default metrics scrape job.
Expand Down
42 changes: 42 additions & 0 deletions charms/garm/tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,48 @@ def test_maybe_first_run_skips_on_missing_credential_key():
mock_client_cls.assert_not_called()


def test_get_credentials_action_returns_credentials_when_available():
"""
arrange: Admin credentials secret exists and contains valid content.
act: Call _on_get_credentials_action().
assert: event.set_results is called once with the credentials dict and
event.fail is not called.
"""
Comment thread
Thanhphan1147 marked this conversation as resolved.
charm = MagicMock()
event = MagicMock()
credentials = {
"username": "admin",
"password": "s3cr3t",
"email": "admin@garm.local",
"full-name": "GARM Admin",
}
charm._get_admin_credentials.return_value = credentials

GarmCharm._on_get_credentials_action(charm, event)

event.set_results.assert_called_once_with(credentials)
event.fail.assert_not_called()
Comment thread
florentianayuwono marked this conversation as resolved.


def test_get_credentials_action_fails_when_credentials_unavailable():
"""
arrange: Admin credentials secret does not exist yet.
act: Call _on_get_credentials_action().
assert: event.fail is called with a message containing "not yet available" and
event.set_results is not called.
"""
charm = MagicMock()
event = MagicMock()
charm._get_admin_credentials.return_value = None

GarmCharm._on_get_credentials_action(charm, event)

event.fail.assert_called_once()
fail_message = event.fail.call_args[0][0]
assert "not yet available" in fail_message
event.set_results.assert_not_called()
Comment thread
Thanhphan1147 marked this conversation as resolved.


def test_proxy_environment_happy_path():
"""
arrange: All three JUJU_CHARM_* proxy vars are set in the environment.
Expand Down
Loading
Loading