xray-cli 0.1.0__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 (41) hide show
  1. xray_cli-0.1.0/LICENSE +21 -0
  2. xray_cli-0.1.0/PKG-INFO +260 -0
  3. xray_cli-0.1.0/README.md +235 -0
  4. xray_cli-0.1.0/pyproject.toml +37 -0
  5. xray_cli-0.1.0/setup.cfg +4 -0
  6. xray_cli-0.1.0/src/xray_cli/__init__.py +3 -0
  7. xray_cli-0.1.0/src/xray_cli/auth.py +88 -0
  8. xray_cli-0.1.0/src/xray_cli/cli.py +153 -0
  9. xray_cli-0.1.0/src/xray_cli/commands/__init__.py +0 -0
  10. xray_cli-0.1.0/src/xray_cli/commands/_shared.py +23 -0
  11. xray_cli-0.1.0/src/xray_cli/commands/execution.py +97 -0
  12. xray_cli-0.1.0/src/xray_cli/commands/folder.py +39 -0
  13. xray_cli-0.1.0/src/xray_cli/commands/precondition.py +30 -0
  14. xray_cli-0.1.0/src/xray_cli/commands/results.py +82 -0
  15. xray_cli-0.1.0/src/xray_cli/commands/run.py +111 -0
  16. xray_cli-0.1.0/src/xray_cli/commands/test.py +80 -0
  17. xray_cli-0.1.0/src/xray_cli/commands/test_plan.py +30 -0
  18. xray_cli-0.1.0/src/xray_cli/commands/test_set.py +30 -0
  19. xray_cli-0.1.0/src/xray_cli/config.py +177 -0
  20. xray_cli-0.1.0/src/xray_cli/errors.py +78 -0
  21. xray_cli-0.1.0/src/xray_cli/formatter.py +273 -0
  22. xray_cli-0.1.0/src/xray_cli/graphql_client.py +97 -0
  23. xray_cli-0.1.0/src/xray_cli/log.py +37 -0
  24. xray_cli-0.1.0/src/xray_cli/resolver.py +87 -0
  25. xray_cli-0.1.0/src/xray_cli/rest_client.py +109 -0
  26. xray_cli-0.1.0/src/xray_cli/service.py +619 -0
  27. xray_cli-0.1.0/src/xray_cli.egg-info/PKG-INFO +260 -0
  28. xray_cli-0.1.0/src/xray_cli.egg-info/SOURCES.txt +39 -0
  29. xray_cli-0.1.0/src/xray_cli.egg-info/dependency_links.txt +1 -0
  30. xray_cli-0.1.0/src/xray_cli.egg-info/entry_points.txt +2 -0
  31. xray_cli-0.1.0/src/xray_cli.egg-info/top_level.txt +1 -0
  32. xray_cli-0.1.0/tests/test_auth.py +127 -0
  33. xray_cli-0.1.0/tests/test_cli.py +122 -0
  34. xray_cli-0.1.0/tests/test_config.py +252 -0
  35. xray_cli-0.1.0/tests/test_errors.py +115 -0
  36. xray_cli-0.1.0/tests/test_formatter.py +90 -0
  37. xray_cli-0.1.0/tests/test_graphql_client.py +173 -0
  38. xray_cli-0.1.0/tests/test_integration.py +107 -0
  39. xray_cli-0.1.0/tests/test_resolver.py +82 -0
  40. xray_cli-0.1.0/tests/test_rest_client.py +166 -0
  41. xray_cli-0.1.0/tests/test_service.py +435 -0
xray_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,260 @@
1
+ Metadata-Version: 2.1
2
+ Name: xray-cli
3
+ Version: 0.1.0
4
+ Summary: Command-line interface for Xray Cloud test management
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/arieluchka/xray-cli
7
+ Project-URL: Repository, https://github.com/arieluchka/xray-cli
8
+ Project-URL: Issues, https://github.com/arieluchka/xray-cli/issues
9
+ Keywords: xray,jira,testing,test-management,cli
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+
26
+ # xray-cli
27
+
28
+ Command-line interface for [Xray Cloud](https://www.getxray.app/) test management.
29
+
30
+ A zero-dependency Python CLI that talks to the Xray Cloud API (GraphQL + REST) to manage test executions, tests, runs, results, preconditions, test sets, test plans, and folders — all from your terminal.
31
+
32
+ ## Installation
33
+
34
+ Requires Python 3.8+.
35
+
36
+ ```bash
37
+ pip install .
38
+ ```
39
+
40
+ This installs the `xray` command.
41
+
42
+ ## Quick Start
43
+
44
+ 1. **Set your Xray API credentials** (get them from Xray Cloud → API Keys):
45
+
46
+ ```bash
47
+ export XRAY_CLIENT_ID="your-client-id"
48
+ export XRAY_CLIENT_SECRET="your-client-secret"
49
+ ```
50
+
51
+ Or create a config file (see [Configuration](#configuration) below).
52
+
53
+ 2. **Run a command:**
54
+
55
+ ```bash
56
+ xray execution list --limit 5
57
+ xray test get XSP-123
58
+ xray run list --execution XSP-456 --failed-only
59
+ ```
60
+
61
+ ## Global Flags
62
+
63
+ | Flag | Short | Description |
64
+ |------|-------|-------------|
65
+ | `--version` | | Print version and exit |
66
+ | `--config PATH` | | Path to config file (default: auto-discover `.xray-cli.ini`) |
67
+ | `--json` | | Output in JSON format instead of human-readable text |
68
+ | `--verbose` | `-v` | Enable verbose/debug output |
69
+ | `--quiet` | `-q` | Suppress all non-error output |
70
+ | `--timeout SECONDS` | | Request timeout in seconds (default: from config or 30) |
71
+
72
+ > **Note:** Global flags can be placed anywhere in the command, e.g. `xray --json execution list` or `xray execution list --json`.
73
+
74
+ ## Commands
75
+
76
+ ### `xray execution` — Test Execution Operations
77
+
78
+ **List executions:**
79
+
80
+ ```bash
81
+ xray execution list [--jql JQL] [--limit N] [--start N]
82
+ ```
83
+
84
+ **Create an execution:**
85
+
86
+ ```bash
87
+ xray execution create --project XSP --summary "Regression run" [--test XSP-100 --test XSP-101]
88
+ ```
89
+
90
+ | Argument | Required | Description |
91
+ |----------|----------|-------------|
92
+ | `--project` | Yes | Jira project key |
93
+ | `--summary` | Yes | Summary for the new execution |
94
+ | `--test` | No | Test issue key to include (repeatable) |
95
+
96
+ ### `xray test` — Test Operations
97
+
98
+ **Get a single test:**
99
+
100
+ ```bash
101
+ xray test get XSP-123
102
+ ```
103
+
104
+ **List tests:**
105
+
106
+ ```bash
107
+ xray test list [--jql JQL] [--limit N] [--start N]
108
+ ```
109
+
110
+ ### `xray run` — Test Run Operations
111
+
112
+ **List runs:**
113
+
114
+ ```bash
115
+ xray run list [--test XSP-123] [--execution XSP-456] [--status PASS] [--failed-only] [--limit N] [--start N]
116
+ ```
117
+
118
+ | Argument | Description |
119
+ |----------|-------------|
120
+ | `--test` | Filter by test issue key (repeatable) |
121
+ | `--execution` | Filter by execution issue key (repeatable) |
122
+ | `--status` | Filter by run status name |
123
+ | `--failed-only` | Show only failed/aborted runs |
124
+
125
+ **Update run status:**
126
+
127
+ ```bash
128
+ xray run update-status <run_id> <status>
129
+ ```
130
+
131
+ Status values: `PASS`, `FAIL`, `TODO`, `EXECUTING`, `ABORTED`, etc.
132
+
133
+ **Add evidence to a run:**
134
+
135
+ ```bash
136
+ xray run add-evidence <run_id> path/to/screenshot.png
137
+ ```
138
+
139
+ ### `xray results` — Import Test Results
140
+
141
+ **Import Xray JSON results:**
142
+
143
+ ```bash
144
+ xray results import xray-json results.json [--project XSP] [--execution XSP-456]
145
+ ```
146
+
147
+ **Import JUnit XML results:**
148
+
149
+ ```bash
150
+ xray results import junit results.xml [--project XSP] [--execution XSP-456]
151
+ ```
152
+
153
+ ### `xray precondition` — Precondition Operations
154
+
155
+ ```bash
156
+ xray precondition list [--jql JQL] [--limit N] [--start N]
157
+ ```
158
+
159
+ ### `xray test-set` — Test Set Operations
160
+
161
+ ```bash
162
+ xray test-set list [--jql JQL] [--limit N] [--start N]
163
+ ```
164
+
165
+ ### `xray test-plan` — Test Plan Operations
166
+
167
+ ```bash
168
+ xray test-plan list [--jql JQL] [--limit N] [--start N]
169
+ ```
170
+
171
+ ### `xray folder` — Folder Operations
172
+
173
+ ```bash
174
+ xray folder list --project XSP [--path /MyFolder] [--limit N] [--start N]
175
+ ```
176
+
177
+ ## Configuration
178
+
179
+ ### Config File
180
+
181
+ Create `.xray-cli.ini` in your project directory or home directory:
182
+
183
+ ```ini
184
+ [xray]
185
+ client_id = YOUR_CLIENT_ID
186
+ client_secret = YOUR_CLIENT_SECRET
187
+
188
+ [defaults]
189
+ project_key = XSP
190
+ output = text
191
+ timeout_seconds = 30
192
+ page_limit = 50
193
+ ```
194
+
195
+ See `xray-cli.ini.example` for a full annotated template.
196
+
197
+ You can also specify a config file explicitly:
198
+
199
+ ```bash
200
+ xray --config /path/to/config.ini execution list
201
+ ```
202
+
203
+ ### Environment Variables
204
+
205
+ All settings can be set via environment variables:
206
+
207
+ | Variable | Config Key |
208
+ |----------|------------|
209
+ | `XRAY_CLIENT_ID` | `client_id` |
210
+ | `XRAY_CLIENT_SECRET` | `client_secret` |
211
+ | `XRAY_AUTH_URL` | `auth_url` |
212
+ | `XRAY_GRAPHQL_URL` | `graphql_url` |
213
+ | `XRAY_IMPORT_BASE_URL` | `import_base_url` |
214
+ | `XRAY_PROJECT_KEY` | `project_key` |
215
+ | `XRAY_OUTPUT` | `output` |
216
+ | `XRAY_TIMEOUT` | `timeout_seconds` |
217
+ | `XRAY_PAGE_LIMIT` | `page_limit` |
218
+
219
+ ### Precedence
220
+
221
+ Settings are resolved in this order (highest wins):
222
+
223
+ 1. CLI flags (`--json`, `--timeout`)
224
+ 2. Environment variables (`XRAY_*`)
225
+ 3. Config file (`.xray-cli.ini`)
226
+ 4. Built-in defaults
227
+
228
+ ## Output Formats
229
+
230
+ By default, output is human-readable text. Use `--json` for machine-readable JSON:
231
+
232
+ ```bash
233
+ # Human-readable
234
+ xray test list --limit 3
235
+
236
+ # JSON output (pipe to jq, etc.)
237
+ xray --json test list --limit 3 | jq '.results[].issue_key'
238
+ ```
239
+
240
+ ## Exit Codes
241
+
242
+ | Code | Meaning |
243
+ |------|---------|
244
+ | 0 | Success |
245
+ | 2 | Usage / validation error |
246
+ | 3 | Configuration error |
247
+ | 4 | Authentication error |
248
+ | 5 | Network / transport error |
249
+ | 6 | API / resolution error |
250
+ | 7 | Import error |
251
+
252
+ ## Running Tests
253
+
254
+ ```bash
255
+ PYTHONPATH=src python3 -m unittest discover -s tests
256
+ ```
257
+
258
+ ## License
259
+
260
+ MIT
@@ -0,0 +1,235 @@
1
+ # xray-cli
2
+
3
+ Command-line interface for [Xray Cloud](https://www.getxray.app/) test management.
4
+
5
+ A zero-dependency Python CLI that talks to the Xray Cloud API (GraphQL + REST) to manage test executions, tests, runs, results, preconditions, test sets, test plans, and folders — all from your terminal.
6
+
7
+ ## Installation
8
+
9
+ Requires Python 3.8+.
10
+
11
+ ```bash
12
+ pip install .
13
+ ```
14
+
15
+ This installs the `xray` command.
16
+
17
+ ## Quick Start
18
+
19
+ 1. **Set your Xray API credentials** (get them from Xray Cloud → API Keys):
20
+
21
+ ```bash
22
+ export XRAY_CLIENT_ID="your-client-id"
23
+ export XRAY_CLIENT_SECRET="your-client-secret"
24
+ ```
25
+
26
+ Or create a config file (see [Configuration](#configuration) below).
27
+
28
+ 2. **Run a command:**
29
+
30
+ ```bash
31
+ xray execution list --limit 5
32
+ xray test get XSP-123
33
+ xray run list --execution XSP-456 --failed-only
34
+ ```
35
+
36
+ ## Global Flags
37
+
38
+ | Flag | Short | Description |
39
+ |------|-------|-------------|
40
+ | `--version` | | Print version and exit |
41
+ | `--config PATH` | | Path to config file (default: auto-discover `.xray-cli.ini`) |
42
+ | `--json` | | Output in JSON format instead of human-readable text |
43
+ | `--verbose` | `-v` | Enable verbose/debug output |
44
+ | `--quiet` | `-q` | Suppress all non-error output |
45
+ | `--timeout SECONDS` | | Request timeout in seconds (default: from config or 30) |
46
+
47
+ > **Note:** Global flags can be placed anywhere in the command, e.g. `xray --json execution list` or `xray execution list --json`.
48
+
49
+ ## Commands
50
+
51
+ ### `xray execution` — Test Execution Operations
52
+
53
+ **List executions:**
54
+
55
+ ```bash
56
+ xray execution list [--jql JQL] [--limit N] [--start N]
57
+ ```
58
+
59
+ **Create an execution:**
60
+
61
+ ```bash
62
+ xray execution create --project XSP --summary "Regression run" [--test XSP-100 --test XSP-101]
63
+ ```
64
+
65
+ | Argument | Required | Description |
66
+ |----------|----------|-------------|
67
+ | `--project` | Yes | Jira project key |
68
+ | `--summary` | Yes | Summary for the new execution |
69
+ | `--test` | No | Test issue key to include (repeatable) |
70
+
71
+ ### `xray test` — Test Operations
72
+
73
+ **Get a single test:**
74
+
75
+ ```bash
76
+ xray test get XSP-123
77
+ ```
78
+
79
+ **List tests:**
80
+
81
+ ```bash
82
+ xray test list [--jql JQL] [--limit N] [--start N]
83
+ ```
84
+
85
+ ### `xray run` — Test Run Operations
86
+
87
+ **List runs:**
88
+
89
+ ```bash
90
+ xray run list [--test XSP-123] [--execution XSP-456] [--status PASS] [--failed-only] [--limit N] [--start N]
91
+ ```
92
+
93
+ | Argument | Description |
94
+ |----------|-------------|
95
+ | `--test` | Filter by test issue key (repeatable) |
96
+ | `--execution` | Filter by execution issue key (repeatable) |
97
+ | `--status` | Filter by run status name |
98
+ | `--failed-only` | Show only failed/aborted runs |
99
+
100
+ **Update run status:**
101
+
102
+ ```bash
103
+ xray run update-status <run_id> <status>
104
+ ```
105
+
106
+ Status values: `PASS`, `FAIL`, `TODO`, `EXECUTING`, `ABORTED`, etc.
107
+
108
+ **Add evidence to a run:**
109
+
110
+ ```bash
111
+ xray run add-evidence <run_id> path/to/screenshot.png
112
+ ```
113
+
114
+ ### `xray results` — Import Test Results
115
+
116
+ **Import Xray JSON results:**
117
+
118
+ ```bash
119
+ xray results import xray-json results.json [--project XSP] [--execution XSP-456]
120
+ ```
121
+
122
+ **Import JUnit XML results:**
123
+
124
+ ```bash
125
+ xray results import junit results.xml [--project XSP] [--execution XSP-456]
126
+ ```
127
+
128
+ ### `xray precondition` — Precondition Operations
129
+
130
+ ```bash
131
+ xray precondition list [--jql JQL] [--limit N] [--start N]
132
+ ```
133
+
134
+ ### `xray test-set` — Test Set Operations
135
+
136
+ ```bash
137
+ xray test-set list [--jql JQL] [--limit N] [--start N]
138
+ ```
139
+
140
+ ### `xray test-plan` — Test Plan Operations
141
+
142
+ ```bash
143
+ xray test-plan list [--jql JQL] [--limit N] [--start N]
144
+ ```
145
+
146
+ ### `xray folder` — Folder Operations
147
+
148
+ ```bash
149
+ xray folder list --project XSP [--path /MyFolder] [--limit N] [--start N]
150
+ ```
151
+
152
+ ## Configuration
153
+
154
+ ### Config File
155
+
156
+ Create `.xray-cli.ini` in your project directory or home directory:
157
+
158
+ ```ini
159
+ [xray]
160
+ client_id = YOUR_CLIENT_ID
161
+ client_secret = YOUR_CLIENT_SECRET
162
+
163
+ [defaults]
164
+ project_key = XSP
165
+ output = text
166
+ timeout_seconds = 30
167
+ page_limit = 50
168
+ ```
169
+
170
+ See `xray-cli.ini.example` for a full annotated template.
171
+
172
+ You can also specify a config file explicitly:
173
+
174
+ ```bash
175
+ xray --config /path/to/config.ini execution list
176
+ ```
177
+
178
+ ### Environment Variables
179
+
180
+ All settings can be set via environment variables:
181
+
182
+ | Variable | Config Key |
183
+ |----------|------------|
184
+ | `XRAY_CLIENT_ID` | `client_id` |
185
+ | `XRAY_CLIENT_SECRET` | `client_secret` |
186
+ | `XRAY_AUTH_URL` | `auth_url` |
187
+ | `XRAY_GRAPHQL_URL` | `graphql_url` |
188
+ | `XRAY_IMPORT_BASE_URL` | `import_base_url` |
189
+ | `XRAY_PROJECT_KEY` | `project_key` |
190
+ | `XRAY_OUTPUT` | `output` |
191
+ | `XRAY_TIMEOUT` | `timeout_seconds` |
192
+ | `XRAY_PAGE_LIMIT` | `page_limit` |
193
+
194
+ ### Precedence
195
+
196
+ Settings are resolved in this order (highest wins):
197
+
198
+ 1. CLI flags (`--json`, `--timeout`)
199
+ 2. Environment variables (`XRAY_*`)
200
+ 3. Config file (`.xray-cli.ini`)
201
+ 4. Built-in defaults
202
+
203
+ ## Output Formats
204
+
205
+ By default, output is human-readable text. Use `--json` for machine-readable JSON:
206
+
207
+ ```bash
208
+ # Human-readable
209
+ xray test list --limit 3
210
+
211
+ # JSON output (pipe to jq, etc.)
212
+ xray --json test list --limit 3 | jq '.results[].issue_key'
213
+ ```
214
+
215
+ ## Exit Codes
216
+
217
+ | Code | Meaning |
218
+ |------|---------|
219
+ | 0 | Success |
220
+ | 2 | Usage / validation error |
221
+ | 3 | Configuration error |
222
+ | 4 | Authentication error |
223
+ | 5 | Network / transport error |
224
+ | 6 | API / resolution error |
225
+ | 7 | Import error |
226
+
227
+ ## Running Tests
228
+
229
+ ```bash
230
+ PYTHONPATH=src python3 -m unittest discover -s tests
231
+ ```
232
+
233
+ ## License
234
+
235
+ MIT
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45,<75"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xray-cli"
7
+ version = "0.1.0"
8
+ description = "Command-line interface for Xray Cloud test management"
9
+ requires-python = ">=3.8"
10
+ license = {text = "MIT"}
11
+ readme = "README.md"
12
+ keywords = ["xray", "jira", "testing", "test-management", "cli"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.8",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Testing",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/arieluchka/xray-cli"
30
+ Repository = "https://github.com/arieluchka/xray-cli"
31
+ Issues = "https://github.com/arieluchka/xray-cli/issues"
32
+
33
+ [project.scripts]
34
+ xray = "xray_cli.cli:main"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Xray Cloud CLI - command-line interface for Xray test management."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,88 @@
1
+ """Token service for Xray Cloud API authentication."""
2
+
3
+ import json
4
+ import urllib.request
5
+ import urllib.error
6
+
7
+ from xray_cli.errors import AuthError, TransportError
8
+ from xray_cli import log
9
+
10
+
11
+ class TokenService:
12
+ """Acquires and caches Xray API tokens using Client ID and Secret."""
13
+
14
+ def __init__(self, auth_url, client_id, client_secret, timeout=30):
15
+ self._auth_url = auth_url
16
+ self._client_id = client_id
17
+ self._client_secret = client_secret
18
+ self._timeout = timeout
19
+ self._token = None
20
+
21
+ def get_token(self):
22
+ """Return a cached token, or acquire a new one."""
23
+ if self._token is not None:
24
+ return self._token
25
+ self._token = self._authenticate()
26
+ return self._token
27
+
28
+ def invalidate_token(self):
29
+ """Clear the cached token so the next call re-authenticates."""
30
+ self._token = None
31
+
32
+ def _authenticate(self):
33
+ """Exchange Client ID/Secret for an auth token."""
34
+ payload = json.dumps({
35
+ "client_id": self._client_id,
36
+ "client_secret": self._client_secret,
37
+ }).encode("utf-8")
38
+
39
+ req = urllib.request.Request(
40
+ self._auth_url,
41
+ data=payload,
42
+ headers={"Content-Type": "application/json"},
43
+ method="POST",
44
+ )
45
+
46
+ log.debug("Authenticating against %s", self._auth_url)
47
+
48
+ try:
49
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
50
+ body = resp.read().decode("utf-8")
51
+ # The Xray auth endpoint returns the token as a bare JSON string
52
+ token = json.loads(body)
53
+ if isinstance(token, str):
54
+ log.debug("Token acquired successfully")
55
+ return token
56
+ # Some responses may wrap it differently
57
+ if isinstance(token, dict) and "token" in token:
58
+ log.debug("Token acquired successfully")
59
+ return token["token"]
60
+ raise AuthError(
61
+ "Unexpected auth response format",
62
+ detail=f"Response body: {body[:200]}",
63
+ )
64
+ except urllib.error.HTTPError as exc:
65
+ status = exc.code
66
+ try:
67
+ detail = exc.read().decode("utf-8", errors="replace")[:500]
68
+ except Exception:
69
+ detail = None
70
+ if status in (401, 403):
71
+ raise AuthError(
72
+ "Authentication failed: invalid Client ID or Client Secret",
73
+ detail=detail,
74
+ )
75
+ raise AuthError(
76
+ f"Authentication failed with HTTP {status}",
77
+ detail=detail,
78
+ )
79
+ except urllib.error.URLError as exc:
80
+ raise TransportError(
81
+ f"Cannot reach auth endpoint: {exc.reason}",
82
+ detail=str(exc),
83
+ )
84
+ except (OSError, ValueError) as exc:
85
+ raise TransportError(
86
+ f"Auth request failed: {exc}",
87
+ detail=str(exc),
88
+ )