Repository navigation
feat: add the general, plugin and storage commands - #41
Merged
marevol merged 5 commits intoOct 16, 2026
Merged
Conversation
Adds three admin API wrappers, available on 14.19.2, 15.6.1, 15.7.0, 15.8.0 and 15.9.0 (storage upload is not possible on 15.6 to 15.8, see below). - general get / update: update reads the current settings, sends the four required fields plus only the keys given with --set KEY=VALUE, and relies on the server merging them into the stored settings, so other settings keep their values. Unknown keys are rejected locally because the server ignores them silently. - plugin installed / available / install / uninstall: the uninstall request carries a JSON body on DELETE, so send_request now sends json_data on DELETE through httpx.request. Install and uninstall are carried out by Fess in a background thread; the command says the request was accepted. - storage list / download / upload / delete: objects are addressed by the URL-safe Base64 of their path, computed by the client. Download writes to --file (refusing to overwrite without --force) or to stdout, and treats the 200 with Content-Length 0 that Fess sends for an unreadable object as an error. Upload is a multipart POST (14.x, 15.9+); Fess 15.6 to 15.8 only declare PUT and do not parse it, so the command reports that instead. All commands exit non-zero on an API failure in every output format. Includes unit tests, integration tests and skills reference pages.
Fess queries the remote plugin repositories while serving `plugin available`, which can exceed the 5 second default client timeout (CI failed with "Network error: timed out" on 15.6.1). Use a 60 second timeout for that command.
…orage-commands # Conflicts: # skills/fessctl/SKILL.md # skills/fessctl/references/workflows.md # src/fessctl/api/client.py # src/fessctl/cli.py
This was referenced Oct 16, 2026
Verified against fake-gcs-server (Fess 15.9.0 with fess-storage-gcs): - upload: strip leading and trailing slashes from --dir. Fess joins the directory and the name with a slash without normalising, so `-d /a/b/` stored `/a/b//file`, which `list` and `download` could not reach. - list: use the long transfer timeout. Fess waits for the storage backend while serving the request, so an unreachable backend ended in a traceback after the 5 second default. - download: check that the directory of --file exists before fetching the object, instead of crashing with FileNotFoundError after it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three admin API wrappers:
general(get, update),plugin(installed, available, install, uninstall) andstorage(list, download, upload, delete). All commands support--output text|json|yamland exit non-zero on an API failure in every format.general update --set KEY=VALUEreads the current settings, then sends the four fields the server requires plus only the keys given. The server merges the request into the stored settings, so every other setting keeps its value. Unknown keys are rejected locally (the server accepts and ignores them). Integer settings are coerced from the type seen inget. Masked secrets are never sent back.plugin uninstallneeds a JSON body on DELETE, sosend_requestnow sendsjson_dataon DELETE throughhttpx.request(unchanged when no body is given). Install and uninstall are carried out by Fess in a background thread, so the commands say the request was accepted.uninstallasks for confirmation unless--yes.storagetakes plain object paths and computes the URL-safe Base64 id the API uses.downloadwrites to--file(refuses to overwrite without--force) or to stdout.deleteasks for confirmation unless--yes. Upload is a multipart POST.skills/fessctl/references/features/{general,plugin,storage}.md, SKILL.md index and count (28), stale line inworkflows.md.Versions
On 15.6 to 15.8 the upload endpoint is declared as PUT only and the server does not parse a multipart PUT (
file is required.; POST answers a 302), sostorage uploadreports that instead of sending a request. Before 15.9 an unreachable storage lists as an empty list; 15.9 returnsStorage Access Error: ....Verified live
Against Fess 15.9.0 (fess-opensearch 3.9.0), plus 15.8.0 and 14.19.2 for the version differences; the S3 backend was a LocalStack container.
get --output json(diff of 1 line) and reverting it gave a byte-identical 4107-byte file; also changing a string and an integer together, unknown key, non-integer, out-of-range value (server rejects, nothing changed), SPNEGO NTLM/Basic validation error, bad token (exit 3), secret handling. update also exercised on 15.8.0 and 14.19.2.fess-ds-csvon 15.9.0 (jar appears and disappears inWEB-INF/plugin), unknown plugin/version, declined confirmation, repeat uninstall;installed/availableon all three versions (41 available on 15.9.0). The 15 integration tests of this PR, run against stacks of 15.9.0, 15.8.0 and 14.19.2 through a stand-in for the compose fixture, passed on each (15/15).file is required.for PUT on 15.8.0.Access key ID cannot be blank), an unknownstorage_type, and the real case withfess-storage-s3uninstalled and Fess restarted (No storage client is registered as s3StorageClient ... install fess-storage-s3).uv run pytest tests/unit); the integration tests collect.Not verified
fess-ds-csvsuccessfully on 15.8.0 and 14.19.2 too).tests/commandswas not run through the repository's own compose fixture (it uses fixed ports/names).Fess API oddities found
storage downloadof a missing object or unreachable storage returns HTTP 200 withContent-Length: 0; a real empty object is sent chunked. fessctl treats the former as an error.generalaccepts unknown keys silently; a non-integer in an integer field returns onlyerror_code:<uuid>; unset LDAP keys are left out of the GET response.PUT /api/admin/storage/upload/{pathId}/is a 405 on 15.8.0; the path is a form field.Also verified against GCS (Fess 15.9.0 +
fess-storage-gcs+ fake-gcs-server): list, upload (small, 300000-byte, zero-byte, directories with spaces/unicode), download (SHA-1 identical to stdout and file), overwrite refusal, delete. This found and fixed three issues:upload --dir /a/b/stored/a/b//file(Fess does not normalise the key),listhit the 5 second timeout when the backend is unreachable, anddownload --fileinto a missing directory crashed after fetching. Not verified: real GCS and the credentials-file mode.Stacked PRs (merge from the bottom; each PR's base is the branch below it, so no conflicts remain):
main← #45 theme timeout (+ CI runs for stacked PRs) ← #46 typer ← #43 ops commands ← #44 dict ← #41 general/plugin/storage ← #42 searchlist/documents.After a PR is squash-merged GitHub retargets the next one to
main.