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
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:
fess-version:
- fess_version: "14.19.2"
opensearch_version: "2.19.1"
- fess_version: "15.1.0"
opensearch_version: "3.1.0"
- fess_version: "15.3.2"
opensearch_version: "3.3.2"

steps:
- uses: actions/checkout@v4
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The easiest way to get started is using the pre-built Docker image:
docker run --rm \
-e FESS_ENDPOINT=https://your-fess-server \
-e FESS_ACCESS_TOKEN=your_access_token_here \
-e FESS_VERSION=15.1.0 \
-e FESS_VERSION=15.3.2 \
ghcr.io/codelibs/fessctl:0.1.0 --help
```

Expand All @@ -35,13 +35,13 @@ Run actual commands:
docker run --rm \
-e FESS_ENDPOINT=https://your-fess-server \
-e FESS_ACCESS_TOKEN=your_access_token_here \
-e FESS_VERSION=15.1.0 \
-e FESS_VERSION=15.3.2 \
ghcr.io/codelibs/fessctl:0.1.0 ping

docker run --rm \
-e FESS_ENDPOINT=https://your-fess-server \
-e FESS_ACCESS_TOKEN=your_access_token_here \
-e FESS_VERSION=15.1.0 \
-e FESS_VERSION=15.3.2 \
ghcr.io/codelibs/fessctl:0.1.0 user list
```

Expand All @@ -61,7 +61,7 @@ Then run with your custom image:
docker run --rm \
-e FESS_ENDPOINT=https://your-fess-server \
-e FESS_ACCESS_TOKEN=your_access_token_here \
-e FESS_VERSION=15.1.0 \
-e FESS_VERSION=15.3.2 \
fessctl:latest --help
```

Expand All @@ -86,7 +86,7 @@ uv pip install -e src
```bash
export FESS_ACCESS_TOKEN=your_access_token_here
export FESS_ENDPOINT=https://your-fess-server
export FESS_VERSION=15.1.0
export FESS_VERSION=15.3.2

fessctl --help
fessctl ping
Expand All @@ -100,7 +100,7 @@ All three methods require the following environment variables:

- `FESS_ENDPOINT`: The URL of your Fess server's API endpoint (default: `http://localhost:8080`)
- `FESS_ACCESS_TOKEN`: Bearer token for API authentication (required)
- `FESS_VERSION`: Target Fess version for API compatibility (default: `15.1.0`)
- `FESS_VERSION`: Target Fess version for API compatibility (default: `15.3.2`)

## License

Expand Down
16 changes: 8 additions & 8 deletions src/fessctl/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, status_code: int, content: str):


class FessAPIClient:
def __init__(self, settings: Settings):
def __init__(self, settings: Settings, timeout: float = 5.0):
self.base_url = settings.fess_endpoint
self.admin_api_headers = {
"Authorization": f"Bearer {settings.access_token}",
Expand All @@ -39,7 +39,7 @@ def __init__(self, settings: Settings):
self.search_api_headers = {
"Content-Type": "application/json",
}
self.timeout = 5.0
self.timeout = timeout
self._major_version, self._minor_version = self._parse_version(
settings.fess_version)

Expand Down Expand Up @@ -100,17 +100,17 @@ def send_request(
except httpx.RequestError as e:
raise FessAPIClientError(
status_code=-1,
content=str(e)
content=f"Network error: {str(e)}"
) from e
# response.raise_for_status()

# Parse JSON response - Fess API returns JSON even for errors
try:
return response.json()
except json.decoder.JSONDecodeError as e:
raw = response.text
code = response.status_code
# If JSON parsing fails, raise an exception with HTTP status info
raise FessAPIClientError(
status_code=code,
content=raw
status_code=response.status_code,
content=f"Invalid JSON response (HTTP {response.status_code}): {response.text}"
) from e

def ping(self) -> dict:
Expand Down
86 changes: 46 additions & 40 deletions src/fessctl/commands/fileauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,49 +196,55 @@ def get_fileauth(
Retrieve a FileAuth by ID.
"""
client = FessAPIClient(Settings())
result = client.get_fileauth(config_id)
status = result.get("response", {}).get("status", 1)
try:
result = client.get_fileauth(config_id)
status = result.get("response", {}).get("status", 1)

if output == "json":
typer.echo(json.dumps(result, indent=2))
elif output == "yaml":
typer.echo(yaml.dump(result))
else:
if status == 0:
fileauth = result.get("response", {}).get("setting", {})
console = Console()
table = Table(title=f"FileAuth Details: {fileauth.get('id', '-')}")
table.add_column("Field", style="cyan", no_wrap=True)
table.add_column("Value", style="magenta")
if output == "json":
typer.echo(json.dumps(result, indent=2))
elif output == "yaml":
typer.echo(yaml.dump(result))
else:
if status == 0:
fileauth = result.get("response", {}).get("setting", {})
console = Console()
table = Table(title=f"FileAuth Details: {fileauth.get('id', '-')}")
table.add_column("Field", style="cyan", no_wrap=True)
table.add_column("Value", style="magenta")

# Output fields (FileAuth public name fields only)
table.add_row("id", str(fileauth.get("id", "-")))
table.add_row("updated_by", str(fileauth.get("updated_by", "-")))
table.add_row("updated_time", to_utc_iso8601(
fileauth.get("updated_time")))
table.add_row("version_no", str(fileauth.get("version_no", "-")))
table.add_row("crud_mode", str(fileauth.get("crud_mode", "-")))
table.add_row("hostname", str(fileauth.get("hostname", "-")))
table.add_row("port", str(fileauth.get("port", "-")))
table.add_row("protocol_scheme", str(
fileauth.get("protocol_scheme", "-")))
table.add_row("username", str(fileauth.get("username", "-")))
table.add_row("password", str(fileauth.get("password", "-")))
table.add_row("parameters", str(fileauth.get("parameters", "-")))
table.add_row("file_config_id", str(
fileauth.get("file_config_id", "-")))
table.add_row("created_by", str(fileauth.get("created_by", "-")))
table.add_row("created_time", to_utc_iso8601(
fileauth.get("created_time")))
# Output fields (FileAuth public name fields only)
table.add_row("id", str(fileauth.get("id", "-")))
table.add_row("updated_by", str(fileauth.get("updated_by", "-")))
table.add_row("updated_time", to_utc_iso8601(
fileauth.get("updated_time")))
table.add_row("version_no", str(fileauth.get("version_no", "-")))
table.add_row("crud_mode", str(fileauth.get("crud_mode", "-")))
table.add_row("hostname", str(fileauth.get("hostname", "-")))
table.add_row("port", str(fileauth.get("port", "-")))
table.add_row("protocol_scheme", str(
fileauth.get("protocol_scheme", "-")))
table.add_row("username", str(fileauth.get("username", "-")))
table.add_row("password", str(fileauth.get("password", "-")))
table.add_row("parameters", str(fileauth.get("parameters", "-")))
table.add_row("file_config_id", str(
fileauth.get("file_config_id", "-")))
table.add_row("created_by", str(fileauth.get("created_by", "-")))
table.add_row("created_time", to_utc_iso8601(
fileauth.get("created_time")))

console.print(table)
else:
message: str = result.get("response", {}).get("message", "")
typer.secho(
f"Failed to retrieve FileAuth. {message} Status code: {status}",
fg=typer.colors.RED,
)
raise typer.Exit(code=1)
console.print(table)
else:
message: str = result.get("response", {}).get("message", "")
typer.secho(
f"Failed to retrieve FileAuth. {message} Status code: {status}",
fg=typer.colors.RED,
)
raise typer.Exit(code=status)
except typer.Exit:
raise
except Exception as e:
typer.secho(f"Error retrieving fileauth: {e}", fg=typer.colors.RED)
raise typer.Exit(code=1)


@fileauth_app.command("list")
Expand Down
150 changes: 78 additions & 72 deletions src/fessctl/commands/fileconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,81 +295,87 @@ def get_fileconfig(
Retrieve a FileConfig by ID.
"""
client = FessAPIClient(Settings())
result = client.get_fileconfig(config_id)
status = result.get("response", {}).get("status", 1)
try:
result = client.get_fileconfig(config_id)
status = result.get("response", {}).get("status", 1)

if output == "json":
typer.echo(json.dumps(result, indent=2))
elif output == "yaml":
typer.echo(yaml.dump(result))
else:
if status == 0:
fileconfig = result.get("response", {}).get("setting", {})
console = Console()
table = Table(
title=f"FileConfig Details: {fileconfig.get('name', '-')}")
table.add_column("Field", style="cyan", no_wrap=True)
table.add_column("Value", style="magenta")
if output == "json":
typer.echo(json.dumps(result, indent=2))
elif output == "yaml":
typer.echo(yaml.dump(result))
else:
if status == 0:
fileconfig = result.get("response", {}).get("setting", {})
console = Console()
table = Table(
title=f"FileConfig Details: {fileconfig.get('name', '-')}")
table.add_column("Field", style="cyan", no_wrap=True)
table.add_column("Value", style="magenta")

# Output all public fields
table.add_row("id", str(fileconfig.get("id", "-")))
table.add_row("updated_by", str(fileconfig.get("updated_by", "-")))
table.add_row("updated_time", to_utc_iso8601(
fileconfig.get("updated_time")))
table.add_row("version_no", str(fileconfig.get("version_no", "-")))
table.add_row(
"label_type_ids", ", ".join(
fileconfig.get("label_type_ids", []))
)
table.add_row("crud_mode", str(fileconfig.get("crud_mode", "-")))
table.add_row("name", str(fileconfig.get("name", "-")))
table.add_row("description", str(
fileconfig.get("description", "-")))
table.add_row("paths", str(fileconfig.get("paths", "-")))
table.add_row("included_paths", str(
fileconfig.get("included_paths", "-")))
table.add_row("excluded_paths", str(
fileconfig.get("excluded_paths", "-")))
table.add_row(
"included_doc_paths", str(
fileconfig.get("included_doc_paths", "-"))
)
table.add_row(
"excluded_doc_paths", str(
fileconfig.get("excluded_doc_paths", "-"))
)
table.add_row(
"config_parameter", str(
fileconfig.get("config_parameter", "-"))
)
table.add_row("depth", str(fileconfig.get("depth", "-")))
table.add_row(
"max_access_count", str(
fileconfig.get("max_access_count", "-"))
)
table.add_row("num_of_thread", str(
fileconfig.get("num_of_thread", "-")))
table.add_row("interval_time", str(
fileconfig.get("interval_time", "-")))
table.add_row("boost", str(fileconfig.get("boost", "-")))
table.add_row("available", str(fileconfig.get("available", "-")))
table.add_row("permissions", str(
fileconfig.get("permissions", "-")))
table.add_row("virtual_hosts", str(
fileconfig.get("virtual_hosts", "-")))
table.add_row("sort_order", str(fileconfig.get("sort_order", "-")))
table.add_row("created_by", str(fileconfig.get("created_by", "-")))
table.add_row("created_time", to_utc_iso8601(
fileconfig.get("created_time")))
# Output all public fields
table.add_row("id", str(fileconfig.get("id", "-")))
table.add_row("updated_by", str(fileconfig.get("updated_by", "-")))
table.add_row("updated_time", to_utc_iso8601(
fileconfig.get("updated_time")))
table.add_row("version_no", str(fileconfig.get("version_no", "-")))
table.add_row(
"label_type_ids", ", ".join(
fileconfig.get("label_type_ids", []))
)
table.add_row("crud_mode", str(fileconfig.get("crud_mode", "-")))
table.add_row("name", str(fileconfig.get("name", "-")))
table.add_row("description", str(
fileconfig.get("description", "-")))
table.add_row("paths", str(fileconfig.get("paths", "-")))
table.add_row("included_paths", str(
fileconfig.get("included_paths", "-")))
table.add_row("excluded_paths", str(
fileconfig.get("excluded_paths", "-")))
table.add_row(
"included_doc_paths", str(
fileconfig.get("included_doc_paths", "-"))
)
table.add_row(
"excluded_doc_paths", str(
fileconfig.get("excluded_doc_paths", "-"))
)
table.add_row(
"config_parameter", str(
fileconfig.get("config_parameter", "-"))
)
table.add_row("depth", str(fileconfig.get("depth", "-")))
table.add_row(
"max_access_count", str(
fileconfig.get("max_access_count", "-"))
)
table.add_row("num_of_thread", str(
fileconfig.get("num_of_thread", "-")))
table.add_row("interval_time", str(
fileconfig.get("interval_time", "-")))
table.add_row("boost", str(fileconfig.get("boost", "-")))
table.add_row("available", str(fileconfig.get("available", "-")))
table.add_row("permissions", str(
fileconfig.get("permissions", "-")))
table.add_row("virtual_hosts", str(
fileconfig.get("virtual_hosts", "-")))
table.add_row("sort_order", str(fileconfig.get("sort_order", "-")))
table.add_row("created_by", str(fileconfig.get("created_by", "-")))
table.add_row("created_time", to_utc_iso8601(
fileconfig.get("created_time")))

console.print(table)
else:
message: str = result.get("response", {}).get("message", "")
typer.secho(
f"Failed to retrieve FileConfig. {message} Status code: {status}",
fg=typer.colors.RED,
)
raise typer.Exit(code=1)
console.print(table)
else:
message: str = result.get("response", {}).get("message", "")
typer.secho(
f"Failed to retrieve FileConfig. {message} Status code: {status}",
fg=typer.colors.RED,
)
raise typer.Exit(code=status)
except typer.Exit:
raise
except Exception as e:
typer.secho(f"Error retrieving fileconfig: {e}", fg=typer.colors.RED)
raise typer.Exit(code=1)


@fileconfig_app.command("list")
Expand Down
Loading