phlo-clickstack 0.4.0__tar.gz → 0.15.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/PKG-INFO +2 -2
  2. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/pyproject.toml +2 -2
  3. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack/authorization.py +8 -3
  4. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack/cli.py +9 -42
  5. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack/cli_plugin.py +7 -1
  6. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack/observability_backend.py +12 -1
  7. phlo_clickstack-0.15.1/src/phlo_clickstack/plugin.py +23 -0
  8. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack/resource_provider.py +10 -1
  9. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack/service.yaml +1 -4
  10. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack.egg-info/PKG-INFO +2 -2
  11. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack.egg-info/requires.txt +1 -1
  12. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/tests/test_authorization.py +37 -65
  13. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/tests/test_clickstack_cli.py +8 -1
  14. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/tests/test_clickstack_plugin.py +16 -1
  15. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/tests/test_observability_backend.py +6 -1
  16. phlo_clickstack-0.4.0/src/phlo_clickstack/plugin.py +0 -15
  17. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/README.md +0 -0
  18. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/setup.cfg +0 -0
  19. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack/__init__.py +0 -0
  20. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack.egg-info/SOURCES.txt +0 -0
  21. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack.egg-info/dependency_links.txt +0 -0
  22. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack.egg-info/entry_points.txt +0 -0
  23. {phlo_clickstack-0.4.0 → phlo_clickstack-0.15.1}/src/phlo_clickstack.egg-info/top_level.txt +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phlo-clickstack
3
- Version: 0.4.0
3
+ Version: 0.15.1
4
4
  Summary: clickstack service plugin for Phlo
5
5
  Author-email: Phlo Team <team@phlo.dev>
6
6
  License: MIT
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/plain
9
- Requires-Dist: phlo>=0.1.0
9
+ Requires-Dist: phlo<0.16,>=0.15.1
10
10
  Requires-Dist: requests>=2.32.5
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: pytest>=7.0; extra == "dev"
@@ -7,13 +7,13 @@ requires = [
7
7
 
8
8
  [project]
9
9
  dependencies = [
10
- "phlo>=0.1.0",
10
+ "phlo>=0.15.1,<0.16",
11
11
  "requests>=2.32.5",
12
12
  ]
13
13
  description = "clickstack service plugin for Phlo"
14
14
  name = "phlo-clickstack"
15
15
  requires-python = ">=3.11"
16
- version = "0.4.0"
16
+ version = "0.15.1"
17
17
 
18
18
  [[project.authors]]
19
19
  email = "team@phlo.dev"
@@ -1,8 +1,12 @@
1
- """phlo_clickstack CLI authorization table."""
1
+ """Authorization table for the phlo-clickstack CLI surface.
2
+
3
+ Declares which commands mutate state and maps every command onto the resource
4
+ and action names evaluated by the shared CLI surface adapter.
5
+ """
2
6
 
3
7
  from __future__ import annotations
4
8
 
5
- from phlo.cli.authorization import cli_surface_adapter_class
9
+ from phlo.cli.authorization import CliSurfaceAdapter, cli_surface_adapter_class
6
10
 
7
11
  SURFACE_NAME = "phlo-clickstack"
8
12
  FRAMEWORK_TYPE = "cli"
@@ -23,5 +27,6 @@ ClickStackSurfaceAdapter = cli_surface_adapter_class(
23
27
  )
24
28
 
25
29
 
26
- def get_adapter() -> ClickStackSurfaceAdapter:
30
+ def get_adapter() -> CliSurfaceAdapter:
31
+ """Return the shared ClickStackSurfaceAdapter instance for this surface."""
27
32
  return ClickStackSurfaceAdapter.get_instance()
@@ -35,23 +35,10 @@ logger = get_logger(__name__)
35
35
  def _read_query(*, query: str | None, file: Path | None) -> str:
36
36
  """Read and validate SQL query from inline argument or file.
37
37
 
38
- Accepts either an inline SQL query string or a path to a SQL file.
39
- Validates that exactly one input source is provided and that the
40
- content is non-empty.
41
-
42
- Args:
43
- query: Inline SQL query string, or None if reading from file.
44
- file: Path to SQL file, or None if using inline query.
45
-
46
- Returns:
47
- str: The SQL query text to execute.
48
-
49
- Raises:
50
- click.ClickException: If both query and file are provided.
51
- click.ClickException: If file cannot be read.
52
- click.ClickException: If file is empty or contains only whitespace.
53
- click.ClickException: If neither query nor file is provided.
54
-
38
+ Accepts either an inline SQL query string or a path to a SQL file;
39
+ exactly one non-empty source must be provided. Raises ClickException
40
+ when both are given, the file is unreadable/empty, or neither is
41
+ provided.
55
42
  """
56
43
  if query and file:
57
44
  raise exclusive_options_error("an inline query", "--file")
@@ -71,15 +58,8 @@ def _read_query(*, query: str | None, file: Path | None) -> str:
71
58
  def _ensure_phlo_dir() -> Path:
72
59
  """Locate and return the local .phlo directory.
73
60
 
74
- Searches for a .phlo directory in the current working directory.
75
- This directory is required for Docker Compose project configuration.
76
-
77
- Returns:
78
- Path: Path to the .phlo directory.
79
-
80
- Raises:
81
- click.ClickException: If .phlo directory does not exist.
82
-
61
+ Required for Docker Compose project configuration; raises
62
+ ClickException when the directory does not exist.
83
63
  """
84
64
  return ensure_compose_project()
85
65
 
@@ -113,22 +93,9 @@ def clickstack_query(
113
93
 
114
94
  Runs a SQL query via clickhouse-client inside the ClickStack container.
115
95
  Accepts SQL either as a command argument or from a file. Requires Docker
116
- and a running phlo services stack with ClickStack enabled.
117
-
118
- Args:
119
- query: SQL query string to execute.
120
- query_file: Path to file containing SQL query.
121
- output_format: ClickHouse output format (default: TabSeparatedRaw).
122
- timeout_seconds: Query execution timeout in seconds.
123
-
124
- Raises:
125
- click.ClickException: If Docker is unavailable.
126
- click.ClickException: If .phlo directory is missing.
127
- click.ClickException: If both query and file options provided.
128
- click.ClickException: If query file cannot be read.
129
- click.ClickException: If query execution fails.
130
- click.ClickException: If query times out.
131
-
96
+ and a running phlo services stack with ClickStack enabled; raises
97
+ ClickException when Docker is unavailable, the .phlo directory is
98
+ missing, inputs are invalid, execution fails, or the query times out.
132
99
  """
133
100
  sql = _read_query(query=query, file=query_file)
134
101
  if is_mutating_sql(sql):
@@ -1,4 +1,10 @@
1
- """Clickstack CLI plugin registration."""
1
+ """Clickstack CLI plugin registration.
2
+
3
+ Registers the clickstack command group with the plugin system via
4
+ cli_command_plugin_class; owns no logic beyond registration.
5
+
6
+ Loaded through the phlo plugin entry-point mechanism at startup rather than imported directly.
7
+ """
2
8
 
3
9
  from __future__ import annotations
4
10
 
@@ -1,4 +1,10 @@
1
- """ClickStack-backed observability capability provider."""
1
+ """ClickStack-backed observability capability provider.
2
+
3
+ Span queries post SQL to ClickStack's HTTP endpoint and decode its
4
+ newline-delimited JSON reply into TraceSpans. The endpoint resolves from
5
+ CLICKSTACK_QUERY_URL, falling back to the in-container service address or
6
+ localhost depending on environment.
7
+ """
2
8
 
3
9
  from __future__ import annotations
4
10
 
@@ -27,9 +33,14 @@ class ClickStackObservabilityBackend(DefaultObservabilityBackend):
27
33
  """Observability backend with OTEL span queries backed by ClickStack."""
28
34
 
29
35
  def run_trace_spans(self, run_id: str, limit: int = 500) -> list[TraceSpan]:
36
+ """Return trace spans for a single run id, bounded by limit."""
30
37
  return self.trace_spans(TraceSpanFilter(run_id=run_id, limit=limit))
31
38
 
32
39
  def trace_spans(self, filters: TraceSpanFilter) -> list[TraceSpan]:
40
+ """Post the span query to ClickStack and decode newline-delimited JSON into TraceSpans.
41
+
42
+ Raises requests.HTTPError when the query request fails.
43
+ """
33
44
  query_url = self._resolve_clickstack_query_url()
34
45
  query = _build_trace_spans_query(filters)
35
46
  response = requests.post(
@@ -0,0 +1,23 @@
1
+ """Clickstack service plugin registration.
2
+
3
+ Declares the ClickStack observability backend (logs, metrics, traces)
4
+ as a service plugin. The class object is created at import time via
5
+ service_plugin_class so plugin discovery can pick it up without
6
+ instantiation.
7
+ Loaded through the phlo plugin entry-point mechanism at startup rather than imported directly.
8
+ Registers the clickstack observability backend service through phlo.plugins.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from phlo.plugins import service_plugin_class
14
+
15
+
16
+ ClickStackServicePlugin = service_plugin_class(
17
+ "ClickStackServicePlugin",
18
+ name="clickstack",
19
+ version="0.1.0",
20
+ description="ClickStack all-in-one observability backend",
21
+ author="Phlo Team",
22
+ tags=["observability", "logs", "metrics", "traces"],
23
+ )
@@ -1,4 +1,10 @@
1
- """Resource provider for ClickStack observability capabilities."""
1
+ """Resource provider for ClickStack observability capabilities.
2
+
3
+ ClickStackResourceProvider registers under the "clickstack" name and reports
4
+ ClickStack as an observability backend; it declares no standalone resources.
5
+ Loaded through the phlo plugin entry-point mechanism at startup rather than
6
+ imported directly; declares ClickStack as an observability backend via phlo.plugins.
7
+ """
2
8
 
3
9
  from __future__ import annotations
4
10
 
@@ -12,6 +18,7 @@ class ClickStackResourceProvider(ResourceProviderPlugin):
12
18
 
13
19
  @property
14
20
  def metadata(self) -> PluginMetadata:
21
+ """Return the ClickStack provider metadata."""
15
22
  return PluginMetadata(
16
23
  name="clickstack",
17
24
  version="0.1.0",
@@ -20,7 +27,9 @@ class ClickStackResourceProvider(ResourceProviderPlugin):
20
27
  )
21
28
 
22
29
  def get_resources(self) -> list:
30
+ """Report no standalone resources."""
23
31
  return []
24
32
 
25
33
  def get_observability_backends(self):
34
+ """Return the ClickStack observability backend spec."""
26
35
  return [build_clickstack_observability_spec()]
@@ -4,7 +4,7 @@ category: observability
4
4
  default: false
5
5
  profile: observability
6
6
 
7
- image: ${CLICKSTACK_IMAGE:-docker.hyperdx.io/hyperdx/hyperdx-all-in-one}
7
+ image: docker.io/hyperdx/hyperdx-all-in-one:2.31.0@sha256:b01cc48cb5aaf30d630865a88217c826ab86fb9828374201f6cd7c539d5beed1
8
8
 
9
9
  compose:
10
10
  restart: unless-stopped
@@ -32,9 +32,6 @@ compose:
32
32
  retries: 10
33
33
 
34
34
  env_vars:
35
- CLICKSTACK_IMAGE:
36
- default: "docker.hyperdx.io/hyperdx/hyperdx-all-in-one"
37
- description: Official ClickStack all-in-one image reference
38
35
  CLICKSTACK_PORT:
39
36
  default: 18080
40
37
  description: ClickStack UI port
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phlo-clickstack
3
- Version: 0.4.0
3
+ Version: 0.15.1
4
4
  Summary: clickstack service plugin for Phlo
5
5
  Author-email: Phlo Team <team@phlo.dev>
6
6
  License: MIT
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/plain
9
- Requires-Dist: phlo>=0.1.0
9
+ Requires-Dist: phlo<0.16,>=0.15.1
10
10
  Requires-Dist: requests>=2.32.5
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: pytest>=7.0; extra == "dev"
@@ -1,4 +1,4 @@
1
- phlo>=0.1.0
1
+ phlo<0.16,>=0.15.1
2
2
  requests>=2.32.5
3
3
 
4
4
  [dev]
@@ -1,4 +1,10 @@
1
- """Tests for ClickStack authorization module."""
1
+ """Tests for ClickStack authorization module.
2
+
3
+ Package-specific command-table coverage plus the shared surface-adapter
4
+ contract from ``phlo_testing.authorization_surface``: reads skip enforcement,
5
+ unknown commands deny closed, mutation commands honor policy allow/deny, and
6
+ the shared ``CliPrincipalResolver`` environment fallbacks hold.
7
+ """
2
8
 
3
9
  from __future__ import annotations
4
10
 
@@ -8,6 +14,13 @@ from unittest.mock import patch
8
14
  import pytest
9
15
 
10
16
  from phlo.cli.authorization import CliPrincipalResolver
17
+ from phlo_testing.authorization_surface import (
18
+ assert_mutation_enforcement_allows_and_denies,
19
+ assert_read_commands_allow_without_enforcement,
20
+ assert_unknown_command_denied,
21
+ reset_surface_adapter_singleton,
22
+ run_cli_principal_resolver_contract,
23
+ )
11
24
  from phlo_clickstack.authorization import (
12
25
  COMMAND_ACTION_MAP,
13
26
  COMMAND_RESOURCE_MAP,
@@ -21,32 +34,7 @@ pytestmark = pytest.mark.core_regression
21
34
 
22
35
 
23
36
  class TestClickStackPrincipalResolver:
24
- """Tests for the shared CLI principal resolver."""
25
-
26
- def test_resolve_service_account(self):
27
- """PHLO_SERVICE_ACCOUNT creates service principal."""
28
- env = {"PHLO_SERVICE_ACCOUNT": "ci-bot@phlo.svc"}
29
- with patch.dict(os.environ, env, clear=True):
30
- resolver = CliPrincipalResolver()
31
- principal = resolver.resolve()
32
- assert principal.subject == "ci-bot@phlo.svc"
33
- assert principal.principal_type == "service"
34
- assert "operators" in principal.groups
35
-
36
- def test_resolve_human_principal(self):
37
- """PHLO_AUTH_SUBJECT creates human principal."""
38
- env = {
39
- "PHLO_AUTH_SUBJECT": "user@example.com",
40
- "PHLO_AUTH_TYPE": "user",
41
- "PHLO_AUTH_GROUPS": "admin,developers",
42
- }
43
- with patch.dict(os.environ, env, clear=True):
44
- resolver = CliPrincipalResolver()
45
- principal = resolver.resolve()
46
- assert principal.subject == "user@example.com"
47
- assert principal.principal_type == "user"
48
- assert "admin" in principal.groups
49
- assert "developers" in principal.groups
37
+ """Extra principal-resolver edge case beyond the shared contract."""
50
38
 
51
39
  def test_resolve_human_principal_empty_groups(self):
52
40
  """PHLO_AUTH_SUBJECT with no groups."""
@@ -60,34 +48,19 @@ class TestClickStackPrincipalResolver:
60
48
  assert principal.subject == "user@example.com"
61
49
  assert principal.groups == ()
62
50
 
63
- def test_resolve_dev_mode_fallback(self):
64
- """PHLO_DEV_MODE creates admin fallback with warning."""
65
- env = {"PHLO_DEV_MODE": "1"}
66
- with patch.dict(os.environ, env, clear=True):
67
- resolver = CliPrincipalResolver()
68
- principal = resolver.resolve()
69
- assert principal.subject == "local:root"
70
- assert principal.principal_type == "user"
71
- assert "admin" in principal.groups
72
51
 
73
- def test_resolve_anonymous_default(self):
74
- """No env vars creates anonymous principal."""
75
- env = {}
76
- with patch.dict(os.environ, env, clear=True):
77
- resolver = CliPrincipalResolver()
78
- principal = resolver.resolve()
79
- assert principal.subject == "anonymous"
80
- assert principal.principal_type == "user"
52
+ def test_cli_principal_resolver_contract():
53
+ """The shared CLI principal resolver environment fallbacks hold."""
54
+ run_cli_principal_resolver_contract()
81
55
 
82
56
 
83
57
  class TestClickStackSurfaceAdapter:
84
58
  """Tests for ClickStackSurfaceAdapter."""
85
59
 
86
- def setup_method(self):
87
- ClickStackSurfaceAdapter._instance = None
88
-
89
- def teardown_method(self):
90
- ClickStackSurfaceAdapter._instance = None
60
+ @pytest.fixture(autouse=True)
61
+ def reset_singleton(self):
62
+ with reset_surface_adapter_singleton(ClickStackSurfaceAdapter):
63
+ yield
91
64
 
92
65
  def test_surface_name(self):
93
66
  adapter = ClickStackSurfaceAdapter()
@@ -131,25 +104,24 @@ class TestClickStackSurfaceAdapter:
131
104
  class TestEnforcement:
132
105
  """Tests for ClickStack mutation enforcement."""
133
106
 
134
- def setup_method(self):
135
- ClickStackSurfaceAdapter._instance = None
107
+ @pytest.fixture(autouse=True)
108
+ def reset_singleton(self):
109
+ with reset_surface_adapter_singleton(ClickStackSurfaceAdapter):
110
+ yield
136
111
 
137
- def teardown_method(self):
138
- ClickStackSurfaceAdapter._instance = None
112
+ def test_read_commands_allow_without_enforcement(self):
113
+ """Read commands allow without touching the enforcement path."""
114
+ assert_read_commands_allow_without_enforcement(ClickStackSurfaceAdapter())
139
115
 
140
- def test_check_read_command_allowed(self):
141
- """Read commands are always allowed."""
142
- adapter = ClickStackSurfaceAdapter()
143
- for cmd in READ_COMMANDS:
144
- result = adapter.check_command_authorization(cmd)
145
- assert result.allowed, f"Read command {cmd} should be allowed"
116
+ def test_unknown_command_denied_closed(self):
117
+ """Unknown commands are denied closed."""
118
+ assert_unknown_command_denied(ClickStackSurfaceAdapter(), "clickstack.unknown")
146
119
 
147
- def test_check_unknown_command_denied(self):
148
- """Unknown commands are denied."""
149
- adapter = ClickStackSurfaceAdapter()
150
- result = adapter.check_command_authorization("unknown.command")
151
- assert not result.allowed
152
- assert result.reason_code == "unknown_command"
120
+ def test_clickstack_query_mutation_honors_policy_decision(self):
121
+ """clickstack.query flows a policy allow through and blocks a policy deny."""
122
+ assert_mutation_enforcement_allows_and_denies(
123
+ ClickStackSurfaceAdapter(), "clickstack.query"
124
+ )
153
125
 
154
126
 
155
127
  class TestMutationCommandLists:
@@ -1,4 +1,11 @@
1
- """Tests for ClickStack CLI commands."""
1
+ """Tests for ClickStack CLI commands.
2
+
3
+ Drives the query command through CliRunner with mocked subprocess calls to
4
+ verify SQL executes inside the ClickStack container via the project's
5
+ configured compose backend, that mutating statements pass through surface
6
+ mutation authorization, and that missing input or timeouts surface as CLI
7
+ errors.
8
+ """
2
9
 
3
10
  from __future__ import annotations
4
11
 
@@ -1,4 +1,8 @@
1
- """Tests for ClickStack service plugin."""
1
+ """Tests for the ClickStack service plugin.
2
+
3
+ Covers service definition metadata, pinned upstream images, and CLI rejection
4
+ of commands without an initialized .phlo directory.
5
+ """
2
6
 
3
7
  from click.testing import CliRunner
4
8
 
@@ -21,6 +25,17 @@ def test_clickstack_service_definition() -> None:
21
25
  assert "./volumes/clickstack:/var/lib/clickhouse" not in defn["compose"]["volumes"]
22
26
 
23
27
 
28
+ def test_clickstack_uses_pinned_upstream_image() -> None:
29
+ definition = ClickStackServicePlugin().service_definition
30
+
31
+ assert definition["image"] == (
32
+ "docker.io/hyperdx/hyperdx-all-in-one:2.31.0@"
33
+ "sha256:b01cc48cb5aaf30d630865a88217c826ab86fb9828374201f6cd7c539d5beed1"
34
+ )
35
+ assert "build" not in definition
36
+ assert "CLICKSTACK_IMAGE" not in definition["env_vars"]
37
+
38
+
24
39
  def test_clickstack_plugin_metadata() -> None:
25
40
  """Validate ClickStack plugin metadata."""
26
41
  plugin = ClickStackServicePlugin()
@@ -1,4 +1,9 @@
1
- """Tests for ClickStack observability backend capability."""
1
+ """Tests for ClickStack observability backend capability.
2
+
3
+ Covers spec defaults, host-port versus in-container query URL selection,
4
+ trace span queries issued to the ClickStack HTTP API, and filter-aware query
5
+ construction.
6
+ """
2
7
 
3
8
  from __future__ import annotations
4
9
 
@@ -1,15 +0,0 @@
1
- """Clickstack service plugin registration."""
2
-
3
- from __future__ import annotations
4
-
5
- from phlo.plugins import service_plugin_class
6
-
7
-
8
- ClickStackServicePlugin = service_plugin_class(
9
- "ClickStackServicePlugin",
10
- name="clickstack",
11
- version="0.1.0",
12
- description="ClickStack all-in-one observability backend",
13
- author="Phlo Team",
14
- tags=["observability", "logs", "metrics", "traces"],
15
- )