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
12 changes: 6 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
default_stages: [commit]
default_stages: [pre-commit]
repos:
- repo: https://github.com/ambv/black
rev: 24.3.0
Expand Down Expand Up @@ -68,7 +68,7 @@ repos:
name: GitGuardian Shield
entry: pipenv run ggshield secret scan pre-commit
language: system
stages: [commit]
stages: [pre-commit]

- repo: local
hooks:
Expand All @@ -77,14 +77,14 @@ repos:
entry: pipenv run ggshield secret scan pre-push
language: system
pass_filenames: false
stages: [push]
stages: [pre-push]

- repo: https://github.com/gitguardian/ggshield
rev: v1.32.0
hooks:
- id: ggshield
language_version: python3
stages: [commit]
stages: [pre-commit]

- repo: local
hooks:
Expand All @@ -94,12 +94,12 @@ repos:
language: system
pass_filenames: false
types: [python]
stages: [commit]
stages: [pre-commit]

- id: import-linter
name: Import Linter
entry: pipenv run lint-imports
language: system
pass_filenames: false
types: [python]
stages: [commit]
stages: [pre-commit]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Added

- `ggshield config list` command now supports the `--json` option, allowing output in JSON format.

### Changed

- `ggshield api-status --json` now also outputs the instance URL.
13 changes: 12 additions & 1 deletion doc/schemas/api-status.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
"minimum": 200,
"description": "HTTP status code for the request"
},
"instance": {
"type": "string",
"format": "uri",
"description": "URL of the GitGuardian instance"
},
"detail": {
"type": "string",
"description": "Human-readable version of the status"
Expand All @@ -21,5 +26,11 @@
"description": "Version of the secrets engine"
}
},
"required": ["status_code", "detail", "app_version", "secrets_engine_version"]
"required": [
"status_code",
"instance",
"detail",
"app_version",
"secrets_engine_version"
]
}
67 changes: 67 additions & 0 deletions doc/schemas/config_list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ggshield config list",
"type": "object",
"properties": {
"instances": {
"type": "array",
"items": {
"type": "object",
"properties": {
"instance_name": {
"type": "string",
"description": "Name of the GitGuardian instance"
},
"default_token_lifetime": {
"type": ["string", "null"],
"description": "Default token lifetime"
},
"workspace_id": {
"type": ["number", "string"],
"description": "Workspace ID"
},
"url": {
"type": "string",
"format": "uri",
"description": "URL of the GitGuardian instance"
},
"token": {
"type": "string",
"description": "API Token for the instance"
},
"token_name": {
"type": "string",
"description": "Name of the token"
},
"expiry": {
"type": "string",
"description": "Expiration date of the token"
}
},
"required": [
"instance_name",
"workspace_id",
"url",
"token",
"token_name",
"expiry"
]
}
},
"global_values": {
"type": "object",
"properties": {
"instance": {
"type": ["string", "null"],
"description": "Name of the default GitGuardian instance"
},
"default_token_lifetime": {
"type": ["string", "null"],
"description": "Default token lifetime"
}
},
"required": ["instance", "default_token_lifetime"]
}
},
"required": ["instances", "global_values"]
}
137 changes: 90 additions & 47 deletions ggshield/cmd/config/config_list.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,112 @@
from typing import Any, Tuple
import json
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

import click

from ggshield.cmd.utils.common_options import add_common_options
from ggshield.cmd.utils.common_options import (
add_common_options,
json_option,
text_json_format_option,
)
from ggshield.cmd.utils.context_obj import ContextObj
from ggshield.core.config.auth_config import InstanceConfig

from .constants import DATETIME_FORMAT, FIELDS


@dataclass
class InstanceInfo:
instance_name: str
default_token_lifetime: Optional[int]
workspace_id: Any
url: str
token: str
token_name: str
expiry: str


@dataclass
class ConfigData:
instances: List[InstanceInfo] = field(default_factory=list)
global_values: Dict[str, Any] = field(default_factory=dict)

def as_dict(self) -> Dict[str, Any]:
return {
"instances": [instance.__dict__ for instance in self.instances],
"global_values": self.global_values,
}


def get_instance_info(
instance: InstanceConfig, default_token_lifetime: Any
) -> InstanceInfo:
"""Helper function to extract instance information."""
instance_name = instance.name or instance.url
account = instance.account

if account is not None:
workspace_id = account.workspace_id
token = account.token
token_name = account.token_name
expire_at = account.expire_at
expiry = expire_at.strftime(DATETIME_FORMAT) if expire_at else "never"
else:
workspace_id = token = token_name = expiry = "not set"

_default_token_lifetime = instance.default_token_lifetime or default_token_lifetime

return InstanceInfo(
instance_name=instance_name,
default_token_lifetime=_default_token_lifetime,
workspace_id=workspace_id,
url=instance.url,
token=token,
token_name=token_name,
expiry=expiry,
)


@click.command()
@click.pass_context
@json_option
@text_json_format_option
@add_common_options()
def config_list_cmd(ctx: click.Context, **kwargs: Any) -> int:
"""
Print the list of configuration keys and values.
"""
config = ContextObj.get(ctx).config
ctx_obj = ContextObj.get(ctx)
config = ctx_obj.config
default_token_lifetime = config.auth_config.default_token_lifetime

message_lines = []

def add_entries(*entries: Tuple[str, Any]):
for key, value in entries:
message_lines.append(f"{key}: {value}")

# List global values
for field in FIELDS.values():
config_obj = config.auth_config if field.auth_config else config.user_config
value = getattr(config_obj, field.name)
add_entries((field.name, value))
message_lines.append("")

# List instance values
for instance in config.auth_config.instances:
instance_name = instance.name or instance.url

if instance.account is not None:
workspace_id = instance.account.workspace_id
token = instance.account.token
token_name = instance.account.token_name
expire_at = instance.account.expire_at
expiry = (
expire_at.strftime(DATETIME_FORMAT)
if expire_at is not None
else "never"
)
else:
workspace_id = token = token_name = expiry = "not set"

_default_token_lifetime = (
instance.default_token_lifetime
if instance.default_token_lifetime is not None
else default_token_lifetime
config_data = ConfigData()
for config_field in FIELDS.values():
config_obj = (
config.auth_config if config_field.auth_config else config.user_config
)
value = getattr(config_obj, config_field.name)
config_data.global_values[config_field.name] = value

message_lines.append(f"[{instance_name}]")
add_entries(
("default_token_lifetime", _default_token_lifetime),
("workspace_id", workspace_id),
("url", instance.url),
("token", token),
("token_name", token_name),
("expiry", expiry),
)
config_data.instances = [
get_instance_info(instance, default_token_lifetime)
for instance in config.auth_config.instances
]

if ctx_obj.use_json:
click.echo(json.dumps(config_data.as_dict()))
else:
message_lines = [
f"{key}: {value}" for key, value in config_data.global_values.items()
]
message_lines.append("")
for instance in config_data.instances:
message_lines.append(f"[{instance.instance_name}]")
for key, value in instance.__dict__.items():
if key != "instance_name":
message_lines.append(f"{key}: {value}")
message_lines.append("")

click.echo("\n".join(message_lines).strip())

click.echo("\n".join(message_lines).strip())
return 0
12 changes: 7 additions & 5 deletions ggshield/cmd/status.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/python3
import json
from typing import Any

import click
Expand Down Expand Up @@ -29,17 +30,18 @@
if not isinstance(response, HealthCheckResponse):
raise UnexpectedError("Unexpected health check response")

click.echo(
response.to_json()
if ctx_obj.use_json
else (
if ctx_obj.use_json:
json_output = response.to_dict()
json_output["instance"] = client.base_uri
click.echo(json.dumps(json_output))
else:
click.echo(

Check warning on line 38 in ggshield/cmd/status.py

View check run for this annotation

Codecov / codecov/patch

ggshield/cmd/status.py#L38

Added line #L38 was not covered by tests
f"{format_text('API URL:', STYLE['key'])} {client.base_uri}\n"
f"{format_text('Status:', STYLE['key'])} {format_healthcheck_status(response)}\n"
f"{format_text('App version:', STYLE['key'])} {response.app_version or 'Unknown'}\n"
f"{format_text('Secrets engine version:', STYLE['key'])} "
f"{response.secrets_engine_version or 'Unknown'}\n"
)
)

return 0

Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ def api_status_json_schema() -> Dict[str, Any]:
return _load_json_schema("api-status.json")


@pytest.fixture(scope="session")
def config_list_json_schema() -> Dict[str, Any]:
"""Load the JSON schema for `config list` command."""
return _load_json_schema("config_list.json")


@pytest.fixture(scope="session")
def sca_scan_all_json_schema() -> Dict[str, Any]:
"""Load the JSON schema for `sca scan all` command."""
Expand Down
Loading
Loading