Skip to content

Commit c9cebb0

Browse files
GG-HHclaude
andcommitted
feat(api-status): show account id of the token
Display the workspace_id from the /api_tokens/self response as "Account ID" in both text and JSON output of `ggshield api-status`, right below the API URL. Omitted when the api_tokens call fails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c7a1019 commit c9cebb0

4 files changed

Lines changed: 38 additions & 2 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Added
2+
3+
- `ggshield api-status` now displays the account ID (workspace ID) associated with the current token, in both text and JSON output.

doc/schemas/api-status.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
"type": "array",
3838
"items": { "type": "string" },
3939
"description": "List of scopes granted to the current authentication token"
40+
},
41+
"account_id": {
42+
"type": "integer",
43+
"description": "Identifier of the workspace (account) the authentication token belongs to"
4044
}
4145
},
4246
"required": [

ggshield/cmd/status.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ def status_cmd(ctx: click.Context, **kwargs: Any) -> int:
3333
raise UnexpectedError("Unexpected health check response")
3434

3535
token_scopes: Optional[List[str]] = None
36+
account_id: Optional[int] = None
3637
token_response = client.api_tokens()
3738
if isinstance(token_response, APITokensResponse):
3839
token_scopes = token_response.scopes
40+
account_id = token_response.workspace_id
3941

4042
instance, instance_source = ctx_obj.config.get_instance_name_and_source()
4143
_, api_key_source = ctx_obj.config.get_api_key_and_source()
@@ -46,16 +48,24 @@ def status_cmd(ctx: click.Context, **kwargs: Any) -> int:
4648
json_output["api_key_source"] = api_key_source.name
4749
if token_scopes is not None:
4850
json_output["token_scopes"] = token_scopes
51+
if account_id is not None:
52+
json_output["account_id"] = account_id
4953
click.echo(json.dumps(json_output))
5054
else:
5155
scopes_line = (
5256
f"{format_text('Token scopes:', STYLE['key'])} {', '.join(token_scopes)}\n"
5357
if token_scopes is not None
5458
else ""
5559
)
60+
account_line = (
61+
f"{format_text('Account ID:', STYLE['key'])} {account_id}\n"
62+
if account_id is not None
63+
else ""
64+
)
5665
click.echo(
5766
f"{format_text('API URL:', STYLE['key'])} {instance}\n"
58-
f"{format_text('Status:', STYLE['key'])} {format_healthcheck_status(response)}\n"
67+
+ account_line
68+
+ f"{format_text('Status:', STYLE['key'])} {format_healthcheck_status(response)}\n"
5969
f"{format_text('App version:', STYLE['key'])} {response.app_version or 'Unknown'}\n"
6070
f"{format_text('Secrets engine version:', STYLE['key'])} "
6171
f"{response.secrets_engine_version or 'Unknown'}\n\n"

tests/unit/cmd/test_status.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def test_api_status(cli_fs_runner, api_status_json_schema):
4848
"instance_source": In(x.name for x in ConfigSource),
4949
"api_key_source": In(x.name for x in ConfigSource),
5050
"token_scopes": ["scan"],
51+
"account_id": 1,
5152
}
5253
)
5354
)
@@ -198,6 +199,22 @@ def test_api_status_shows_token_scopes(cli_fs_runner):
198199
assert "scan" in result.output
199200

200201

202+
def test_api_status_shows_account_id(cli_fs_runner):
203+
"""
204+
GIVEN a valid API token
205+
WHEN running api-status
206+
THEN the workspace id is displayed as the account id, right below the API URL
207+
"""
208+
with my_vcr.use_cassette("test_health_check"):
209+
result = cli_fs_runner.invoke(cli, ["api-status"], color=False)
210+
assert_invoke_ok(result)
211+
assert "Account ID: 1" in result.output
212+
213+
lines = result.output.splitlines()
214+
api_url_index = next(i for i, line in enumerate(lines) if line.startswith("API URL:"))
215+
assert lines[api_url_index + 1].startswith("Account ID:")
216+
217+
201218
@mock.patch(
202219
"pygitguardian.GGClient.api_tokens",
203220
return_value=Detail("Unauthorized", 401),
@@ -212,8 +229,10 @@ def test_api_status_scopes_omitted_on_error(
212229
"""
213230
GIVEN an api_tokens call that returns an error
214231
WHEN running api-status
215-
THEN the command succeeds and token scopes are simply omitted from the output
232+
THEN the command succeeds and token scopes and account id are simply
233+
omitted from the output
216234
"""
217235
result = cli_fs_runner.invoke(cli, ["api-status"], color=False)
218236
assert_invoke_ok(result)
219237
assert "Token scopes:" not in result.output
238+
assert "Account ID:" not in result.output

0 commit comments

Comments
 (0)