Compare commits

...
Author SHA1 Message Date
Jörn Friedrich Dreyer 1bafa4bd18 align the handler verifier, update docs
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-08 17:11:55 +02:00
Dominik Schmidt b18618ab4a Merge pull request #3484 from opencloud-eu/fix/tika-embedded-facet-leak
fix(search): extract facets from the main tika document only
2026-09-08 15:52:21 +02:00
Dominik Schmidt 65f19b5d71 fix(thumbnails): close rejected vips ref, map oversized convert error to forbidden 2026-09-08 15:28:37 +02:00
Dominik Schmidt 7f687ea288 fix(thumbnails): bound declared image dimensions before decoding
The imaging build decodes the full pixel buffer from the header-declared
dimensions before the existing MaxInputWidth/MaxInputHeight guard runs, so a
tiny crafted file whose header declares huge dimensions forces a multi-GB
allocation and can OOM the worker. Read the header with DecodeConfig and reject
oversized sources before the decode allocates, in both the imaging and vips
builds, and thread the limit through the audio cover-art and geogebra decoders
that decode a second attacker-controlled image.
2026-09-08 15:28:37 +02:00
Dominik Schmidt be83ed19e3 fix(search): take extraction facets from the main document only
The recursive tika response lists the file first, then its embedded
resources (cover art, thumbnails, the clip appended to a motion photo).
The loop applied getImage/getPhoto/getLocation/getAudio/getLivePhoto to
every part, so an mp3's embedded cover art leaked a 200x200 image facet
onto the track (and an embedded EXIF image would leak photo/location).
Read those facets from metas[0] only, like the video facet already does;
the loop now only concatenates title/content and detects the motion
photo clip.
2026-09-08 14:52:35 +02:00
Dominik Schmidt b5cb1cd83b test(search): re-search until the expected files are in the result (#3488)
Indexing of newly uploaded resources is asynchronous; the search step
slept a fixed 10s and searched once, so under load a just-uploaded file
was missing from the result and the following assertion failed. The
search step now remembers its query, and new eventually-contain steps
re-run the search until the expected entries are present (or the
WaitHelper timeout elapses), then assert as before. contentSearch
scenarios use them for their positive assertions; the exact-count check
runs after the expected entries have appeared.
2026-09-08 14:50:49 +02:00
zerox80 9797ee903f fix(config): correct pending version annotations
Use the supported %%NEXT%% marker for seven graph and policies settings so the environment annotation check passes.
2026-09-08 14:40:40 +02:00
Jörn Friedrich Dreyer 973dbbdf1a Merge pull request #3241 from opencloud-eu/activitylog-event-handler-split
Activitylog event handler split
2026-09-08 14:13:28 +02:00
Dominik Schmidt 1ce7f07523 Merge pull request #3486 from opencloud-eu/test/extractedprops-wait-for-values
test(search): wait for expected properties and documents
2026-09-08 14:05:12 +02:00
Dominik Schmidt 3b83221a87 test(search): retry the driveItem GET until the extracted properties match
Property extraction reaches the driveItem asynchronously, so the GET
step previously waited only until any extraction facet key appeared and
a separate step then validated the full schema, which could run on a
partial payload and fail intermittently. The GET and the status check
stay plain steps; a new explicit Then step re-fetches the item until
the response satisfies the expected schema (a partial never matches) or
the WaitHelper timeout elapses.
2026-09-08 13:39:00 +02:00
Jörn Friedrich Dreyer 391705b017 Merge pull request #3441 from opencloud-eu/dependabot/go_modules/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp-0.71.0
build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.70.0 to 0.71.0
2026-09-08 10:47:59 +02:00
Alex Ababii 8875d062b0 upd tests for auth requests middelware 2026-09-08 09:37:51 +02:00
Alex Ababii d1d20edbd2 log jwt expired on debug level instead of error 2026-09-08 09:37:51 +02:00
dependabot[bot] b50c5e54d6 build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.70.0 to 0.71.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.70.0...zpages/v0.71.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.71.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-08 07:26:51 +00:00
opencloudeu c2a34e8c45 [tx] updated from transifex 2026-09-07 23:17:03 +00:00
Dominik Schmidt d6c6b6fd0d Merge pull request #3481 from opencloud-eu/fix/proxy-signed-url-methods
fix(proxy): restrict JWT signed urls to the allowed HTTP methods
2026-09-07 16:13:40 +02:00
Dominik Schmidt 7f7a938c3f fix(proxy): restrict JWT signed urls to the allowed HTTP methods
The legacy OC-Signature path already rejects methods outside
PRE_SIGNED_URL_ALLOWED_HTTP_METHODS, the JWT path did not. A leaked
signed download url could be used for PUT, DELETE, MOVE or PROPFIND
as the signing user for the lifetime of the signature.
2026-09-07 13:00:09 +02:00
Jörn Friedrich Dreyer cfefb83d26 revert unrelated changes 2026-09-07 12:52:33 +02:00
Jörn Friedrich Dreyer 9357124a91 add tests to extracted services
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:33 +02:00
Jörn Friedrich Dreyer c36af05d14 simplify ConnectNatsKV
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:33 +02:00
Jörn Friedrich Dreyer 7947c1c98a reduce packages
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:33 +02:00
Jörn Friedrich Dreyer d58c1e3dab use json encode
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:33 +02:00
Jörn Friedrich Dreyer 5a609c98dc handle error types
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:33 +02:00
Jörn Friedrich Dreyer 29913e1d7f revert to legacy autoack events
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:32 +02:00
Jörn Friedrich Dreyer 5028364e2c add tests to cover more ack scenarios
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:32 +02:00
Jörn Friedrich Dreyer b2b15d44f7 rename test files
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:32 +02:00
Jörn Friedrich Dreyer 6ea363b0b9 add debouncer tests
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:32 +02:00
Jörn Friedrich Dreyer 9594313243 add explicit ack
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:32 +02:00
Jörn Friedrich Dreyer 62d815abe9 split handlers, fix bugs
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:32 +02:00
Jörn Friedrich Dreyer 5923aa5e5e move event + http handler code to the events package
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-09-07 12:52:32 +02:00
Dominik Schmidt 81536bbd0e Merge pull request #3471 from opencloud-eu/feat/graph-expand-thumbnails
feat(graph): expand thumbnails on driveItems
2026-09-07 12:50:53 +02:00
Jörn Friedrich Dreyer be226ebb4c Merge pull request #3478 from opencloud-eu/reva-bump-main-20260907
[full-ci] chore: bump reva to latest main
2026-09-07 12:48:12 +02:00
Dominik Schmidt c2b37668e4 chore: bump reva to latest main 2026-09-07 09:33:59 +02:00
opencloudeu 4b7c6908f0 [tx] updated from transifex 2026-09-06 23:16:18 +00:00
Dominik Schmidt 6231a870d5 feat(graph): expand thumbnails on driveItems
$expand=thumbnails was only honored by sharedByMe and sharedWithMe. The
driveItem stat, the children listing and the root children listing now
honor it as well, so a client that lists a folder learns which items have
a preview instead of guessing from the mime type.

The thumbnails are set from the resource info the listing already has, so
a later preview check that needs more than the mime type has a single
place to sit. The two share listings carry driveItems only, they keep
matching on the mime type but share the url building.
2026-09-06 23:51:35 +02:00
opencloudeu 17003fe34b [tx] updated from transifex 2026-09-05 23:17:58 +00:00
Dominik Schmidt 2ce9faec5a Merge pull request #3444 from opencloud-eu/feat/driveitem-lock
graph: expose lockInfo on driveItems
2026-09-04 09:51:13 +02:00
opencloudeu f5696d40f8 [tx] updated from transifex 2026-09-03 23:17:09 +00:00
Dominik Schmidt 9a769b8019 build(deps): bump github.com/opencloud-eu/libre-graph-api-go to 45af3945a067 2026-09-03 16:02:43 +00:00
Dominik Schmidt bafbc88a40 graph: expose lockInfo on driveItems 2026-09-03 17:52:13 +02:00
Dominik Schmidt addb32812a Merge pull request #3438 from opencloud-eu/feat/driveitem-share-types
graph: expose @libre.graph.shareTypes on driveItems
2026-09-03 17:52:10 +02:00
Dominik Schmidt aa968ec898 graph: expose @libre.graph.shareTypes on driveItems 2026-09-03 16:59:29 +02:00
Dominik Schmidt 61037cc22f Merge pull request #3202 from opencloud-eu/feat/search-live-photo-facet
feat(search): live photo facet
2026-09-03 11:42:28 +02:00
Jörn Friedrich Dreyer 993265b64e Merge pull request #3445 from opencloud-eu/feat/graph-expand-children
feat: support $expand=children on the driveItem endpoint
2026-09-03 09:35:05 +02:00
Dominik Schmidt 5995938c34 chore(search): trim the live photo comments 2026-09-03 03:34:33 +02:00
Dominik Schmidt 0a4f3ed786 fix(search): keep livePhoto.contentId out of the search siblings 2026-09-03 03:34:33 +02:00
Dominik Schmidt a7214b3d73 fix(search): drop the live photo facet on an empty pairing id 2026-09-03 03:34:33 +02:00
Dominik Schmidt 81b0df1ad4 feat(search): live photo facet 2026-09-03 03:34:33 +02:00
Dominik Schmidt e6102c0405 Merge pull request #3200 from opencloud-eu/feat/search-motion-photo-facet
feat(search): motion photo facet
2026-09-03 03:34:29 +02:00
opencloudeu ab0b3c58f1 [tx] updated from transifex 2026-09-02 23:18:41 +00:00
Dominik Schmidt 3515594787 fix(search): keep the video facet on the file itself 2026-09-03 01:09:41 +02:00
Dominik Schmidt a8318fe03b refactor(search): take the motion photo video size from the video tika extracted 2026-09-03 00:32:07 +02:00
Dominik Schmidt 706aaf02f2 refactor(search): decide the motion photo from the file xmp and its extracted video 2026-09-03 00:32:06 +02:00
Dominik Schmidt 88f21d804b refactor(search): restore cs3 Retrieve, the download split had only one caller left 2026-09-03 00:31:40 +02:00
Dominik Schmidt 8aebf80124 refactor(search): confirm the motion photo video by type, not by name 2026-09-03 00:31:40 +02:00
Dominik Schmidt ae9fd595d9 chore(search): drop the dead legacy tika key 2026-09-03 00:31:40 +02:00
Dominik Schmidt 709962b616 feat(search): confirm the motion photo video via tika instead of reading bytes 2026-09-03 00:31:40 +02:00
Dominik Schmidt a2050d1fb9 feat(search): index and expose the motion photo facet 2026-09-03 00:31:40 +02:00
Dominik Schmidt 0f4b95b9ae feat(search): add RetrieveRange to the content retriever 2026-09-03 00:27:04 +02:00
Dominik Schmidt 42876ca61c refactor(search): let getFirstValue try multiple metadata keys 2026-09-03 00:27:04 +02:00
Jörn Friedrich Dreyer f5a064395a Merge pull request #3201 from opencloud-eu/feat/search-video-facet
feat(search): video facet
2026-09-03 00:20:02 +02:00
dependabot[bot] 6688a037f7 build(deps): bump go.opentelemetry.io/contrib/zpages
Bumps [go.opentelemetry.io/contrib/zpages](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.70.0 to 0.71.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.70.0...zpages/v0.71.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/zpages
  dependency-version: 0.71.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-02 16:48:58 +02:00
Jörn Friedrich Dreyer 20a579e6ea Merge pull request #3257 from opencloud-eu/fix/userlog_notifications_handling
fix: notification handling for share removal and space membership expiry
2026-09-02 10:25:07 +02:00
Florian Schade a52b55461e Merge pull request #3432 from pbleser-oc/chore/muse-dynamic-go-version
chore(mise): determine go version in mise.toml by parsing go.mod
2026-09-01 22:16:50 +02:00
Dominik Schmidt cc73c95a14 Merge pull request #3420 from fredrikblau/fix/3410-upload-file-date
fix(search): ignore invalid photo exposure times
2026-09-01 21:59:31 +02:00
Pascal Bleser 1ab6710e4c chore(mise): determine go version by parsing go.mod
Instead of statically defining the version of Go in mise.toml as well as
in its native files (go.mod), use a small shell script to parse go.mod
and determine the version dynamically from there.

That version is then available as an env variable in the mise.toml file
and can be referenced for go, in order to avoid having to keep those
versions in sync, especially since dependabot will update go in the
go.mod file, but not in mise.toml.
2026-09-01 21:21:59 +02:00
Dominik Schmidt 0750884597 perf: check the resource type before parsing the query 2026-09-01 20:04:22 +02:00
Dominik Schmidt aff81159a5 chore: drop the listDriveItemChildren comment 2026-09-01 20:01:54 +02:00
Dominik Schmidt 701d6fea17 chore: trim comments 2026-09-01 20:01:06 +02:00
Dominik Schmidt 74dd57d500 refactor: let formatDriveItems return values so children needs no copy 2026-09-01 19:59:51 +02:00
Dominik Schmidt b95e341f9c feat: support $expand=children on the driveItem endpoint 2026-09-01 19:55:14 +02:00
amir 67124a6c37 fix(search): ignore invalid exposure denominators 2026-09-01 20:24:44 +03:30
amir db4cee156b fix(search): ignore invalid photo exposure times 2026-09-01 20:24:19 +03:30
Dominik Schmidt 727c986de8 feat(search): index and expose the video facet 2026-09-01 16:26:01 +02:00
Alex Ababii 8d407417fe granular notifications enabled by default in config 2026-09-01 15:48:03 +02:00
Alex Ababii 4a9b3f4002 upd test cases after intorduced fixes 2026-09-01 15:48:01 +02:00
Alex Ababii 71eb0716dc fix in app space memebership expired notification 2026-09-01 15:42:03 +02:00
Alex Ababii 59fd05f0a9 SpaceMembershipExpired notification email fix 2026-09-01 15:42:03 +02:00
Alex Ababii 3bd17299e3 send email when a share is removed (backport https://github.com/owncloud/ocis/pull/10915) 2026-09-01 15:42:03 +02:00
Alex Ababii cdc806d21c bump reva version with backport and fixes for SpaceMembershipExpired 2026-09-01 15:42:02 +02:00
Alex Ababii 8997fbc326 removed env vars for granular notifications from docker and vscode config 2026-09-01 15:40:31 +02:00
Alex Ababii df16f6d07f fix userlog policy violation/antivirus event handling, enabled granular notifications 2026-09-01 15:40:31 +02:00
Dominik Schmidt f60e2e52e5 Merge pull request #3437 from opencloud-eu/feat/driveitem-pending-operations
graph: expose pendingOperations on driveItems
2026-09-01 15:35:24 +02:00
Pascal Bleser 249d105d27 Merge pull request #3293 from pbleser-oc/chore/#1312-graph-event-handler-split
chore(graph): disable HTTP or eventhandlers by configuration
2026-09-01 14:31:24 +02:00
Alex Ababii 5a7afe0bf6 Merge pull request #3348 from opencloud-eu/fix/posix_cli_commands
fix: posix cli commands
2026-09-01 14:07:17 +02:00
Dominik Schmidt 000c99c63b graph: expose pendingOperations on driveItems 2026-09-01 14:05:18 +02:00
Dominik Schmidt df8d0e8fd3 Merge pull request #3198 from opencloud-eu/feat/search-tika-facets
feat(search): extract more data from tika 4 (if available)
2026-09-01 11:45:11 +02:00
Dominik Schmidt f7c5d3adb4 Merge pull request #3113 from opencloud-eu/feat/driveitem-following-tags
graph: expose following state, tags and allowed actions on driveItems
2026-09-01 11:34:53 +02:00
Pascal Bleser e4365320de chore(graph): use metrics tools introduced in 8821053ff8
* introduce a fix in the metrics tools introduced with 8821053ff8 in
    order to deal with a struct that has fields that are not exported
    (as is the case here)

  * introduce a func in the toplevel metrics package that registers a
    single metric, as that needs to be done explicitly for unexported
    ones

  * in the graph service, use RegisterAll and BuildInfo from the
    toplevel metrics package, as introduced with 8821053ff8
2026-09-01 10:57:54 +02:00
Pascal Bleser 5b72318493 chore(graph): add metrics for HTTP API and LDAP
Introducing gowrap as a build-time tool to generate interface delegate
structs from templates:

 * added as a 'make go-generate' target in services/graph,
 * added as a build-time dependency in .bingo/

Introduce an LDAP client abstraction interface to be able to wrap the
go-ldap client API with metrics transparently (and possibly hooks and
such in the future), in order to use delegation patterns to measure the
time LDAP (client) operations take to finish, as well as to track their
results (success, failure, not-found).

Has two implementations that are generated using gowrap:

* a go-ldap adapter implementation that directly delegates to a go-ldap
  connection
* a time measuring and metrics collecting implementation that delegates
  to another LdapClient

The metrics collecting one is disabled by default, can be enabled with
GRAPH_LDAP_METRICS_DISABLE=false

It collects durations of outbound LDAP client operations into a histogram, as
well as the number of concurrent outbound LDAP operations in a gauge (via an
atomic int and a gauge function, as that performs best).

Add an HTTP middleware that measures how long Graph HTTP API requests
take, storing taken time into a histogram along with labels for

 * method,
 * path pattern (from the chi routes),
 * Graph API version prefix,
 * Graph API resource name,
 * and the resulting status code.

It also tracks the number of concurrent inbound Graph API HTTP requests
using a gauge (also using an atomic int and a gauge function).

Disabled by default, can be enabled with
GRAPH_HTTP_METRICS_DISABLE=false

Add Backend and EducationBackend delegate implementations that measure
execution time on the level of the higher API call operations there
(CreateUser, DeleteUser, ..., CreateSchool, ...), generated using
gowrap.

Disabled by default, can be enabled with
GRAPH_IDENTITY_BACKEND_METRICS_DISABLE=false

Also added a small k6 script to produce some read-only load on the Graph
API, for a casual test of the metrics, as well as k6 in mise.toml.

Make an internal changes to how singular LDAP entry searches work in the LDAP
identity backends:

 * check whether searches for a singular entry returns more than one
   result, in which case a new error TooManyResults is returned, instead
   of leaving that undetected, blindly taking the first result, and
   potentially risking data inconsistencies

Improve the loggers in identity backends by adding attributes for their
request targets (Reva gateway address or LDAP URI, respectively).

Also add a "backend" attribute for all Graph API logs (set to "ldap" or
"cs3"), to help debug potential issues, and remove them from all the logger
debug calls at the beginning of each LDAP-related function as those should
really be part of the logger and set beforehand.

The LDAP identity backend logger also has two new attributes to help
debugging with logs:

 * write (bool): whether write operations are enabled
 * refint (bool): whether refint is enabled or not

Also adds a dedicated counter metric for user password change operations.

Minor campfire improvements:

 * add a constructor func for the CS3 backend

 * add a constructor func for the LDAP backend

 * in the LDAP identity backend, in searchLDAPEntryByFilter (used by all
   search/get public functions), errors that occur when performing LDAP
   SEARCH operations were blindly mapped to a ItemNotFound error,
   instead of being analyzed as it could be caused by a technical error

 * in the requireadmin middleware, add debug logging to explain why a
   request is denied

 * when an LDAP password change fails because the user entry was not
   found in LDAP, we now have a log message that tracks that
2026-09-01 10:57:47 +02:00
Pascal Bleser e8cf677353 chore(graph): disable HTTP or eventhandlers by configuration
In the scope of the broader issue #1312, this PR deals with performing
those changes for the `graph` service, namely to add the ability to
disable the HTTP API or to disable the events API handler by
configuration.

It also adds metrics for the events processing, and tests for the events
processing.

The previous implementation was combining the HTTP server service and
the events consumption, which is why this PR refactors the composition
of those services:

 * the event consumption has been moved into its own service
 * the identity.Backend is created beforehand, and then injected as a
   collaborator in both the HTTP service as well as the event consumer
   service

It also adds metrics, mainly for the event processing.

To encourage re-use in latter implementations and changes, it also
introduces two top-level package changes:

 * internal/eventstest/events_test_helpers: contains a TestBus
   implementation to unit-test event consumers without NATS
 * internal/metricstest/metrics_test_helpers: contains assertion
   functions to test Prometheus metrics
2026-09-01 10:57:26 +02:00
Dominik Schmidt d2bcd3cc9e test(search): comment the root-id assumption, pin the OR fan-out 2026-09-01 10:57:26 +02:00
Dominik Schmidt 71b3104e7b fix(search): keep wildcard drive ids out of completion and pinning 2026-09-01 10:57:26 +02:00
Dominik Schmidt 5f03dcf768 test(search): pin the driveId scope in the parity suite
SCOPE-06..09 cover the completed root id, negation and composition; the cross-space fixture root follows the space-root convention (opaque id = space id).
2026-09-01 10:57:26 +02:00
Dominik Schmidt 25043babd5 perf(search): prune the space fan-out for drive-pinned queries
When top-level AND conjuncts pin the query to a single root
(driveId/RootID restrictions), only that space's index is asked; the
restriction itself stays in the query, so this is purely an
optimization. Conservative by design: any top-level OR, negated or
group-nested restriction leaves the fan-out untouched, searching a
space too many is wasted work while skipping one would be wrong.
Mountpoints are kept for result path mapping.

Costs one extra parse of the query in the service; parsing once and
handing the AST to the engines (which currently re-parse per space) is
a follow-up that changes the engine interface.
2026-09-01 10:57:26 +02:00
Dominik Schmidt 8bd130cc41 feat(search): driveId as a query field
scope: takes an opaque resource id, which is hostile to hand-written
queries. Accept driveId:"<storage$space>" as a regular KQL field
instead: it resolves to the indexed RootID, and a bare drive id is
completed to the root resource id (a space root's opaque id is its
space id). Full root ids pass through untouched.

Combined with path: this gives a readable location scope without any
token stripping: both are plain fields, so they compose with groups,
OR and NOT like everything else.
2026-09-01 10:57:26 +02:00
Dominik Schmidt 394b0fdc45 graph: expose @libre.graph.permissions.actions.allowedValues on driveItems
Opt-in via $select, on GetDriveItem and the root children listing.
2026-09-01 10:21:50 +02:00
Dominik Schmidt 11d8b8a382 graph: expose @libre.graph.tags on driveItems 2026-09-01 10:21:50 +02:00
Dominik Schmidt f0515bb28c graph: expose @libre.graph.me.following on driveItems 2026-09-01 10:21:50 +02:00
Alex Ababii d923bfe1f6 cli: upd check for removing empty dir 2026-09-01 10:03:34 +02:00
Alex Ababii 9822c1ffd4 upd ci config for cliCommands suite 2026-09-01 10:03:34 +02:00
Alex Ababii c742ff6798 fix for trash purge empty dirs for posix 2026-09-01 10:03:34 +02:00
Alex Ababii a2d5ee2bc6 fix revisions purge cli command for posix 2026-09-01 10:03:34 +02:00
Alex Ababii 7aa5b467f6 cli: fix for cleanup call 2026-09-01 10:03:34 +02:00
Alex Ababii 983c836653 cli: restore single item from trash bin, listing on empty thrash bin 2026-09-01 10:03:34 +02:00
Dominik Schmidt 7d4c6d6524 Merge pull request #3424 from opencloud-eu/feat/search-scope
feat(search): scope searches to a drive via the driveId field
2026-09-01 09:36:20 +02:00
Jörn Friedrich Dreyer a249fa5f74 Merge pull request #2662 from dschmidt/docs/adr-reflection-based-search-mapping
docs(adr): unified search index mapping
2026-09-01 09:11:09 +02:00
Dominik Schmidt 5e4f124e78 fix(search): find the language endpoint on tika 4, keep the legacy path 2026-09-01 09:09:55 +02:00
Dominik Schmidt a1e8aa783d fix(search): reject NaN coordinates and non-positive bitrates 2026-09-01 09:09:55 +02:00
Dominik Schmidt ae9ec4ce70 fix(search): harden facet value parsing 2026-09-01 09:09:55 +02:00
Dominik Schmidt c03b9c7229 fix(search): gate the audio facet inside getAudio, keep facets across meta entries 2026-09-01 09:09:55 +02:00
Dominik Schmidt 77bb974f9b test(search): run acceptance and CI against the released tika 4.0.0
Both tika pins (local compose and the CI service) point at apache/tika:4.0.0-full; the search README links tika version-less.
2026-09-01 09:09:55 +02:00
Dominik Schmidt 1be01ae50c fix(search): read the tika 4 content key, keep the legacy one
Tika 4.0.0 renamed the meta prefix from X-TIKA: to tk:.
2026-09-01 09:09:55 +02:00
Dominik Schmidt 2b4a6fd739 feat(search): populate audio and gps-altitude facets from tika, drop the audio-only gate 2026-09-01 09:09:55 +02:00
Dominik Schmidt 7e0f536b83 refactor(search): split the tika extractor and its tests into per-facet files 2026-09-01 09:09:55 +02:00
Jörn Friedrich Dreyer 49ebe072f1 Merge pull request #3426 from opencloud-eu/dependabot/go_modules/go.opentelemetry.io/otel-1.46.0
build(deps): bump go.opentelemetry.io/otel from 1.45.0 to 1.46.0
2026-09-01 08:25:05 +02:00
Jörn Friedrich Dreyer 8028e3e310 Merge pull request #3435 from fschade/test-search-cjk-parity
test(search): cover chinese search in the parity suite
2026-09-01 08:24:34 +02:00
Dominik Schmidt 7d75b98c69 Merge pull request #3423 from opencloud-eu/test/parity-readme-staleness
test(search): fail the parity suite when the committed matrix is stale
2026-09-01 08:24:15 +02:00
Florian Schade 9e8403818c test(search): cover chinese search in the parity suite 2026-09-01 07:46:03 +02:00
Dominik Schmidt ef41429838 test(search): rename the update flag to UPDATE_SEARCH_PARITY_MATRIX 2026-09-01 07:35:15 +02:00
Dominik Schmidt 7bf64e329c docs(adr): apply review wording suggestion 2026-09-01 07:26:32 +02:00
Dominik Schmidt 268a4497b4 Merge pull request #3197 from opencloud-eu/feat/search-schema-change-handling
feat(search): check the index schema on startup and refuse breaking changes
2026-09-01 07:17:29 +02:00
Florian Schade b4893baa81 Merge pull request #3379 from opencloud-eu/ci/search-opensearch-pipelines
ci: run search API and e2e suites against OpenSearch
2026-09-01 06:56:07 +02:00
opencloudeu ebf9566886 [tx] updated from transifex 2026-08-31 23:20:06 +00:00
Dominik Schmidt b149316349 docs(adr): keep the facet reachability gap out of the problem list 2026-08-31 21:22:22 +02:00
Dominik Schmidt c6ddc13055 docs(adr): date the context section 2026-08-31 21:20:54 +02:00
Dominik Schmidt 65ea519099 docs(adr): update to the implemented state 2026-08-31 21:16:43 +02:00
Dominik Schmidt 2041fdc259 test(search): comment the root-id assumption, pin the OR fan-out 2026-08-31 21:14:58 +02:00
Dominik Schmidt 8e68a34b8f fix(search): keep wildcard drive ids out of completion and pinning 2026-08-31 21:14:04 +02:00
Jörn Friedrich Dreyer 8f44f6eb42 Merge pull request #3429 from opencloud-eu/dependabot/go_modules/google.golang.org/grpc-1.83.2
build(deps): bump google.golang.org/grpc from 1.83.1 to 1.83.2
2026-08-31 18:10:47 +02:00
Jörn Friedrich Dreyer 0f9c6dc1ec Merge pull request #3425 from pbleser-oc/chore/mise-go-1.25.9
chore(mise): upgrade go in mise to 1.25.9 after 578861b473
2026-08-31 18:07:57 +02:00
dependabot[bot] 5bfbfd8ff7 build(deps): bump google.golang.org/grpc from 1.83.1 to 1.83.2
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.83.1 to 1.83.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.83.1...v1.83.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:45:06 +00:00
dependabot[bot] fe1e7632fa build(deps): bump go.opentelemetry.io/otel from 1.45.0 to 1.46.0
Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.45.0 to 1.46.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.45.0...v1.46.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:44:13 +00:00
Pascal Bleser 15b7fad504 chore(mise): upgrade go in mise to 1.25.9 after 578861b473
* upgrade the go version in mise.toml to 1.25.9 because dependabot
   upgraded go to 1.25.9 in go.mod

 * mise: upgrade dlv from 1.27.0 to 1.27.1:
   https://github.com/go-delve/delve/releases/tag/v1.27.1
2026-08-31 15:52:30 +02:00
v.scharf 5c97014cd2 ci: run search API and e2e suites against OpenSearch 2026-08-31 15:29:37 +02:00
Dominik Schmidt 774c732f52 test(search): pin the driveId scope in the parity suite
SCOPE-06..09 cover the completed root id, negation and composition; the cross-space fixture root follows the space-root convention (opaque id = space id).
2026-08-31 15:23:25 +02:00
Dominik Schmidt 95f175fe10 perf(search): prune the space fan-out for drive-pinned queries
When top-level AND conjuncts pin the query to a single root
(driveId/RootID restrictions), only that space's index is asked; the
restriction itself stays in the query, so this is purely an
optimization. Conservative by design: any top-level OR, negated or
group-nested restriction leaves the fan-out untouched, searching a
space too many is wasted work while skipping one would be wrong.
Mountpoints are kept for result path mapping.

Costs one extra parse of the query in the service; parsing once and
handing the AST to the engines (which currently re-parse per space) is
a follow-up that changes the engine interface.
2026-08-31 15:23:25 +02:00
Dominik Schmidt 094baab7be feat(search): driveId as a query field
scope: takes an opaque resource id, which is hostile to hand-written
queries. Accept driveId:"<storage$space>" as a regular KQL field
instead: it resolves to the indexed RootID, and a bare drive id is
completed to the root resource id (a space root's opaque id is its
space id). Full root ids pass through untouched.

Combined with path: this gives a readable location scope without any
token stripping: both are plain fields, so they compose with groups,
OR and NOT like everything else.
2026-08-31 15:23:25 +02:00
Dominik Schmidt feb13d426b test(search): name the update flag in the generated header 2026-08-31 15:16:15 +02:00
Dominik Schmidt b1533a3135 test(search): write the matrix only with UPDATE_MATRIX
A plain run compares and fails on a stale README without touching the working tree; UPDATE_MATRIX=true regenerates it.
2026-08-31 15:16:15 +02:00
Dominik Schmidt 874d8fdac2 test(search): fail the parity suite when the committed matrix is stale
The suite already regenerates README.md on every run; now a run whose content differs from the committed file also fails, so CI catches a stale matrix. The fresh content is on disk after the run, committing it is the fix.
2026-08-31 15:16:15 +02:00
Dominik Schmidt 0e2e2876f4 test(search): a golden diff says whether it needs a SchemaVersion bump
The failure runs the classifier on golden vs generated: additive means regenerate only, breaking means bump too.
2026-08-31 15:15:18 +02:00
Dominik Schmidt e11ce95d95 test(search): golden for the generated OpenSearch index definition
Pins the shipped schema as a reviewable diff; regenerate with UPDATE_GOLDEN=1.
2026-08-31 15:12:54 +02:00
Dominik Schmidt 7493f189a0 chore(search): adapt tests and golden to the v4 base
The refuse specs use registered analyzers (fulltext is gone), the golden regenerates via UPDATE_GOLDEN, MappingGetResp grew an accessor, and the parity suite passes the new NewBackend signature.
2026-08-31 15:12:54 +02:00
Dominik Schmidt 53fa66f120 chore(search): follow the rebased base
The opensearch-go bump renamed the mapping-get accessor, the schema check
takes a context and a logger now, and the golden bleve mapping carries the
word-broken Name and Title.
2026-08-31 15:12:54 +02:00
Dominik Schmidt 519dd112ea review: note SchemaVersion bump on bleve marshaling drift in golden test 2026-08-31 15:12:54 +02:00
Dominik Schmidt 414247d6c4 review: use any over interface{}, clone slice in AddBreaking 2026-08-31 15:12:54 +02:00
Dominik Schmidt 7ff2643d23 refactor(search): share SortedUnionKeys + Classification.AddBreaking
- export mapping.SortedUnionKeys and reuse it in bleve.compareKeysExcept
  instead of a copied union-of-keys block
- add Classification.AddBreaking to fold engine-specific breaking reasons and
  force the verdict, replacing the identical block in the bleve and opensearch
  Classify paths
2026-08-31 15:12:54 +02:00
Dominik Schmidt a0521f0260 review: trim verbose comments, cover Reconcile, table-driven Classify tests
- shorten the multi-line doc comments flagged as too verbose
- add reconcile_test.go: direct unit tests for Reconcile incl. the
  persisted-but-errored and classify-error branches (previously only
  reached indirectly through the engine integration tests)
- convert the 11 near-identical Classify It blocks to a DescribeTable
2026-08-31 15:12:54 +02:00
Dominik Schmidt 6b07a0d9dc chore(search): tighten the schema-reconcile comments 2026-08-31 15:12:54 +02:00
Dominik Schmidt dc863c64c6 refactor(search): single-source the new-index log message 2026-08-31 15:12:54 +02:00
Dominik Schmidt a8ffe91c79 feat(search): log a reindex hint when a fresh search index is created 2026-08-31 15:12:54 +02:00
Dominik Schmidt fec7536617 refactor(search): warn on a persisted additive change even when the reopen fails 2026-08-31 15:12:54 +02:00
Dominik Schmidt e8c93359b1 refactor(search): route schema verdict handling through a shared mapping.Reconcile 2026-08-31 15:12:54 +02:00
Dominik Schmidt 04c37d12d2 refactor(search): extract opensearch.NewClient out of server startup 2026-08-31 15:12:54 +02:00
Dominik Schmidt 9755728d43 refactor(search): harden the index-diff helpers against unset input 2026-08-31 15:12:54 +02:00
Dominik Schmidt 37eed7c321 refactor(search): only enforce analysis settings, tolerate operational drift 2026-08-31 15:12:54 +02:00
Dominik Schmidt a6bfb61935 refactor(search): make the breaking-schema error developer-facing 2026-08-31 15:12:54 +02:00
Dominik Schmidt 5abe2ccb26 fix(search): list the schema mismatch reasons on separate lines 2026-08-31 15:12:54 +02:00
Dominik Schmidt e9347998b7 fix(search): name the service to stop in the schema mismatch error 2026-08-31 15:12:54 +02:00
Dominik Schmidt 33299afbaa fix(search): address max-review findings
- the additive warnings advertise --all-spaces --force-rescan; a plain
  walk skips unchanged documents and never backfills the new fields
- Apply checks index existence first again, so a pre-provisioned index
  needs no create privilege and odd create-error shapes (string error
  bodies, cluster blocks) cannot fail a healthy startup; Create on 404
  keeps the typed already-exists swallow as the creation-race backstop
- number_of_replicas drift is not breaking, it is runtime-tunable and
  needs no rebuild
- bleve returns the classification alongside post-persist errors and
  the server warns before the error check, so the one-time additive
  warning is not lost when close or reopen fails
- a golden fixture pins the marshaled bleve mapping so a dependency
  bump that changes marshaling fails in CI instead of refusing every
  installation in the field
2026-08-31 15:12:54 +02:00
Dominik Schmidt 451e893d7d chore(search): tighten doc comments 2026-08-31 15:12:54 +02:00
Dominik Schmidt b67811ab3c chore(search): warn on additive opensearch changes and name the exact delete step
Addresses the two Copilot review comments on the PR: the additive
opensearch log now matches the bleve warning (level and re-index hint),
and the refuse message spells out how to delete the index per engine
(DELETE /<name> vs removing the bleve directory).
2026-08-31 15:12:54 +02:00
Dominik Schmidt 09ea7e5f15 chore(search): mention the impact of disabling search in the refuse message 2026-08-31 15:12:54 +02:00
Dominik Schmidt 1c130c448c chore(search): drop the changelog entry 2026-08-31 15:12:54 +02:00
Dominik Schmidt 03dfabddbb feat(search): check the index schema on startup and refuse breaking changes
Both engines now diff the stored/live index schema against the schema
generated from code when the service starts. A shared recursive
classifier in the mapping package is the single oracle:

- equal: start normally.
- additive (new fields without any indexed data): applied in place.
  OpenSearch gets a PUT _mapping with the full code properties, bleve
  persists the code mapping into the index (SetInternal + reopen) so
  the new fields are properly typed immediately and later startups
  classify equal. A startup warning lists the new fields because
  documents indexed before the upgrade lack them until re-indexed.
- breaking (changed definitions or analyzers, removed or renamed
  fields, or new fields that already contain data of unknown form):
  refuse to start with an error describing the rebuild procedure
  (delete the index, start, run "opencloud search index --all-spaces")
  and the OC_EXCLUDE_RUN_SERVICES=search escape hatch.

PUT _mapping is deliberately only the apply mechanism, never the
judge: its merge semantics cannot see removals or renames and it
accepts in-place updatable param changes with an ack. bleve
additionally checks idx.Fields() so previously dynamically indexed
data (which leaves no schema trace in bleve) is caught, matching by
exact name and by path prefix.

While at it: the OpenSearch startup check runs with a real,
minute-bounded context instead of context.TODO(), bleve indexes are
opened with a 5s bolt_timeout so a second process fails fast instead
of hanging on the file lock, and the reversed errors.Is arguments in
bleve.NewIndex were fixed.

https://github.com/opencloud-eu/opencloud/issues/3092
2026-08-31 15:12:54 +02:00
Dominik Schmidt d38fbc8e52 Merge pull request #3345 from opencloud-eu/refactor/search-mapping
refactor: reflection-based search mapping + location geopoint
2026-08-31 15:12:49 +02:00
Benedikt Kulmann 109e9555c8 chore(policies): disable gRPC or event handlers by configuration + metrics (#3287)
* add the ability to disable the gRPC API handler
   (POLICIES_GRPC_DISABLED)

 * add the ability to disable the Events API handler
   (POLICIES_EVENTS_DISABLED)

 * add metrics

 * add support for specifying rego files via the environment varirable
   POLICIES_ENGINE_FILES

 * for file paths specified in yaml or in POLICIES_ENGINE_FILES, support
   'config:' and 'data:' path prefixes

 * fix typos in the documentation, and try to make it more clear

 * add metrics to the documentation

 * add a section for testing in the documentation

 * introduces a new top-level package pkg/metrics/ with utilities for
   metrics that are backported from the groupware branch, with unit
   tests
2026-08-31 14:58:52 +02:00
Dominik Schmidt 1bf813068e docs(search): version-free index example in the README too 2026-08-31 14:17:29 +02:00
Dominik Schmidt aa2f7da029 docs(search): the unversioned index is the one up to 7.4 2026-08-31 14:15:17 +02:00
Dominik Schmidt 0188b18d2b docs(search): everything but the current index generation can go 2026-08-31 14:14:19 +02:00
Dominik Schmidt 73bfc1cf48 docs(search): just the reindex step 2026-08-31 14:12:52 +02:00
Dominik Schmidt 3a7d6ca54b docs(search): no index names in the migration steps, admins never type them 2026-08-31 14:12:38 +02:00
Dominik Schmidt 5d3c67b587 docs(search): the only migration path is a reindex
Copying the old index over misses the search sibling fields, copied documents would be unfindable.
2026-08-31 14:06:12 +02:00
Dominik Schmidt 2458d51ed1 feat(search): favorites are opaque user ids, no lowercase sibling
Same rule as the other ids; removing the sibling later would take another schema generation.
2026-08-31 13:50:36 +02:00
Dominik Schmidt d4f1ae910c fix(search): scope the OpenSearch purge to the resource's space
The delete-by-query matched the bare Path, so a purge could take same-path documents in other spaces with it; bleve was already RootID-scoped. Pinned as rootscope-04.
2026-08-31 13:50:36 +02:00
Dominik Schmidt 5abef94482 test(search): regenerate the parity matrix for the mediatype:file rows 2026-08-31 13:45:42 +02:00
Dominik Schmidt e14bef7ef3 chore(search): version-free schema examples in docs strings 2026-08-31 13:44:47 +02:00
Dominik Schmidt 98bdce156b chore: drop the duplicated vendor entry for analysis/char/regexp 2026-08-31 13:44:47 +02:00
Dominik Schmidt dd6b3548f9 fix(search): tighten the generated search siblings
The OpenSearch _lowercase siblings are search-only like bleve's (no doc_values, own map instance instead of aliasing the base), and the dead Path_words field is gone: _words exists for keyword fields only, as SearchSiblings declares.
2026-08-31 13:44:47 +02:00
Dominik Schmidt 9e8bb474b4 fix(search): compose the mediatype:file negation correctly
The file expansion is grouped again so its NOT stays atomic next to other terms (OpenSearch turned 'mediatype:file OR x' into '(NOT dir) AND x'), and the bleve compiler no longer re-keys resolved groups (a negated mediatype group targeted the raw 'mediatype' field and matched everything). Pinned as MEDIATYPE-07..10.
2026-08-31 13:43:19 +02:00
Dominik Schmidt 9503884472 docs(search): migration notes for schema v4
Also make the reindex copy safe to run after the service already indexed (op_type create, conflicts proceed).
2026-08-31 13:40:43 +02:00
Dominik Schmidt 2383bcddcc test(search): leave behavior to the parity suite
The engine suites keep what is engine-specific (index setup, health, purge-space batching); every behavior answer lives in the parity suite once. The shared test client learns IndicesCount, and FIELDS-15 pins the Size/Type gate for number queries.
2026-08-31 13:40:43 +02:00
Dominik Schmidt b92d671c02 chore(search): drop the orphaned hand-written v3 mapping leftovers 2026-08-31 13:40:43 +02:00
Dominik Schmidt 523d377a08 test(search): carry the parity suite over to the rebased semantics
Typed Mtime fixtures, VersionedIndexName, and audio.artist matches case-insensitively now (facets search case-insensitively by default).
2026-08-31 13:40:43 +02:00
Dominik Schmidt d5872aa851 fix(search): match fulltext phrases in order on bleve
Content:"reports monthly" must not match 'monthly reports'; the phrase runs on the analyzed field like on OpenSearch.
2026-08-31 13:40:43 +02:00
Dominik Schmidt 283de0d29a feat(search): adopt the parity-pinned query semantics on the sibling routing
From #3408: hidden takes bool words only, type categories map to the stored value in the shared pass, ? counts as a wildcard, a non-suffix wildcard on a word-broken field forgives the extension, = matches the whole value on the lowercased sibling, paths lose their trailing slash. Dead compiler helpers removed.
2026-08-31 13:40:42 +02:00
Dominik Schmidt ba210232ab feat(search): analyze Content by words on both engines, no stemming
Adopts the parity-pinned semantics from #3408: 'report' does not match 'reports'; the porter fulltext analyzer is gone.
2026-08-31 13:40:42 +02:00
Dominik Schmidt b5693d7058 chore(search): one schema generation, bump it to 4
The hand-written v3 of the interim mapping is taken; both engines derive index name and directory from search.SchemaVersion.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 903ef4521b feat(search): search facets by word too, whole values only by opt-out
SharePoint's default for a text property is word breaking, so ours is too:
every keyword field gets the _words sibling unless it opts out with
NoWordBreaker, which now carries SharePoint's polarity as well. Artist,
album, camera model and the other facets match by word like name and title;
tags and favorites stay one label, ids, paths and the mime type one value.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 96e728901e feat(search): search every keyword field case-insensitively by default
KQL searches case-insensitively, so every keyword field gets its lowercased
search sibling unless it opts out: ids are opaque, paths are POSIX, the mime
type is normalized already. That takes the facets along, artist or camera
model match regardless of case, while the case-preserved base still answers
and aggregates. Which siblings a field carries is decided once, from the
struct and the overrides, and the renderers, the document writer and the
query lowering all read it from there.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 6b07ea745c feat(search): split names and titles into words on both engines
A single word finds the names and titles that contain it, `report` finds
Report.txt, on bleve as well as on OpenSearch, which so far only did it by
accident of its dynamic mapping. Modelled like SharePoint's NoWordBreaker:
a keyword field is one whole value unless the override switches that off,
which adds a search-only _words sibling next to _lowercase, analyzed into
lowercased words (a dot is a word boundary, no stemming). The base stays the
whole value for returning and aggregating, wildcards and whole values keep
using _lowercase. Quotes do not change the meaning, a phrase is a phrase
either way, and there is no exact-match operator yet.
2026-08-31 13:40:42 +02:00
Dominik Schmidt e64692ce9e test(search): re-apply opensearch backend coverage under ginkgo 2026-08-31 13:40:42 +02:00
Dominik Schmidt 9c46713a65 refactor(search): make paths case-sensitive and scope searches at query level
Paths act as references (location scoping, deep links): /Foo and /foo
are distinct siblings, so path: matching must be exact. Case-insensitive
folder discovery is served by name: and its lowercase sibling. This also
matches bleve on main, where path queries have always been
case-sensitive.

Dropping the sibling removes its biggest maintenance cost: Path is the
one mutable sibling field, a move rewrites the paths of a whole subtree
and the OpenSearch move script had to rebuild Path_lowercase alongside
the base field.

With paths case-sensitive, the ref path scope moves into the query
itself: bleve as a term/prefix disjunction on the keyword Path,
OpenSearch as a term filter on its path_hierarchy tokens. The
post-filter that used to drop out-of-scope hits after the query ran is
gone; totals and paging now respect the scope instead of being computed
over the whole space, and a wrong-cased scope simply matches nothing.
2026-08-31 13:40:42 +02:00
Dominik Schmidt f6739f552d fix(search): bind a leading NOT to the term right after it, on both backends
A leading NOT next to an operator was miscompiled: the bleve compiler left the consumed term in `next`, so `NOT x AND y` dropped `y` and produced a self-contradicting clause; the OpenSearch transpiler checked nextOp==AND before prevOp==NOT, so the negated term landed in `must` instead of `must_not`. NOT is unary and binds to the node directly after it regardless of what follows. This also fixes `mediatype:file AND <term>` (the web Files filter) at the root, so the earlier mediatype:file group workaround is dropped.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 5d9a255b16 test(search): port fieldindex, mimetype and normalize tests to ginkgo 2026-08-31 13:40:42 +02:00
Dominik Schmidt 17144d4791 fix(search): keep OpenSearch path queries as unanalyzed term queries
A path value with spaces went through a match_phrase query, which analyzes
the query with the path_hierarchy analyzer; the resulting "." prefix token
matches every document in the space, breaking descendant matching and the
stale-path check after a move.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 0284224787 refactor(search): anchor the OpenSearch move prefix replace to the path start
The move script rewrote Path/Path_lowercase with painless String.replace, which replaces every occurrence of the old path, not just the leading prefix. OpenCloud paths are ./-prefixed so the full old path only occurs at the start and the result is byte-identical, but startsWith + substring makes the prefix-only intent explicit and robust to any path format. Not a live bug fix, a hardening.
2026-08-31 13:40:42 +02:00
Dominik Schmidt fc41995a47 fix(search): validate CaseInsensitive against the effective field type
The guard only rejected CaseInsensitive when a non-keyword/path Type was set explicitly. With no Type, isCasedType treated the field as cased, so CaseInsensitive on an inferred numeric/bool/datetime field passed validation but produced no _lowercase sibling, and the query would silently match nothing. Validate now falls back to the inferred Go type.
2026-08-31 13:40:42 +02:00
Dominik Schmidt c94059fb85 fix(search): keep mediatype:file atomic so it composes with other terms
mediatype:file expands to a NOT restriction. Spliced inline as `NOT MimeType:httpd/unix-directory`, the bleve compiler's NOT branch left a stale operand, so `mediatype:file AND name:x` dropped `name:x` and matched nothing (the web Files filter). It is now wrapped in a group so the negation stays atomic; verified fixing both bleve and OpenSearch.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 69c517a8b2 fix(search): nest json-tagged embedded structs instead of flattening them
resolveField marked every anonymous field embedded, so walkFields (mapping, field index, validate) and fillStruct (deserializer) flattened a json-tagged embedded struct, while conversions.To/encoding/json on the write path nests it under the tag, mapping and deserializing it at the wrong path. An anonymous field is now embedded only without a json tag name, matching encoding/json; fillStruct also recurses into a value nested struct. No current type has a tagged embedded struct, so runtime behavior is unchanged; this hardens the reflection walker.
2026-08-31 13:40:42 +02:00
Dominik Schmidt dad99e85b2 fix(search): make mediatype categories and MIME types case-insensitive
mediatype:Folder / mediatype:IMAGE resolved to a literal MimeType search and matched nothing because Expand switched on the raw value. The value is now lowercased in the lowering pass, so categories and literal MIME types match regardless of case, consistently on both backends.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 8cb973d695 test(search): cover mediatype and direct MimeType search on both backends
Adds bleve and OpenSearch coverage for category (image), literal MIME (image/svg+xml, with + and /), and raw MimeType: queries. Documents why MimeType skips the bleve escaper: it is not a bug, bleve treats / and + as literals mid-term, so a literal MIME still matches exactly while the category wildcard image/* keeps its *.
2026-08-31 13:40:42 +02:00
Dominik Schmidt c680a90e49 fix(search): reject CaseInsensitive on non-keyword/path fields
CaseInsensitive routes queries to a <field>_lowercase sibling that is only generated for keyword/path fields, so marking any other type CaseInsensitive would silently match nothing. Validate now rejects it up front.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 1bf15599f7 fix(search): write a consistent empty _lowercase sibling for empty arrays
The []any branch skipped the sibling for an empty array while the []string branch wrote an empty one; both now write it, matching the base field.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 727ca92afe fix(search): review fixes for path AND-term and content wildcard
bleve compiled a path restriction to a DisjunctionQuery, which mapBinary redistributes as an OR-chain, so `path:/Foo AND name:bar` matched the folder itself unconditionally. It is now a BooleanQuery (should: folder OR descendants), which mapBinary keeps atomic under an enclosing AND.

The OpenSearch full-text branch ran before the wildcard check, so `content:foo*` degraded to a phrase match and diverged from bleve; the wildcard check now comes first.

Adds the missing coverage the review flagged: path AND term, content wildcard, case-insensitive tags (the array sibling branch), and a spaced path with descendants on OpenSearch.
2026-08-31 13:40:42 +02:00
Dominik Schmidt e3c45dd1d0 fix(search): analyze OpenSearch full-text queries, stem Content like bleve
Single-term `content:` built an unanalyzed term query, so once this branch dropped the blanket query-value lowercasing, `content:Foo` missed on OpenSearch (bleve was unaffected, its query analyzes). Fielded full-text queries now use a match query. OpenSearch `Content` also gets a porter stemming analyzer (it used the default standard analyzer and never stemmed), so full-text search matches bleve on both case and stemming.
2026-08-31 13:40:42 +02:00
Dominik Schmidt ec58861e4e feat(search): per-field case-insensitive search via _lowercase siblings
Keyword and path fields always index their case-preserved base and, when CaseInsensitive is set, an additional <field>_lowercase sibling used only for matching. The KQL lowering marks a restriction case-insensitive; each backend searches the sibling and lowercases the query value the same way the sibling is precomputed at index time (Go strings.ToLower on both sides, so non-ASCII stays consistent).

Search always returns the case-preserved base, so the sibling never has to be read back. In bleve it is indexed but not stored, kept out of _all, and without doc values. In OpenSearch it deliberately stays in _source: excluding it would make every update-by-query script rebuild all siblings from the document via painless toLowerCase, which lowercases differently than Go and would drift from the query side. Keeping it in _source avoids that, and a lowercased copy of a name or path is negligible disk in a cluster.

The OpenSearch move script keeps the base and its sibling in sync by swapping the moved prefix in Path_lowercase and setting Name_lowercase from Go-lowercased params, so case-insensitive search still finds a file after it moves (previously the sibling went stale). bleve re-indexes the whole document on move/delete/restore, so its siblings stay fresh for free.

This also repairs OpenSearch path search (the query value was no longer folded to lowercase, so path:<Foo> returned nothing) and makes bleve path queries match a folder and its descendants like OpenSearch's path_hierarchy. The Path base stays case-preserved so the move/delete descendant update (an exact TermQuery on Path) matches mixed-case folders.
2026-08-31 13:40:42 +02:00
Dominik Schmidt fb22dd81a4 refactor(search): run the OpenSearch backend on the shared lowering pass
KQLToOpenSearchBoolQuery runs query.Normalize, then only value lowercasing stays
backend-specific; remapKey and unfoldValue are gone.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 1cba974c1d refactor(search): run the bleve backend on the shared lowering pass
The bleve Creator runs query.Normalize before compiling; the compiler consumes a
canonical AST with no field resolution or media-type special-casing.
2026-08-31 13:40:42 +02:00
Dominik Schmidt bf7d04f7a1 feat(search): add shared KQL lowering pass
query.Normalize resolves field names (query.ResolveField, from the derived
index + a small alias overlay) and expands media-type restrictions
(mimetype.Expand) once, between parse and backend compilation.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 45a8096617 feat(search): derive a case-insensitive field-name index from the resource struct
mapping.FieldNameIndex walks the struct and maps a lowercased field path to the
real field name, including nested facet sub-fields. Backend-neutral.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 65f615d927 refactor(kql): move parse/validation errors into pkg/kql
The KQL parser produced its own validation errors but imported them from the
search service's query package. Move them into pkg/kql and let the search
backend consume kql.IsValidationError, so the parser stops depending on a
service package.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 40a2f14600 fix(search): preserve query value case for case-sensitive fields on OpenSearch
OpenSearch lowercased every KQL query value, so exact-match queries on
case-preserved keyword fields (facet values, ids) never matched their stored
token. Fold the value only for fields with a lowercasing analyzer, mirroring the
bleve backend. The field set is derived once in search.LowercaseValueFields and
shared by both backends (bleve's local buildLowercaseFields is dropped).
2026-08-31 13:40:42 +02:00
Dominik Schmidt 7d55849900 feat(search): version the search index by schema version
Both backends carry a shared search.SchemaVersion in the index name
(OpenSearch <base>-vN) and data path (bleve-vN). A breaking schema change
bumps the version so the service builds a fresh index instead of colliding
with the incompatible previous one; the old index is left in place.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 450f380198 fix(search): type mtime as a date 2026-08-31 13:40:42 +02:00
Dominik Schmidt 7cc144a9c3 test(search): set Mtime on opensearch folder and root fixtures
The Mtime field is mapped as an OpenSearch `date`, which rejects an
empty value with `mapper_parsing_exception: cannot parse empty date`.
The folder and root fixtures had no Mtime, so serializing them to
`"Mtime": ""` made TestEngine_Purge/purge_resource_trees fail when the
document was indexed. Give both a valid RFC3339 Mtime, matching the
file fixture.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 6695f99c90 test(search): convert mapping package tests to ginkgo
New package, so use the repo's standard test framework.
2026-08-31 13:40:42 +02:00
Dominik Schmidt c6c36212f5 test(search): convert bleve geo/mtime tests to ginkgo
The package's engine suite is ginkgo; these new tests were plain.
2026-08-31 13:40:42 +02:00
Dominik Schmidt c384322d94 test(search): use RFC3339 Mtime in opensearch fixture
Mtime is now a date field; the fixture's Go-format string fails
OpenSearch date parsing.
2026-08-31 13:40:42 +02:00
Dominik Schmidt 29cfdb01a0 feat(search): index Location as a geopoint on both backends
Add a TypeGeopoint field type. The libregraph Location facet is kept as an
object (retrieval / numeric queries) and a sibling <name>_geopoint field
carries the {lat,lon} form for geo-distance / bbox / polygon queries,
uniform across bleve and OpenSearch via the shared mapping. PrepareForIndex
splices the sibling in at write time.
2026-08-31 13:40:42 +02:00
Dominik Schmidt e78cad1cbe refactor: reflection-based search mapping
Build the bleve and OpenSearch index mappings from the Go struct via
reflection (json tags + per-field overrides) instead of hand-rolled
mappings and hit deserializers. New mapping package: BleveBuildMapping,
OpenSearchBuildMapping, Deserialize[T], PrepareForIndex; field decoding is
fail-soft. Mtime is typed as a date so mtime ranges are chronological on
both backends. Route CS3 facet parsing through mapping.DeserializeStringMap.

The any-valued (bleve hit) and string-valued (CS3 metadata) deserializers
share one generic fillStruct walker with a per-value setLeaf callback.
2026-08-31 13:40:42 +02:00
Jörn Friedrich Dreyer 0becbb8212 Merge pull request #3419 from fredrikblau/fix/3413-oidc-nil-provider
fix(middleware): reject empty OIDC providers safely
2026-08-31 13:37:09 +02:00
Florian Schade a9da258dcc Merge pull request #3408 from fschade/fix-search-field-matching
fix(search): make openSearch and bleve behave the same
2026-08-31 13:24:49 +02:00
Florian Schade 1bde3d14bb fix(search): bring back rebase removals 2026-08-31 11:57:19 +02:00
Pascal Bleser 8821053ff8 chore(policies): disable gRPC or event handlers by configuration + metrics
* add the ability to disable the gRPC API handler
   (POLICIES_GRPC_DISABLED)

 * add the ability to disable the Events API handler
   (POLICIES_EVENTS_DISABLED)

 * add metrics

 * add support for specifying rego files via the environment varirable
   POLICIES_ENGINE_FILES

 * for file paths specified in yaml or in POLICIES_ENGINE_FILES, support
   'config:' and 'data:' path prefixes

 * fix typos in the documentation, and try to make it more clear

 * add metrics to the documentation

 * add a section for testing in the documentation

 * introduces a new top-level package pkg/metrics/ with utilities for
   metrics that are backported from the groupware branch, with unit
   tests
2026-08-31 11:54:32 +02:00
Florian Schade 5c153ff74a test(search): drop the engine overrides, both engines answer alike 2026-08-31 11:15:03 +02:00
Florian Schade 0106a700c2 test(search): follow the versioned index and pin the equals operator 2026-08-31 10:58:13 +02:00
dependabot[bot] 4639f8f7c6 build(deps): bump github.com/blevesearch/bleve/v2 from 2.6.0 to 2.6.1
Bumps [github.com/blevesearch/bleve/v2](https://github.com/blevesearch/bleve) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/blevesearch/bleve/releases)
- [Commits](https://github.com/blevesearch/bleve/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: github.com/blevesearch/bleve/v2
  dependency-version: 2.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:48:06 +02:00
Florian Schade bd4287762e fix(search): answer the same on both engines
both engines now agree on names, titles, tags, paths, types, sizes, dates,
hidden flags, facet values and wildcards. quotes only delimit phrases and
the equals operator matches the whole field value, following the kql spec.
the index name carries a generation so a changed mapping starts on a fresh
index, MIGRATION.md says how to fill it.
2026-08-31 10:44:03 +02:00
Florian Schade ffbfe171c3 Merge pull request #3418 from opencloud-eu/test/search-parity-suite
test(search): engine parity suite for bleve and opensearch
2026-08-31 10:22:25 +02:00
dependabot[bot] 1bd59d9210 build(deps): bump github.com/sirupsen/logrus from 1.10.0 to 1.10.1
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.10.0 to 1.10.1.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.10.0...v1.10.1)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:00:26 +02:00
opencloudeu 32a35f8653 [tx] updated from transifex 2026-08-30 23:17:11 +00:00
amir 0a3378eef8 fix(middleware): reject empty OIDC providers safely 2026-08-29 19:29:18 +03:30
opencloudeu dee8d7b0f7 [tx] updated from transifex 2026-08-28 23:17:11 +00:00
Dominik Schmidt fef2df8671 chore(codacy): exclude the generated search parity matrix
Markdown lint has nothing to say about a table the test suite writes.
2026-08-28 19:56:57 +02:00
Florian Schade 18759b0183 test(search): cover a deleted space on both engines 2026-08-28 19:38:09 +02:00
Dominik Schmidt 545aa114e8 test(search): let the parity suite replace the per-engine backend suites
Everything the bleve and OpenSearch backend suites checked now runs against
both engines in services/search/pkg/parity. The two cases without a twin
there join it (a facet value keeps its case, batches stay apart), the one
thing only OpenSearch can do, refuse an unhealthy cluster, stays in its
package.
2026-08-28 19:38:01 +02:00
Dominik Schmidt 889483d69c test(search): document where the engines still differ from the parity suite
The expectations are the desired state; where bleve or OpenSearch answer
differently on main today the case records that answer as an engine
override, so the suite is green and the README lists every divergence as
known.
2026-08-28 19:38:01 +02:00
Dominik Schmidt 1668e23f80 test(search): port the parity suite to ginkgo
Every case is one spec per engine, so one engine failing leaves the other's
answer in the matrix. Query groups share their engines through an Ordered
container, operations build their own. The README is assembled from report
entries in ReportAfterSuite, which also works across ginkgo -p processes, and
is only written once every planned row got an answer from every engine.

A case can carry, per engine, what that engine answers today where it still
differs from the expectation (engineOverrides): the spec asserts the override,
the README marks the row as known, and the override fails once the engine
answers as expected.
2026-08-28 19:37:45 +02:00
Dominik Schmidt c24bfb2e41 test(search): keep the OpenSearch testcontainer between runs on request
KEEP_TEST_CONTAINER=true skips the terminate; the next run picks the
container up by name instead of booting a fresh one.
2026-08-28 19:20:58 +02:00
Dominik Schmidt a03a294a12 test(search): make the OpenSearch testcontainer start reliably
Cold boots take well over the 5s startup timeout, and a full host disk
tripped the flood-stage create-index block mid-run; test indexes are tiny.
2026-08-28 19:15:51 +02:00
Dominik Schmidt a3e40a7471 test(search): run the parity suite against the current main
Move the OpenSearch test helpers to services/search/internal/opensearchtest so
the parity package can import them, and drop the index generation and clock
patch the suite was written against, neither exists on main yet.
2026-08-28 19:15:50 +02:00
Florian Schade 5601be1276 test(search): introduce engine parity suite 2026-08-28 18:41:37 +02:00
Alex Ababii 6ceedcff0e clean up frontend configs after the event consumer move to sharing service 2026-08-26 23:52:46 +02:00
Alex Ababii 74d6ac1183 moved ShareCreated event consumer from frontend to shared service 2026-08-26 23:52:46 +02:00
Ralf Haferkamp 0c601bbb8c chore: Adapt for change opensearch APIs 2026-08-26 11:51:05 +02:00
dependabot[bot] 578861b473 build(deps): bump github.com/opensearch-project/opensearch-go/v4
Bumps [github.com/opensearch-project/opensearch-go/v4](https://github.com/opensearch-project/opensearch-go) from 4.6.0 to 4.7.3.
- [Release notes](https://github.com/opensearch-project/opensearch-go/releases)
- [Changelog](https://github.com/opensearch-project/opensearch-go/blob/v4.7.3/CHANGELOG.md)
- [Commits](https://github.com/opensearch-project/opensearch-go/compare/v4.6.0...v4.7.3)

---
updated-dependencies:
- dependency-name: github.com/opensearch-project/opensearch-go/v4
  dependency-version: 4.7.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-26 11:51:05 +02:00
Ralf Haferkamp a5bad69e20 chore: adjust for changed automemlimit API 2026-08-26 11:31:01 +02:00
dependabot[bot] f41a1e394e build(deps): bump github.com/KimMachineGun/automemlimit
Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.7.5 to 1.0.0.
- [Release notes](https://github.com/KimMachineGun/automemlimit/releases)
- [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.7.5...v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/KimMachineGun/automemlimit
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-26 11:31:01 +02:00
dependabot[bot] 1160a70960 build(deps): bump github.com/nats-io/nats.go from 1.52.0 to 1.53.1
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.52.0 to 1.53.1.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.52.0...v1.53.1)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.53.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-26 08:21:02 +02:00
dependabot[bot] 7d73743a94 build(deps): bump github.com/open-policy-agent/opa from 1.19.0 to 1.19.1
Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-version: 1.19.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 17:42:30 +02:00
OpenCloud Devops cd9095833a 🎉 Release 7.5.0 (#3238) 2026-08-25 15:53:40 +02:00
Benedikt Kulmann 321bcfabd5 Merge pull request #3401 from opencloud-eu/more-version-placeholders-7.5.0
chore: replace remaining version placeholders for 7.5.0
2026-08-25 15:47:02 +02:00
Benedikt Kulmann c01e2d46ec chore: replace remaining version placeholders for 7.5.0 2026-08-25 14:57:45 +02:00
Benedikt Kulmann e857bff6af Merge pull request #3400 from opencloud-eu/env-var-introduction-version-7.5.0
chore: set introduction version to 7.5.0 for new env vars
2026-08-25 14:20:54 +02:00
Benedikt Kulmann 709543234f chore: set introduction version to 7.5.0 for new env vars 2026-08-25 12:53:03 +02:00
Benedikt Kulmann 6eebeb8b57 Merge pull request #3333 from opencloud-eu/bump-webcommitid-180526
[full-ci] chore: bump webcommitid
2026-08-25 12:03:58 +02:00
Alexander Ackermann 71bda95ea6 chore: bump webcommitid 2026-08-25 11:17:17 +02:00
Benedikt Kulmann 81e22fd655 Merge pull request #3399 from opencloud-eu/chore/bump-web-7.4.0
[full-ci] chore: bump web to v7.4.0
2026-08-25 11:16:17 +02:00
Benedikt Kulmann 850b4e7ac6 Merge pull request #3398 from opencloud-eu/reva-bump-2.49.0
[full-ci] chore: bump reva to 2.49.0
2026-08-25 10:47:18 +02:00
Benedikt Kulmann 82dca95473 chore: fix commit message typo in bumping-reva skill 2026-08-25 10:01:26 +02:00
Benedikt Kulmann 330e874110 chore: bump reva to 2.49.0 2026-08-25 10:01:26 +02:00
Benedikt Kulmann 65051a63db chore: bump web to v7.4.0 2026-08-25 09:55:39 +02:00
dependabot[bot] 498fa40613 build(deps): bump google.golang.org/grpc from 1.83.0 to 1.83.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.83.0 to 1.83.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.83.0...v1.83.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 09:47:08 +02:00
dependabot[bot] 011892025b build(deps): bump github.com/stretchr/testify from 1.12.0 to 1.12.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 09:32:01 +02:00
dependabot[bot] 3052b28fdc build(deps): bump github.com/go-chi/chi/v5 from 5.3.1 to 5.3.2
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.3.1 to 5.3.2.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.3.1...v5.3.2)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 09:16:07 +02:00
dependabot[bot] cc77ceb92a build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2
Bumps [github.com/grpc-ecosystem/grpc-gateway/v2](https://github.com/grpc-ecosystem/grpc-gateway) from 2.29.0 to 2.30.0.
- [Release notes](https://github.com/grpc-ecosystem/grpc-gateway/releases)
- [Commits](https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.29.0...v2.30.0)

---
updated-dependencies:
- dependency-name: github.com/grpc-ecosystem/grpc-gateway/v2
  dependency-version: 2.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 15:10:02 +02:00
dependabot[bot] 2880a632ff build(deps): bump golang.org/x/image from 0.44.0 to 0.45.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.44.0 to 0.45.0.
- [Commits](https://github.com/golang/image/compare/v0.44.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 14:04:19 +02:00
dependabot[bot] 1a5c0d58e8 build(deps): bump github.com/beevik/etree from 1.7.0 to 1.7.1
Bumps [github.com/beevik/etree](https://github.com/beevik/etree) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/beevik/etree/releases)
- [Changelog](https://github.com/beevik/etree/blob/main/RELEASE_NOTES.md)
- [Commits](https://github.com/beevik/etree/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/beevik/etree
  dependency-version: 1.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 13:23:58 +02:00
Sawjan Gurung cc8a161fce test(api): update php test dependencies (#3335)
* test: update php dependencies

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: use custom tus client

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: fix php code style and commands

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: use doctrine coding standard

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: convert headers value to string

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: initialize phpunit config

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: fix tus upload

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* test: fix php code style

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

---------

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
2026-08-21 19:38:34 +02:00
Jannik Stehle ec4d5b7e6b Merge pull request #3386 from opencloud-eu/feat/extend-thumbnail-service-resolutions
feat(thumbnails): extend list of default resolutions
2026-08-21 14:03:23 +02:00
Florian Schade 7ddd8566f1 Merge pull request #3388 from fschade/fix-search-write-visibility
fix(search): refresh the opensearch index after a write
2026-08-21 13:50:13 +02:00
Jörn Friedrich Dreyer 9796123462 Merge pull request #3279 from opencloud-eu/feat/eventhistory_event_handler_on_demand
ability to disable grpc and/or event consumer for event history service
2026-08-21 13:31:51 +02:00
Jörn Friedrich Dreyer ed5b6ee780 Update tests/acceptance/features/apiSearch1/favoriteSearch.feature 2026-08-21 13:23:22 +02:00
Florian Schade 97a8be52d2 fix(search): refresh the index after a write 2026-08-21 10:03:52 +02:00
Jannik Stehle e1aff97fe2 Merge pull request #3377 from opencloud-eu/fix/vips-thumbnail-height
fix(thumbnails): respect the requested height in libvips builds
2026-08-21 09:38:16 +02:00
Jörn Friedrich Dreyer efc3e713b1 Merge pull request #3289 from opencloud-eu/enable-direct-download
refactor datagateway into proxy middleware
2026-08-21 09:07:21 +02:00
Jannik Stehle 1e5de6e01b feat(thumbnails): extend list of default resolutions
Extend the list by these resolutions:

"500x280", "280x500", "1000x560", "560x1000", "512x2048"

This allows for a more granular thumbnail generation for requested previews
between 128 and 1080 px.
2026-08-21 09:04:59 +02:00
Jörn Friedrich Dreyer 962b425078 test for Allow Methods returned by tus
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 08:16:26 +02:00
Jörn Friedrich Dreyer f065e8acdc expect only the tus related cors allow header values
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:47 +02:00
Jörn Friedrich Dreyer 738ec6de67 make ocm internally expose the dataprovider
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:47 +02:00
Jörn Friedrich Dreyer 898b6e3479 tus always returns 200 on OPTIONS requests
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jörn Friedrich Dreyer 72c0ee9e54 do not set datagateway url for storage drivers
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jörn Friedrich Dreyer 0776a479da send all /data throught middleware
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jörn Friedrich Dreyer a93ec92789 rewrite more /data urls
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jörn Friedrich Dreyer c0d5380017 drop unused datagateway service and gateway config
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jörn Friedrich Dreyer fff84b844f drop STORAGE_USERS_EXPOSE_DATA_SERVER env var
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jörn Friedrich Dreyer 2830c4eed5 make dataprovider routable in tests
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jörn Friedrich Dreyer a74661618e refactor datagateway into proxy middleware
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-21 07:34:46 +02:00
Jannik Stehle d562d44edf fix(thumbnails): respect the requested height in libvips builds
The libvips generator passed 0 as the target height to vips_thumbnail,
so the value was rejected and dropped.

As a result, a preview of a 4000x5000 portrait image requested with e.g.
x=500&y=500 returned a ...x1920 image instead of a ...x1080 one, which
would be the next correct size in the pre-defined resolutions list. This
was due to the missing height, so it used the width (=1920) to determine
the longest side.

This also aligns it with the non-libvips behavior.
2026-08-20 19:11:28 +02:00
Florian Schade 3b47c6550d Merge pull request #3372 from fschade/fix-search-hidden-flag
fix(search): keep the hidden flag in sync
2026-08-20 17:58:07 +02:00
Florian Schade de1173202d fix(search): keep the hidden flag in sync 2026-08-20 16:54:44 +02:00
Florian Schade b62af81bde Merge pull request #3361 from fschade/fix-search-opensearch-queries
fix(search): fix open-search kql field compilation
2026-08-20 13:39:22 +02:00
Pascal Bleser f88765ed7f fix(acceptance): fix running acceptance tests against host on Linux (#3358)
Add an entry host.docker.internal -> host-gateway entry to the
acceptable tests container in tests/acceptance/docker/src/acceptance.yml
in order to be able to run them against an opencloud server running on
the host on Linux as well.

Reference: https://www.baeldung.com/ops/docker-compose-add-host
2026-08-20 10:56:24 +02:00
Florian Schade 3ddc5b47c8 test(search): drop the issue markers the fixes above resolve 2026-08-20 10:52:12 +02:00
Anja Barz 719327d1ad docs: clarify custom role bootstrap behavior 2026-08-20 09:56:46 +02:00
Jannik Stehle 5b2529602f feat(graph): add LibreGraphContentType on drive 2026-08-20 09:03:56 +02:00
Pascal Bleser f034f2058f fix(graph): adding the same user as multiple members in a group (#3354)
* make sure the list of members to add to a group is unique, by
   removing duplicates in the inbound list of member IDs

 * not attempting to detect duplicate DNs as that should not be
   necessary, and results in an error with OpenLDAP; the built-in IDM
   does not catch those as of now but the issue is tracked there, and
   should not happen due to the UUID filtering in this commit

 * remove the corresponding failing test from the list of expected
   failures

Fixes 3354
2026-08-20 08:11:49 +02:00
dependabot[bot] e25afdd420 build(deps): bump github.com/nats-io/nats-server/v2
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.14.4 to 2.14.5.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.14.4...v2.14.5)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.14.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 08:02:19 +02:00
André Duffeck 78796c4d95 Only log a debug message when an item is still in processing state 2026-08-20 07:58:40 +02:00
dependabot[bot] c1528c53ff build(deps): bump go.opentelemetry.io/contrib/zpages
Bumps [go.opentelemetry.io/contrib/zpages](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.69.0 to 0.70.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.69.0...zpages/v0.70.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/zpages
  dependency-version: 0.70.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 07:49:16 +02:00
dependabot[bot] e96f8bacb1 build(deps): bump github.com/sirupsen/logrus from 1.9.4 to 1.10.0
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.4 to 1.10.0.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.4...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-version: 1.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-20 07:48:49 +02:00
Florian Schade 5cef433c5a fix(search): answer a query the parser refuses with a bad request 2026-08-19 17:35:05 +02:00
Florian Schade 378e2c45ec fix(search): search a value the way the field is analyzed 2026-08-19 17:35:05 +02:00
Andre Duffeck 5f1a3ee869 Merge pull request #3329 from opencloud-eu/chore/bump-reva
chore: bump reva to latest main
2026-08-19 15:54:30 +02:00
Florian Schade 06bc072925 Merge pull request #3337 from fschade/fix-search-event-acks
fix(search): ack every event the index service handles
2026-08-19 15:46:34 +02:00
André Duffeck db23f1a1ca Bump reva 2026-08-19 15:00:00 +02:00
Florian Schade 0747008037 fix(search): ack every event the index service handles 2026-08-19 14:45:28 +02:00
Florian Schade 8a9889c12c Merge pull request #3336 from fschade/fix-search-space-deleted-event
fix(search): take a deleted space out of the index when it is deleted
2026-08-19 14:26:22 +02:00
André Duffeck 3d632fb90e Expose upload status and message 2026-08-19 14:08:12 +02:00
Florian Schade 45403e0909 fix(search): take a deleted space out of the index when it is deleted 2026-08-19 13:47:44 +02:00
Florian Schade 5e55b80e2c Merge pull request #3338 from fschade/refactor-search-param-names
refactor(search): re-name the engine parameters
2026-08-19 13:39:29 +02:00
Florian Schade 545af8e735 refactor(search): re-name the engine parameters 2026-08-19 12:52:47 +02:00
André Duffeck f70e44a474 Run buildifier 2026-08-19 12:48:34 +02:00
André Duffeck 320284d03e Bump reva 2026-08-19 12:35:43 +02:00
André Duffeck e3bf541d88 Disable ldap lookup cache during tests 2026-08-19 12:34:30 +02:00
André Duffeck 12e85ae950 Expose ldap cache ttl, set a default of 10s 2026-08-19 12:31:30 +02:00
junkerderprovinz 654f0e8e20 fix(postprocessing): retry publishing events instead of killing the server
A single transient nats: timeout on event publish was fatal to the whole
single-binary server, with no retry despite the service already having
backoff machinery for step failures. Now retries with the same exponential
backoff, capped at half the ack wait so the source event isn't redelivered
to another worker mid-retry. Also stops acking the source event when
publish ultimately fails, so the upload resumes after restart instead of
staying orphaned, and no longer exits during graceful shutdown.
2026-08-19 11:23:46 +02:00
Jörn Friedrich Dreyer 6d2cbd365f Merge pull request #3344 from opencloud-eu/antivirus-log-error
actually log error on exit
2026-08-19 09:39:11 +02:00
Ralf Haferkamp f94b6c7cdd cleanup: Remove old changelog leftovers
We've switched away from calens for changelog generation. This removes
some leftovers.
2026-08-19 08:40:32 +02:00
dependabot[bot] 07adbac96f build(deps): bump github.com/stretchr/testify from 1.11.1 to 1.12.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.11.1 to 1.12.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.11.1...v1.12.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-19 08:40:11 +02:00
junkerderprovinz 810ff866a0 chore: remove deprecated changelog fragment per review feedback 2026-08-19 08:39:26 +02:00
junkerderprovinz 27eed22bdc fix(activitylog): log missing parent id cache entry at debug level
Moving a file logged "could not delete parent id cache" at error
level even though the operation succeeded. The parent id cache is
populated lazily and its entries expire, so removing a key that was
never cached returns "key not found", which is the expected case
rather than an error. Log it at debug level instead.

Two of the three log sites reported in the issue live in the
vendored opencloud-eu/reva module and need a separate fix there.

Fixes: https://github.com/opencloud-eu/opencloud/issues/1043
2026-08-19 08:39:26 +02:00
dependabot[bot] 965a7f0f12 build(deps): bump golang.org/x/net from 0.57.0 to 0.58.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.57.0 to 0.58.0.
- [Commits](https://github.com/golang/net/compare/v0.57.0...v0.58.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.58.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-19 08:05:19 +02:00
dependabot[bot] 528bacce7d build(deps): bump github.com/onsi/ginkgo/v2 from 2.32.0 to 2.32.1
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.32.0 to 2.32.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.32.0...v2.32.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.32.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-19 08:04:46 +02:00
opencloudeu 80fadeb381 [tx] updated from transifex 2026-08-18 23:17:04 +00:00
Dominik Schmidt 03cfbacafa docs(adr): add concrete mtime/name backend-drift examples 2026-08-18 17:57:27 +02:00
Dominik Schmidt 2223b8ed9d docs(adr): include location.* in the OpenSearch facet sub-strings list
The bullet enumerating which OpenSearch facet sub-fields change
from text+keyword to keyword-only listed audio, photo and image but
omitted location, which is in the same dynamic-template-handled
facet block per the context section. Add it for consistency.
2026-08-18 17:57:27 +02:00
Dominik Schmidt 8053275166 docs(adr): add 0005 unified search index mapping
Propose a single central Go-struct + overrides map as the source of
truth for the search index layout across bleve and OpenSearch — the
same definition drives the per-backend index mapping, the write-time
adapter, the hit-decoding path, and the KQL compiler's case-folding
rules, so the two backends cannot drift silently again.

Also records the end-to-end case-handling principle for facet data
(indexed as case-preserving keywords so aggregation buckets return
correct display values), the sibling-field pattern for geopoint on
Location, and the rationale for replacing the two backends' implicit
defaults with an explicit, backend-agnostic contract.

PR #2659 is a proof-of-concept implementation the proposal emerged
from; scope and APIs there will be revisited once this ADR lands.
2026-08-18 17:57:26 +02:00
Florian Schade 2e5dd7b5ef Merge pull request #3297 from fschade/fix-mention-notification-review
fix(collaboration): harden the mention notification endpoint
2026-08-18 17:57:04 +02:00
Jörn Friedrich Dreyer dcd717f83c actually log error on exit 2026-08-18 17:17:30 +02:00
dependabot[bot] 5056d4a0f4 build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch
Bumps [github.com/testcontainers/testcontainers-go/modules/opensearch](https://github.com/testcontainers/testcontainers-go) from 0.43.0 to 0.44.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.43.0...v0.44.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/opensearch
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-18 15:51:36 +02:00
Jannik Stehle c198c2e453 chore: bump reva to latest main 2026-08-18 15:25:23 +02:00
Florian Schade bb8e185fbd fix(graph): use authResponse token exclusively 2026-08-18 15:03:57 +02:00
Viktor Scharf c2a01e3aad ci: run search acceptance tests against OpenSearch on opensearch PR title (#3323) 2026-08-18 12:51:24 +02:00
Andre Duffeck 8aa55eb010 Merge pull request #3331 from aduffeck/fix-opensearch-space-scope
Fix opensearch space scope
2026-08-18 12:38:42 +02:00
Florian Schade 99b01b0734 refactor(graph): let the cs3 mapper answer the statuses the handler does not name 2026-08-18 11:48:59 +02:00
André Duffeck 6a9ffd6842 Switch to Ginkgo tests for readability 2026-08-18 11:26:23 +02:00
Jannik Stehle 5ad9d61fdc build(deps): bump github.com/opencloud-eu/libre-graph-api-go to 69f82a7dde55 2026-08-18 11:12:23 +02:00
André Duffeck 701276bc80 Make sure to scope updates to the index by space 2026-08-18 10:54:24 +02:00
dependabot[bot] 3445b75216 build(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace
Bumps [go.opentelemetry.io/otel/exporters/stdout/stdouttrace](https://github.com/open-telemetry/opentelemetry-go) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/stdout/stdouttrace
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-18 10:45:45 +02:00
Florian Schade 516734b18a fix(graph): map the cs3 status of a mention to the matching http error 2026-08-18 10:35:02 +02:00
Pascal Bleser fedfddf407 Merge pull request #3307 from fschade/mise-toml
[docs-only] feat: add mise toml
2026-08-18 10:33:33 +02:00
Sawjan Gurung 4bcf97353a test(api): fix share role update test scenario (#3322)
Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
2026-08-18 09:49:18 +02:00
dependabot[bot] 2fa4a0458d build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
Bumps [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.69.0 to 0.70.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.69.0...zpages/v0.70.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.70.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-18 09:43:19 +02:00
Florian Schade bbee2b7433 fix: restructure call order 2026-08-18 09:40:01 +02:00
Alex a3c7b2dbb3 feat: update space template image (#3324)
* feat: update space template image

* skip test

* reduce asset size
2026-08-18 09:14:20 +02:00
opencloudeu 7130c10ef3 [tx] updated from transifex 2026-08-17 23:19:38 +00:00
Florian Schade 93d66a0f37 fix(graph): answer dropped mentions with 202 to prevent access probing 2026-08-17 15:48:00 +02:00
Florian Schade 8b922127b8 feat: move the collaboration mentioning api to graph 2026-08-17 15:44:04 +02:00
Florian Schade 8a3d8fea4d fix(collaboration): check the requester's access before a mention goes out 2026-08-17 15:44:04 +02:00
Florian Schade 6077e7453c Merge pull request #3321 from fschade/bump-libre-graph-api-go
build(deps): bump github.com/opencloud-eu/libre-graph-api-go
2026-08-17 15:30:41 +02:00
Viktor Scharf 1959d79717 test: add api tests for cross-space search index mutation (#3320) 2026-08-17 15:10:34 +02:00
Florian Schade 26bd26f998 build(deps): bump github.com/opencloud-eu/libre-graph-api-go to 387da64db0b1 2026-08-17 12:29:18 +02:00
Viktor Scharf c8a1f55657 ci: run search acceptance tests against OpenSearch in nightly (#3302)
* ci: run search acceptance tests against OpenSearch in nightly

* fix

* add issue tag to failed search tests
2026-08-17 08:19:54 +02:00
opencloudeu 7243c922ef [tx] updated from transifex 2026-08-16 23:17:02 +00:00
opencloudeu 2e912d233a [tx] updated from transifex 2026-08-14 23:17:03 +00:00
Alex Ababii 1d55c4d300 eventhistory: moved consumer related actions to it's constructor 2026-08-14 17:33:09 +02:00
Jannik Stehle 29a56da565 Merge pull request #3313 from opencloud-eu/feat/enable-rclone-crypt-app
feat(web): add rclone-crypt to default apps
2026-08-14 16:05:52 +02:00
Jannik Stehle 4e0cd4038e feat(web): add rclone-crypt to default apps
Enable the rclone-crypt app per default for the upcoming release.
2026-08-14 14:42:45 +02:00
Alex Ababii 4e5bef96a6 Merge branch 'main' of github.com:opencloud-eu/opencloud into feat/eventhistory_event_handler_on_demand 2026-08-14 13:30:32 +02:00
Thomas Schweiger 1fa8950571 Merge pull request #3229 from opencloud-eu/favourite-sse-event 2026-08-14 13:00:30 +02:00
Alex Ababii 6e87f6665b eventhistory service: fix consumer isolation and conditional initialization 2026-08-14 11:56:23 +02:00
Alex AbabiiandViktor Scharf 8989106e91 fix restore file version for shared resource (#3268)
* tmp upd of web and reva version for testing purposes

* chore: bump reva and web

* upd expected failures list in acceptance test for revert file version case

* bump reva and web versions

---------

Co-authored-by: Viktor Scharf <v.scharf@opencloud.eu>
2026-08-14 10:40:59 +02:00
Thomas Schweiger 9a9443fe85 enhance: send events for adding/removing favorite items 2026-08-14 10:16:57 +02:00
Florian Schade 7fd5079973 feat: add mise toml 2026-08-14 09:32:43 +02:00
Alex b2cf7a965f fix(csp): allow blob: in style-src for EPUB reader iframe (#3300) 2026-08-13 13:16:04 +02:00
Jörn Friedrich Dreyer 8478b5daa1 allow tuning the proxies http client
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2026-08-13 11:54:42 +02:00
Pascal Bleser 331f78eb43 feat(nats): enable the NATS monitoring HTTP endpoint in the embedded NATS server
* add configuration options to enable the NATS server HTTP monitoring
   endpoint:
   - NATS_MONITORING_ENABLED (defaults to false)
   - NATS_MONITORING_HTTP_HOST (defaults to 127.0.0.1)
   - NATS_MONITORING_HTTP_PORT (defaults to 8222)
   - NATS_MONITORING_ENABLE_TLS (defaults to false)

Fixes #3294
2026-08-13 11:46:53 +02:00
dependabot[bot] 5deb863dd8 build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 11:42:47 +02:00
dependabot[bot] 1cf3ff4863 build(deps): bump github.com/rogpeppe/go-internal from 1.15.0 to 1.16.0
Bumps [github.com/rogpeppe/go-internal](https://github.com/rogpeppe/go-internal) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/rogpeppe/go-internal/releases)
- [Commits](https://github.com/rogpeppe/go-internal/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/rogpeppe/go-internal
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 11:42:08 +02:00
dependabot[bot] 9ae863ca3a build(deps): bump github.com/kovidgoyal/imaging from 1.8.22 to 1.8.23
Bumps [github.com/kovidgoyal/imaging](https://github.com/kovidgoyal/imaging) from 1.8.22 to 1.8.23.
- [Release notes](https://github.com/kovidgoyal/imaging/releases)
- [Commits](https://github.com/kovidgoyal/imaging/compare/v1.8.22...v1.8.23)

---
updated-dependencies:
- dependency-name: github.com/kovidgoyal/imaging
  dependency-version: 1.8.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 11:40:19 +02:00
Viktor Scharf 0fc2a98e6e api-test: notification settings and getting email notifications (#3281)
* api-test: notification settings and getting email notifications

* disable all email notifications
2026-08-13 11:32:12 +02:00
Jörn Friedrich Dreyer 6f5f993ada Merge pull request #3288 from fschade/feat-modernize-opa
perf(policies): reuse the compiled rego query across evaluations
2026-08-12 17:58:13 +02:00
Florian Schade 1e7cfaf92d refactor(policies): read the rego policies once at startup 2026-08-12 17:01:49 +02:00
Viktor Scharf b41d0c1040 fix(ci): stamp version into daily docker images (#3291)
* fix(ci): stamp version into daily docker images

* fix after review
2026-08-12 12:09:22 +02:00
opencloudeu 716922f76f [tx] updated from transifex 2026-08-11 23:17:55 +00:00
Florian Schade 573844ada4 Merge pull request #3286 from fschade/fix-proxy-middleware-race
Fix proxy middleware race
2026-08-11 12:40:54 +02:00
Florian Schade cd7f8f91b6 fix: oidc middleware claim race 2026-08-11 11:39:50 +02:00
Alex Ababii fec4640b5a eventhistory: debug service always enabled 2026-08-11 11:15:31 +02:00
opencloudeu 8504e29743 [tx] updated from transifex 2026-08-10 23:17:14 +00:00
Alex Ababii 695f6a9e4f uneeded type conversion 2026-08-10 15:31:50 +02:00
Alex Ababii 4838167696 ability to disable grpc and/or event consumer for event history service 2026-08-10 14:36:40 +02:00
dependabot[bot] f03429f47c build(deps): bump go.opentelemetry.io/otel/trace from 1.44.0 to 1.45.0
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 11:24:31 +02:00
Viktor Scharf 97908ac6c1 api-test: add posixfs scan and consistency CLI tests (#3263)
* api-test: add posixfs scan and consistency CLI tests

* fix after review
2026-08-10 08:50:47 +02:00
opencloudeu 3a3cba602c [tx] updated from transifex 2026-08-08 23:16:53 +00:00
Jannik Stehle 1c138efac2 Merge pull request #3259 from opencloud-eu/feat/yjs-server-url
feat(web): add yjsServerUrl config
2026-08-06 15:28:11 +02:00
Jannik Stehle 07fd041efb feat(web): add yjsServerUrl config
This config specifies a URL for a yjs server that is used for document
collaboration in web apps (e.g. tiptap editor, excalidraw, ...). It's
optional, not setting this disables this feature in Web.
2026-08-06 13:37:54 +02:00
Viktor Scharf da7849251d update rolling release template (#3242) 2026-08-06 09:29:16 +02:00
Michael Barz 466c3c8c79 Merge pull request #3252 from aduffeck/fix-favorite-flag
Fix missing favorite flag on opensearch hits
2026-08-06 09:07:57 +02:00
André Duffeck 2142894cba Fix missing favorite flag on opensearch hits 2026-08-05 18:43:13 +02:00
Viktor Scharf 76ff13b47e api-test: replace sleeps with WaitHelper poll for async state (#3239)
* api-test: replace sleeps with WaitHelper poll for async state

* php-style
2026-08-05 09:23:57 +02:00
Andre Duffeck 799f98339e Merge pull request #3235 from pbleser-oc/feat/3182-posixfs-scan-add-subtree
feat(posixfs): #3182 add basepath option in the "posixfs scan" command
2026-08-04 09:15:22 +02:00
Pascal Bleser 6663fbeeed feat(posixfs): #3182 add basepath option in the "posixfs scan" command
* add support for specifying a set of resources when running the
   posixfs scan command scanning, or a singular file to scan, as opposed

 * refactors the resource walking function implemented in
   https://github.com/opencloud-eu/opencloud/pull/3220 in order to
   reuse it for the posixfs scan command

 * implements https://github.com/opencloud-eu/opencloud/issues/3182

 * add --halt-on-error flag

 * collect errors and fail command when more than one error occured
2026-08-03 20:04:39 +02:00
1540 changed files with 190562 additions and 32605 deletions

No files matched your search

+3 -3
View File
@@ -114,16 +114,16 @@ Confirm the reva line in `go.mod` is exactly `github.com/opencloud-eu/reva/v2 RE
- Stage everything the bump touched: `git add go.mod go.sum vendor pkg/version/version.go`.
- One commit, conventional-commits format, **empty body**:
```
chore: reva bump -2.48.0
chore: bump reva to 2.48.0
```
- PR base: **`main`** (reva bumps always target main).
- PR title: `[full-ci] chore: reva bump -2.48.0` (the commit message prefixed with `[full-ci] `).
- PR title: `[full-ci] chore: bump reva to 2.48.0` (the commit message prefixed with `[full-ci] `).
- PR body: the file from step 4.
- Add the label `Type:Maintenance`.
```bash
gh pr create --base main \
--title "[full-ci] chore: reva bump -$REVA_VERSION_NO_V" \
--title "[full-ci] chore: bump reva to $REVA_VERSION_NO_V" \
--label "Type:Maintenance" \
--body-file <body-file>
```
+6
View File
@@ -65,6 +65,12 @@ $(GOVULNCHECK): $(BINGO_DIR)/govulncheck.mod
@echo "(re)installing $(GOBIN)/govulncheck-v1.1.4"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=govulncheck.mod -o=$(GOBIN)/govulncheck-v1.1.4 "golang.org/x/vuln/cmd/govulncheck"
GOWRAP := $(GOBIN)/gowrap-v1.4.3
$(GOWRAP): $(BINGO_DIR)/gowrap.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
@echo "(re)installing $(GOBIN)/gowrap-v1.4.3"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=gowrap.mod -o=$(GOBIN)/gowrap-v1.4.3 "github.com/hexdigest/gowrap/cmd/gowrap"
MOCKERY := $(GOBIN)/mockery-v3.4.0
$(MOCKERY): $(BINGO_DIR)/mockery.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
+5
View File
@@ -0,0 +1,5 @@
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.25.8
require github.com/hexdigest/gowrap v1.4.3 // cmd/gowrap
+55
View File
@@ -0,0 +1,55 @@
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hexdigest/gowrap v1.4.3 h1:m+t8aj1pUiFQbEiE8QJg2xdYVH5DAMluLgZ9P/qEF0k=
github.com/hexdigest/gowrap v1.4.3/go.mod h1:XWL8oQW2H3fX5ll8oT3Fduh4mt2H3cUAGQHQLMUbmG4=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0=
github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+2
View File
@@ -24,6 +24,8 @@ GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.64.6"
GOVULNCHECK="${GOBIN}/govulncheck-v1.1.4"
GOWRAP="${GOBIN}/gowrap-v1.4.3"
MOCKERY="${GOBIN}/mockery-v3.4.0"
MUTAGEN="${GOBIN}/mutagen-v0.18.1"
+2
View File
@@ -18,6 +18,8 @@ exclude_paths:
- 'deployments/**'
- "release-config.ts"
- 'tests/acceptance/expected-failures-*.md'
# written by the search engine parity suite, table rows are as wide as they are
- 'services/search/pkg/parity/README.md'
- 'tests/acceptance/bootstrap/**'
- 'tests/acceptance/TestHelpers/**'
- 'tests/acceptance/scripts/run.sh'
+5 -3
View File
@@ -2,6 +2,8 @@
[Release Template](https://github.com/opencloud-eu/opencloud/blob/main/.github/rolling_release_template.md)
## Prerequisites
* [ ] replace `%%NEXT%%` with the release version
* [ ] web release
* [ ] bump web version
* [ ] squash and merge the web release PR
@@ -14,17 +16,17 @@
## QA Phase
* [ ] bump `opencloud_commitid` in web and run all working tests in CI
* [ ] compatibility test
* [ ] n8n integration QA
* [ ] confirmatory testing, if needed
* [ ] squash and merge Release PR
## Collected bugs
## After QA Phase
* [ ] replace `%%NEXT%%` wuth the release version
* [ ] squash and merge Release PR
* [ ] publish release notes to the docs
* [ ] add migration guide to changelog with prefix `**ACTION REQUIRED:**`, if needed.
* [ ] add release notes from web and reva to opencloud changelog
* [ ] n8n integration - update new opencloud version
* [ ] docker-compose - update new opencloud version
* [ ] update the public matrix channel topic
* [ ] update https://update.opencloud.eu/server.json
* [ ] update the version on demo.opencloud.eu
-1
View File
@@ -36,7 +36,6 @@ vendor-bin/**/composer.lock
vendor-php
# API acceptance tests - auto-generated files
.php-cs-fixer.cache
suite-logs
tests/acceptance/filesForUpload/filesWithVirus/
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
set -euo pipefail
# Purpose of this script is to parse the go.mod file to retrieve the version of
# Go that we are using, either from a "toolchain" directive (preferred), or from
# a "go" directive as a fallback.
#
# The script then outputs that version, where it will be picked up as an environment
# variable in mise.
F="./go.mod"
AWK=awk
# prefer 'gawk' over 'awk' to make sure we get GNU awk
command -v gawk &>/dev/null && AWK=gawk
[[ -e $F ]] || { echo "ERROR: failed to find $F" >&2; exit 1;}
GO_VERSION=$("$AWK" '/^toolchain/ {print $2; exit} /^go / {print $2}' "$F")
GO_VERSION=${GO_VERSION#go} # strip the potential 'go' prefix:
echo "${GO_VERSION}"
-31
View File
@@ -1,31 +0,0 @@
<?php
$dirToParse = 'tests/acceptance/';
$dirIterator = new DirectoryIterator(__DIR__ . '/' . $dirToParse);
$excludeDirs = [
'node_modules',
'vendor-php'
];
$finder = PhpCsFixer\Finder::create()
->exclude($excludeDirs)
->in(__DIR__);
$ocRule = (new OC\CodingStandard\Config())->getRules();
$config = new PhpCsFixer\Config();
$config->setFinder($finder)
->setIndent("\t")
->setRules(
array_merge(
$ocRule,
[
"return_type_declaration" => [
"space_before" => "none",
],
'single_space_around_construct' => true
]
)
);
$config->setFinder($finder);
return $config;
+1 -1
View File
@@ -1,3 +1,3 @@
# The test runner source for UI tests
WEB_COMMITID=52b3c79f772feae23b7e77058c89c4151087502e
WEB_COMMITID=dffb3808702dab198065faa7a8aa16228e048385
WEB_BRANCH=main
+123 -73
View File
@@ -8,7 +8,7 @@ docker_repo_slug = "opencloudeu/opencloud"
# images
ALPINE_GIT = "alpine/git:latest"
APACHE_TIKA = "apache/tika:3.2.3.0-full"
APACHE_TIKA = "apache/tika:4.0.0-full"
CHKO_DOCKER_PUSHRM = "chko/docker-pushrm:1"
CODACY_COVERAGE_REPORTER = "codacy/codacy-coverage-reporter:14.1.3"
COLLABORA_CODE = "collabora/code:24.04.5.1.1"
@@ -202,12 +202,14 @@ config = {
"apiSearch1",
],
"skip": False,
"openSearch": True,
},
"search2": {
"suites": [
"apiSearch2",
],
"skip": False,
"openSearch": True,
},
"sharingNg": {
"suites": [
@@ -271,6 +273,7 @@ config = {
],
"skip": False,
"tikaNeeded": True,
"openSearch": True,
},
"ocm": {
"suites": [
@@ -328,6 +331,8 @@ config = {
"ANTIVIRUS_CLAMAV_SOCKET": "tcp://clamav:3310",
"OC_ASYNC_UPLOADS": True,
"OC_ADD_RUN_SERVICES": "antivirus",
"OC_SERVICE_ACCOUNT_ID": "service-account-id",
"OC_SERVICE_ACCOUNT_SECRET": "service-account-secret",
},
},
"multiTenancy": {
@@ -403,6 +408,16 @@ config = {
"embed",
],
},
# Runs the search-related e2e-tests against an OpenSearch search-engine.
"search": {
"skip": False,
"suites": [
"search/",
"file-action/favorites.feature",
],
"tikaNeeded": True,
"openSearch": True,
},
},
"e2eMultiService": {
"testSuites": {
@@ -817,30 +832,7 @@ def testOpencloud(ctx):
],
"environment": CI_HTTP_PROXY_ENV,
},
{
"name": "open-search",
"image": OPEN_SEARCH,
"detach": True,
"environment": {
"discovery.type": "single-node",
"DISABLE_INSTALL_DEMO_CONFIG": True,
"DISABLE_SECURITY_PLUGIN": True,
},
"entrypoint": ["/usr/share/opensearch/opensearch-docker-entrypoint.sh", "opensearch"],
},
{
"name": "wait-for-open-search",
"image": OC_CI_ALPINE,
"commands": [
"bash -c '" +
"until curl -sS \"http://open-search:9200/_cat/health?h=status\" | grep \"green\\|yellow\"; do\n" +
" echo \"Waiting for http://open-search:9200 to be healthy...\"\n" +
" sleep 5\n" +
"done\n" +
"echo \"http://open-search:9200 healthy...\"\n" +
"'",
],
},
] + waitForOpenSearch() + [
{
"name": "test",
"image": OC_CI_GOLANG,
@@ -872,6 +864,7 @@ def testOpencloud(ctx):
pipeline = {
"name": "test-lint-unit",
"steps": steps,
"services": openSearchService(),
"when": [
event["base"],
event["cron"],
@@ -938,30 +931,6 @@ def buildOpencloudBinaryForTesting(ctx):
}
return [pipeline]
def vendorbinCodestyle(phpVersion):
return [{
"name": "vendorbin-codestyle",
"image": OC_CI_PHP % phpVersion,
"environment": {
"COMPOSER_HOME": "%s/.cache/composer" % dirs["base"],
},
"commands": [
"make vendor-bin-codestyle",
],
}]
def vendorbinCodesniffer(phpVersion):
return [{
"name": "vendorbin-codesniffer",
"image": OC_CI_PHP % phpVersion,
"environment": {
"COMPOSER_HOME": "%s/.cache/composer" % dirs["base"],
},
"commands": [
"make vendor-bin-codesniffer",
],
}]
def checkTestSuitesInExpectedFailures(ctx):
return [{
"name": "check-suites-in-expected-failures",
@@ -1049,24 +1018,22 @@ def codestyle(ctx):
result = {
"name": name,
"steps": vendorbinCodestyle(phpVersion) +
vendorbinCodesniffer(phpVersion) +
[
{
"name": "php-style",
"image": OC_CI_PHP % phpVersion,
"commands": [
"make test-php-style",
],
},
{
"name": "check-env-var-annotations",
"image": OC_CI_PHP % phpVersion,
"commands": [
"make check-env-var-annotations",
],
},
],
"steps": [
{
"name": "php-style",
"image": OC_CI_PHP % phpVersion,
"commands": [
"make test-php-style",
],
},
{
"name": "check-env-var-annotations",
"image": OC_CI_PHP % phpVersion,
"commands": [
"make check-env-var-annotations",
],
},
],
"depends_on": [],
"when": [
event["base"],
@@ -1284,14 +1251,29 @@ def build_api_test_workflow_matrix(ctx, storage, suite_cfg, default_cfg):
matrix = {
"withRemotePhp": m["withRemotePhp"],
"enableWatchFs": m["enableWatchFs"],
"openSearch": False,
}
if override_with_remote_php != None:
matrix["withRemotePhp"] = override_with_remote_php
if override_enable_watch_fs != None:
matrix["enableWatchFs"] = override_enable_watch_fs
if matrix not in workflow_metrices and matrix in matrices:
base = {
"withRemotePhp": matrix["withRemotePhp"],
"enableWatchFs": matrix["enableWatchFs"],
}
if matrix not in workflow_metrices and base in matrices:
workflow_metrices.append(matrix)
# Add an OpenSearch search-engine variant for the search test suites.
if storage == "posix" and suite_cfg.get("openSearch", False):
os_matrix = {
"withRemotePhp": False,
"enableWatchFs": False,
"openSearch": True,
}
if os_matrix not in workflow_metrices:
workflow_metrices.append(os_matrix)
return workflow_metrices
def localApiTestPipeline(ctx):
@@ -1312,6 +1294,7 @@ def localApiTestPipeline(ctx):
"withRemotePhp": False,
"enableWatchFs": False,
"ldapNeeded": False,
"openSearch": False,
}
if "localApiTests" in config:
@@ -1337,6 +1320,7 @@ def localApiTestPipeline(ctx):
for m in matrices:
run_with_remote_php = m["withRemotePhp"]
run_with_watch_fs = m["enableWatchFs"]
run_with_open_search = m["openSearch"]
pipeline_name = "test-API"
if name.startswith("cli"):
@@ -1347,11 +1331,19 @@ def localApiTestPipeline(ctx):
pipeline_name += "-withRemotePhp"
if run_with_watch_fs:
pipeline_name += "-watchfs"
if run_with_open_search:
pipeline_name += "-opensearch"
server_environment = dict(params["extraServerEnvironment"])
if run_with_open_search:
server_environment["SEARCH_ENGINE_TYPE"] = "open-search"
server_environment["SEARCH_ENGINE_OPEN_SEARCH_CLIENT_ADDRESSES"] = "http://open-search:9200"
pipeline = {
"name": pipeline_name,
"steps": evaluateWorkflowStep() + restoreBuildArtifactCache(ctx, dirs["opencloudBinArtifact"], dirs["opencloudBinPath"]) +
(tikaService() if params["tikaNeeded"] else []) +
(waitForOpenSearch() if run_with_open_search else []) +
(waitForWebOffices(["https://collabora:9980", "https://onlyoffice", "http://fakeoffice:8080"]) if params["collaborationServiceNeeded"] else []) +
(waitForClamavService() if params["antivirusNeeded"] else []) +
(waitForEmailService() if params["emailNeeded"] else []) +
@@ -1359,7 +1351,7 @@ def localApiTestPipeline(ctx):
(waitForLdapService() if params["ldapNeeded"] else []) +
opencloudServer(
storage,
extra_server_environment = params["extraServerEnvironment"],
extra_server_environment = server_environment,
with_wrapper = True,
tika_enabled = params["tikaNeeded"],
watch_fs_enabled = run_with_watch_fs,
@@ -1371,6 +1363,7 @@ def localApiTestPipeline(ctx):
logRequests(),
"services": (emailService() if params["emailNeeded"] else []) +
(clamavService() if params["antivirusNeeded"] else []) +
(openSearchService() if run_with_open_search else []) +
((fakeOffice() + collaboraService() + onlyofficeService()) if params["collaborationServiceNeeded"] else []),
"depends_on": getPipelineNames(buildOpencloudBinaryForTesting(ctx)),
"when": [
@@ -1547,6 +1540,7 @@ def e2eTestPipeline(ctx):
"reportTracing": False,
"enableWatchFs": [False],
"storages": ["posix"],
"openSearch": False,
}
extra_server_environment = {
@@ -1590,14 +1584,30 @@ def e2eTestPipeline(ctx):
if "[decomposed]" in ctx.build.title.lower():
params["storages"] = ["decomposed"]
e2e_args = " ".join(params["suites"]) if params["suites"] else ""
if "with-tracing" in ctx.build.title.lower():
params["reportTracing"] = True
e2e_args = " ".join(params["suites"]) if params["suites"] else ""
# OpenSearch search-engine suites run posix-only without the watchfs matrix.
run_with_open_search = params["openSearch"]
if run_with_open_search:
params["storages"] = ["posix"]
params["enableWatchFs"] = [False]
for storage in params["storages"]:
for watch_fs_enabled in params["enableWatchFs"]:
pipeline_name = "test-e2e-%s-%s%s" % (name, storage, "-watchfs" if watch_fs_enabled else "")
pipeline_name = "test-e2e-%s-%s%s%s" % (
name,
storage,
"-watchfs" if watch_fs_enabled else "",
"-opensearch" if run_with_open_search else "",
)
server_environment = dict(extra_server_environment)
if run_with_open_search:
server_environment["SEARCH_ENGINE_TYPE"] = "open-search"
server_environment["SEARCH_ENGINE_OPEN_SEARCH_CLIENT_ADDRESSES"] = "http://open-search:9200"
steps = \
evaluateWorkflowStep() + \
@@ -1606,9 +1616,10 @@ def e2eTestPipeline(ctx):
restoreWebPnpmCache() + \
restoreBrowsersCache() + \
(tikaService() if params["tikaNeeded"] else []) + \
(waitForOpenSearch() if run_with_open_search else []) + \
opencloudServer(
storage,
extra_server_environment = extra_server_environment,
extra_server_environment = server_environment,
tika_enabled = params["tikaNeeded"],
watch_fs_enabled = watch_fs_enabled,
) + \
@@ -1635,6 +1646,7 @@ def e2eTestPipeline(ctx):
pipeline = {
"name": pipeline_name,
"steps": steps,
"services": openSearchService() if run_with_open_search else [],
"depends_on": getPipelineNames(buildOpencloudBinaryForTesting(ctx) + buildWebCache(ctx)),
"when": e2e_trigger,
}
@@ -1906,6 +1918,9 @@ def dockerRelease(ctx, repo, build_type):
"EDITION": "stable" if build_type == "production" else "rolling",
}
if ctx.build.event != "tag":
build_args["STRING"] = ctx.build.commit[:8]
# if no additional tag is given, the build-plugin adds latest
hard_tag = "daily"
if ctx.build.event == "tag":
@@ -2460,6 +2475,7 @@ def opencloudServer(storage = "decomposed", depends_on = [], deploy_type = "", e
"EVENTHISTORY_STORE": "memory",
"OC_TRANSLATION_PATH": "%s/tests/config/translations" % dirs["base"],
"ACTIVITYLOG_WRITE_BUFFER_DURATION": "0", # Disable write buffer so that test expectations are met in time
"OC_LDAP_LOOKUP_CACHE_TTL": "0", # disable ldap lookup cache so that test fixture change are applied right away
# search grpc port needed for index cli tests
"SEARCH_GRPC_ADDR": "0.0.0.0:9220",
# debug addresses required for running services health tests
@@ -2512,10 +2528,14 @@ def opencloudServer(storage = "decomposed", depends_on = [], deploy_type = "", e
if deploy_type == "cs3api_validator":
environment["GATEWAY_GRPC_ADDR"] = "0.0.0.0:9142" # make gateway available to cs3api-validator
environment["STORAGE_USERS_HTTP_ADDR"] = "0.0.0.0:9158" # make dataprovider available to wopi server
environment["STORAGE_USERS_DATA_SERVER_URL"] = "http://opencloud-server:9158/data" # make dataprovider routable by wopi server
environment["OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD"] = False
if deploy_type == "wopi_validator":
environment["GATEWAY_GRPC_ADDR"] = "0.0.0.0:9142" # make gateway available to wopi server
environment["STORAGE_USERS_HTTP_ADDR"] = "0.0.0.0:9158" # make dataprovider available to wopi server
environment["STORAGE_USERS_DATA_SERVER_URL"] = "http://opencloud-server:9158/data" # make dataprovider routable by wopi server
environment["APP_PROVIDER_EXTERNAL_ADDR"] = "eu.opencloud.api.app-provider"
environment["APP_PROVIDER_DRIVER"] = "wopi"
environment["APP_PROVIDER_WOPI_APP_NAME"] = "FakeOffice"
@@ -3424,6 +3444,9 @@ def tikaService():
return [{
"name": "tika",
"image": APACHE_TIKA,
# tika 4 discovers its plugins relative to the image working directory,
# the workspace default would leave the pipes fetchers empty
"directory": "/opt/tika-server",
"detach": True,
}, {
"name": "wait-for-tika-service",
@@ -3433,6 +3456,33 @@ def tikaService():
],
}]
def openSearchService():
return [{
"name": "open-search",
"image": OPEN_SEARCH,
"environment": {
"discovery.type": "single-node",
"DISABLE_INSTALL_DEMO_CONFIG": True,
"DISABLE_SECURITY_PLUGIN": True,
},
"entrypoint": ["/usr/share/opensearch/opensearch-docker-entrypoint.sh", "opensearch"],
}]
def waitForOpenSearch():
return [{
"name": "wait-for-open-search",
"image": OC_CI_ALPINE,
"commands": [
"bash -c '" +
"until curl -sS \"http://open-search:9200/_cat/health?h=status\" | grep \"green\\|yellow\"; do\n" +
" echo \"Waiting for http://open-search:9200 to be healthy...\"\n" +
" sleep 5\n" +
"done\n" +
"echo \"http://open-search:9200 healthy...\"\n" +
"'",
],
}]
def logRequests():
return [{
"name": "api-test-failure-logs",
+71
View File
@@ -1,5 +1,76 @@
# Changelog
## [7.5.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.5.0) - 2026-08-25
### ❤️ Thanks to all contributors! ❤️
@AlexAndBear, @JammingBen, @Svanvith, @aduffeck, @butonic, @fschade, @junkerderprovinz, @kulmann, @maki5, @pbleser-oc, @rhafer, @saw-jan, @schweigisito, @v-scharf
### ✅ Tests
- test(api): update php test dependencies [[#3335](https://github.com/opencloud-eu/opencloud/pull/3335)]
- fix(acceptance): fix running acceptance tests against host on Linux [[#3358](https://github.com/opencloud-eu/opencloud/pull/3358)]
- fix(graph): adding the same user as multiple members in a group (#3354) [[#3356](https://github.com/opencloud-eu/opencloud/pull/3356)]
- test(api): fix share role update test scenario [[#3322](https://github.com/opencloud-eu/opencloud/pull/3322)]
- test: add api tests for cross-space search index mutation [[#3320](https://github.com/opencloud-eu/opencloud/pull/3320)]
- ci: run search acceptance tests against OpenSearch in nightly [[#3302](https://github.com/opencloud-eu/opencloud/pull/3302)]
- api-test: notification settings and getting email notifications [[#3281](https://github.com/opencloud-eu/opencloud/pull/3281)]
- api-test: add posixfs scan and consistency CLI tests [[#3263](https://github.com/opencloud-eu/opencloud/pull/3263)]
- api-test: replace sleeps with WaitHelper poll for async state [[#3239](https://github.com/opencloud-eu/opencloud/pull/3239)]
### 📈 Enhancement
- feat(thumbnails): extend list of default resolutions [[#3386](https://github.com/opencloud-eu/opencloud/pull/3386)]
- ability to disable grpc and/or event consumer for event history service [[#3279](https://github.com/opencloud-eu/opencloud/pull/3279)]
- feat(graph): add LibreGraphContentType on drive [[#3355](https://github.com/opencloud-eu/opencloud/pull/3355)]
- feat: update space template image [[#3324](https://github.com/opencloud-eu/opencloud/pull/3324)]
- feat(web): add rclone-crypt to default apps [[#3313](https://github.com/opencloud-eu/opencloud/pull/3313)]
- enhance: send events for adding/removing favourite items [[#3229](https://github.com/opencloud-eu/opencloud/pull/3229)]
- allow tuning the proxies http client [[#3278](https://github.com/opencloud-eu/opencloud/pull/3278)]
- feat(web): add yjsServerUrl config [[#3259](https://github.com/opencloud-eu/opencloud/pull/3259)]
- feat(posixfs): #3182 add basepath option in the "posixfs scan" command [[#3235](https://github.com/opencloud-eu/opencloud/pull/3235)]
### 🐛 Bug Fixes
- fix(search): refresh the opensearch index after a write [[#3388](https://github.com/opencloud-eu/opencloud/pull/3388)]
- fix(thumbnails): respect the requested height in libvips builds [[#3377](https://github.com/opencloud-eu/opencloud/pull/3377)]
- refactor datagateway into proxy middleware [[#3289](https://github.com/opencloud-eu/opencloud/pull/3289)]
- Only log a debug message when an item is still in processing state [[#3158](https://github.com/opencloud-eu/opencloud/pull/3158)]
- fix(postprocessing): retry publishing events instead of killing the server [[#3347](https://github.com/opencloud-eu/opencloud/pull/3347)]
- actually log error on exit [[#3344](https://github.com/opencloud-eu/opencloud/pull/3344)]
- fix(activitylog): log missing parent id cache entry at debug level [[#3325](https://github.com/opencloud-eu/opencloud/pull/3325)]
- fix restore file version for shared resource [[#3268](https://github.com/opencloud-eu/opencloud/pull/3268)]
- Fix missing favorite flag on opensearch hits [[#3252](https://github.com/opencloud-eu/opencloud/pull/3252)]
### 📚 Documentation
- docs: clarify custom role bootstrap behavior [[#3366](https://github.com/opencloud-eu/opencloud/pull/3366)]
### 📦️ Dependencies
- [full-ci] chore: bump web to v7.4.0 [[#3399](https://github.com/opencloud-eu/opencloud/pull/3399)]
- build(deps): bump google.golang.org/grpc from 1.83.0 to 1.83.1 [[#3396](https://github.com/opencloud-eu/opencloud/pull/3396)]
- build(deps): bump github.com/stretchr/testify from 1.12.0 to 1.12.1 [[#3395](https://github.com/opencloud-eu/opencloud/pull/3395)]
- build(deps): bump github.com/go-chi/chi/v5 from 5.3.1 to 5.3.2 [[#3394](https://github.com/opencloud-eu/opencloud/pull/3394)]
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.29.0 to 2.30.0 [[#3380](https://github.com/opencloud-eu/opencloud/pull/3380)]
- build(deps): bump golang.org/x/image from 0.44.0 to 0.45.0 [[#3381](https://github.com/opencloud-eu/opencloud/pull/3381)]
- build(deps): bump github.com/beevik/etree from 1.7.0 to 1.7.1 [[#3382](https://github.com/opencloud-eu/opencloud/pull/3382)]
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.14.4 to 2.14.5 [[#3357](https://github.com/opencloud-eu/opencloud/pull/3357)]
- build(deps): bump go.opentelemetry.io/contrib/zpages from 0.69.0 to 0.70.0 [[#3360](https://github.com/opencloud-eu/opencloud/pull/3360)]
- build(deps): bump github.com/sirupsen/logrus from 1.9.4 to 1.10.0 [[#3359](https://github.com/opencloud-eu/opencloud/pull/3359)]
- chore: bump reva to latest main [[#3329](https://github.com/opencloud-eu/opencloud/pull/3329)]
- build(deps): bump github.com/stretchr/testify from 1.11.1 to 1.12.0 [[#3341](https://github.com/opencloud-eu/opencloud/pull/3341)]
- build(deps): bump golang.org/x/net from 0.57.0 to 0.58.0 [[#3342](https://github.com/opencloud-eu/opencloud/pull/3342)]
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.32.0 to 2.32.1 [[#3340](https://github.com/opencloud-eu/opencloud/pull/3340)]
- build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch from 0.43.0 to 0.44.0 [[#3303](https://github.com/opencloud-eu/opencloud/pull/3303)]
- build(deps): bump github.com/opencloud-eu/libre-graph-api-go [[#3326](https://github.com/opencloud-eu/opencloud/pull/3326)]
- build(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace from 1.44.0 to 1.45.0 [[#3306](https://github.com/opencloud-eu/opencloud/pull/3306)]
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.69.0 to 0.70.0 [[#3305](https://github.com/opencloud-eu/opencloud/pull/3305)]
- build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.44.0 to 1.45.0 [[#3280](https://github.com/opencloud-eu/opencloud/pull/3280)]
- build(deps): bump github.com/rogpeppe/go-internal from 1.15.0 to 1.16.0 [[#3250](https://github.com/opencloud-eu/opencloud/pull/3250)]
- build(deps): bump github.com/kovidgoyal/imaging from 1.8.22 to 1.8.23 [[#3135](https://github.com/opencloud-eu/opencloud/pull/3135)]
- build(deps): bump go.opentelemetry.io/otel/trace from 1.44.0 to 1.45.0 [[#3266](https://github.com/opencloud-eu/opencloud/pull/3266)]
## [7.4.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.4.0) - 2026-08-03
### ❤️ Thanks to all contributors! ❤️
+3 -21
View File
@@ -68,11 +68,8 @@ OC_MODULES = \
protogen
# bin file definitions
PHP_CS_FIXER=php -d zend.enable_gc=0 vendor-bin/opencloud-codestyle/vendor/bin/php-cs-fixer
PHP_CODESNIFFER=vendor-bin/php_codesniffer/vendor/bin/phpcs
PHP_CODEBEAUTIFIER=vendor-bin/php_codesniffer/vendor/bin/phpcbf
PHAN=php -d zend.enable_gc=0 vendor-bin/phan/vendor/bin/phan
PHPSTAN=php -d zend.enable_gc=0 vendor-bin/phpstan/vendor/bin/phpstan
PHP_CODESNIFFER=vendor-bin/opencloud-codestyle/vendor/bin/phpcs
PHP_CODEBEAUTIFIER=vendor-bin/opencloud-codestyle/vendor/bin/phpcbf
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
include .bingo/Variables.mk
@@ -319,30 +316,15 @@ ci-format: $(BUILDIFIER)
$(BUILDIFIER) --mode=fix .woodpecker.star
.PHONY: test-php-style
test-php-style: vendor-bin/opencloud-codestyle/vendor vendor-bin/php_codesniffer/vendor
$(PHP_CS_FIXER) fix -v --diff --allow-risky yes --dry-run
test-php-style: vendor-bin/opencloud-codestyle/vendor
$(PHP_CODESNIFFER) --cache --runtime-set ignore_warnings_on_exit --standard=phpcs.xml tests/acceptance tests/acceptance/TestHelpers
.PHONY: test-php-style-fix
test-php-style-fix: vendor-bin/opencloud-codestyle/vendor
$(PHP_CS_FIXER) fix -v --diff --allow-risky yes
$(PHP_CODEBEAUTIFIER) --cache --runtime-set ignore_warnings_on_exit --standard=phpcs.xml tests/acceptance
.PHONY: vendor-bin-codestyle
vendor-bin-codestyle: vendor-bin/opencloud-codestyle/vendor
.PHONY: vendor-bin-codesniffer
vendor-bin-codesniffer: vendor-bin/php_codesniffer/vendor
vendor-bin/opencloud-codestyle/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/opencloud-codestyle/composer.lock
composer bin opencloud-codestyle install --no-progress
vendor-bin/opencloud-codestyle/composer.lock: vendor-bin/opencloud-codestyle/composer.json
@echo opencloud-codestyle composer.lock is not up to date.
vendor-bin/php_codesniffer/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/php_codesniffer/composer.lock
composer bin php_codesniffer install --no-progress
vendor-bin/php_codesniffer/composer.lock: vendor-bin/php_codesniffer/composer.json
@echo php_codesniffer composer.lock is not up to date.
-62
View File
@@ -1,62 +0,0 @@
# Table of Contents
{{ range . -}}
* [Changelog for {{ .Version }}](#changelog-for-{{ .Version | replace "." ""}}-{{ .Date | lower -}})
{{ end -}}
{{ $allVersions := . }}
{{- range $index, $changes := . }}{{ with $changes -}}
{{ if gt (len $allVersions) 1 }}
# Changelog for [{{ .Version }}] ({{ .Date }})
The following sections list the changes for {{ .Version}}.
{{/* creating version compare links */ -}}
{{ $next := add1 $index -}}
{{ if ne (len $allVersions) $next -}}
{{ $previousVersion := (index $allVersions $next).Version -}}
{{ if eq .Version "unreleased" -}}
[{{ .Version}}]: https://github.com/opencloud-eu/opencloud/compare/v{{ $previousVersion }}...master
{{ else -}}
[{{ .Version}}]: https://github.com/opencloud-eu/opencloud/compare/v{{ $previousVersion }}...v{{ .Version}}
{{ end -}}
{{ end -}}
{{- /* last version managed by calens, end of the loop */ -}}
{{ if eq .Version "0.1.0" -}}
[{{ .Version }}]: https://github.com/opencloud-eu/opencloud/compare/94f19e653e30cdf16dcf23dbaf36c6d753d37ae9...v{{ .Version }}
{{ end -}}
{{ else -}}
# Changes in {{ .Version}}
{{ end -}}
## Summary
{{ range $entry := .Entries }}{{ with $entry }}
* {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }})
{{- end }}{{ end }}
## Details
{{ range $entry := .Entries }}{{ with $entry }}
* {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }})
{{ range $par := .Paragraphs -}}
{{/* Workaround for keeping lists inside of changelog items well formatted */ -}}
{{ if hasPrefix "*" $par }}
{{ $par | replace " *" "\n *" }}
{{- else }}
{{ wrapIndent $par 80 3 -}}
{{ end }}
{{ end -}}
{{ range $url := .IssueURLs }}
{{ $url -}}
{{ end -}}
{{ range $url := .PRURLs }}
{{ $url -}}
{{ end -}}
{{ range $url := .OtherURLs }}
{{ $url -}}
{{ end }}
{{ end }}{{ end -}}
{{ end }}{{ end -}}
-16
View File
@@ -1,16 +0,0 @@
# Changelog
We are using [calens](https://github.com/restic/calens) to properly generate a
changelog before we are tagging a new release. To get an idea how this could
look like <https://github.com/restic/restic/tree/master/changelog> would be the
best reference.
## Create changelog items
Create a file according to the template for each changelog in the unreleased folder.
The following change types are possible:
- Bugfix (general Bugfix)
- Enhancement (new feature)
- Change (breaking change)
- Security (security related issues)
-15
View File
@@ -1,15 +0,0 @@
Bugfix: Fix behavior for foobar (in present tense)
We've fixed the behavior for foobar, a long-standing annoyance for users. The
text should be wrapped at 80 characters length.
The text in the paragraphs is written in past tense. The last section is a list
of issue URLs, PR URLs and other URLs. The first issue ID (or the first PR ID,
in case there aren't any issue links) is used as the primary ID.
https://github.com/opencloud-eu/opencloud/pull/55555
https://github.com/opencloud-eu/opencloud/issues/1234
Note: Possible keywords are Bugfixes (for bug fixes), Enhancement (for new features),
Change (for breaking changes), Security (for security related topics)
-8
View File
@@ -1,8 +0,0 @@
## Release, Date, Type, Title, Primary ID, Primary URL
{{ range . -}}
{{ $v := .Version -}}
{{ $d := .Date -}}
{{ range $entry := .Entries -}}
{{ $v }},{{ $d }},{{ .Type }},'{{ .Title }}',{{ .PrimaryID }},{{ .PrimaryURL }}
{{ end -}}
{{ end -}}
View File
Whitespace-only changes.
@@ -1,7 +0,0 @@
Enhancement: Add WAYF configuration for reva OCM service
Add WAYF configuration support for the Reva OCM service,
enabling federation discovery functionality for Open Cloud Mesh.
This includes configuration for federations file storage and invite accept dialog URL.
https://github.com/opencloud-eu/opencloud/pull/1714
@@ -1,9 +0,0 @@
Bugfix: Don't re-escape notification email vars for each recipient
When a notification went to several recipients in one call (e.g. a group
invite to a space), each recipient past the first got subjects and bodies
with one extra layer of HTML escaping. escapeStringMap mutated its input
map, and the recipient loop reused the same map across iterations. It now
returns a new map.
https://github.com/opencloud-eu/opencloud/issues/2804
@@ -0,0 +1,21 @@
Bugfix: Retry publishing postprocessing events before giving up
A single transient failure while publishing an event to the event system took
the whole server down. The postprocessing service treated every publish error
as fatal and called log.Fatal, which exits the process and so also stopped all
the other services running in the same binary. A burst of uploads was enough to
run into one nats publish timeout and lose the server with it.
Publishing is now retried using the same exponential backoff that is already
used for failed postprocessing steps. Between the attempts the source event is
marked as in progress and the backoff is capped at half the ack wait, so the
event should not be redelivered to another worker while we are still retrying.
The number of retries is configurable via POSTPROCESSING_PUBLISH_MAX_RETRIES.
Should all attempts fail, the source event is no longer acknowledged. Before,
the event was acknowledged even though its successor was never published, so
the upload was left half processed in the store and did not recover on restart.
https://github.com/opencloud-eu/opencloud/issues/3271
https://github.com/opencloud-eu/opencloud/issues/2232
https://github.com/opencloud-eu/opencloud/issues/2422
+4 -3
View File
@@ -2,7 +2,7 @@
"name": "opencloud-eu/opencloud",
"config": {
"platform": {
"php": "8.3"
"php": "8.4"
},
"vendor-dir": "./vendor-php",
"allow-plugins": {
@@ -11,11 +11,12 @@
},
"require-dev": {
"ext-simplexml": "*",
"bamarni/composer-bin-plugin": "^1.8"
"bamarni/composer-bin-plugin": "^1.9"
},
"extra": {
"bamarni-bin": {
"bin-links": false
"bin-links": false,
"forward-command": false
}
}
}
@@ -45,3 +45,4 @@ directives:
style-src:
- '''self'''
- '''unsafe-inline'''
- 'blob:'
@@ -42,3 +42,4 @@ directives:
style-src:
- '''self'''
- '''unsafe-inline'''
- 'blob:'
@@ -0,0 +1,226 @@
---
title: "5. Unified Search Index Mapping"
---
* Status: accepted
* Deciders: @aduffeck, @butonic, @dschmidt, @fschade
* Date: 2026-04-23, accepted and updated to the implemented state 2026-08-31
Reference: implemented by https://github.com/opencloud-eu/opencloud/pull/3345 (reflection-based mapping, search siblings, shared query lowering) and https://github.com/opencloud-eu/opencloud/pull/3197 (schema versioning and startup checks). https://github.com/opencloud-eu/opencloud/pull/2659 was the original proof-of-concept.
## Context and Problem Statement
This section describes the state at decision time (April 2026); the implementation has since resolved the problems listed here.
The search service supports two backends, bleve (embedded) and
OpenSearch (external). Each backend currently carries its own,
independently maintained description of the index layout:
- The bleve backend hand-builds a document mapping that explicitly
declares only Name, Tags, Favorites and Content. Everything else,
including the entire facet block (audio, image, photo, location),
is left to bleve's dynamic mapping.
- The OpenSearch backend ships a static JSON template that covers a
similar but not identical subset, plus a few OpenSearch-specific
primitives (path_hierarchy analyzer, wildcard MimeType). It does
not list the facet sub-fields either; they are produced by
OpenSearch's dynamic templating at first write.
- The graph DriveItem assembly path keeps its own private copy of a
reflection-based walker to turn CS3 ArbitraryMetadata back into
typed libregraph facets, parallel to the search service's
reflection helpers but maintained separately.
- The bleve KQL compiler keeps a hand-maintained set of field names
whose query values need to be pre-lowercased, with a comment that
literally says "Keep in sync with index.go".
The current implementation has three concrete problems:
1. **The two backends do not behave the same.** Both rely on their
own implicit defaults for fields that are not explicitly
declared. The inferred shapes differ: bleve produces keyword-
analyzed text, OpenSearch produces a `text + keyword` multi-field
with auto-detected dates. Nobody has written down which behavior
is the intended one. Two concrete instances surfaced while building
#2659:
- **mtime** is stored as an RFC3339 string. OpenSearch's dynamic
mapping auto-detects it as `date`; bleve leaves it `keyword`. So
`mtime:>...` is a chronological range on OpenSearch but a
lexicographic string compare on bleve.
- **name/tags**: bleve indexes a single lowercase token (exact or
wildcard match only); OpenSearch word-tokenizes, so a bare
`name:report` matches "My Report.txt" on OpenSearch but not on
bleve.
2. **Drift risk.** The OpenSearch JSON template is a subset of what
actually gets indexed. Even where it overlaps with the bleve
mapping it diverges on analyzer choices. Because the facet
fields were not reachable from user queries at the time (no dot
syntax in the KQL compilers, no facet exposure on the hit and
REPORT paths), the divergence has been invisible, but it would
surface the moment the first working cross-backend facet query
landed.
3. **Per-facet cost.** Adding a new facet (motionPhoto, etc.)
requires coordinated edits across the proto message, both backend
mappings, the bleve hit converters, the OpenSearch convert
closures, the search service's metadata persistence, the graph
DriveItem assembly, and the KQL compiler's lowercasing set. Most
of those edits are boilerplate following a copy-paste pattern.
Adding a genuinely new index capability (geopoint, wildcard,
...) means wiring it in at every one of those sites, and there
is no single place to hook a type-specific adapter.
### A note on backwards compatibility
That the facet fields were unreachable at decision time has a
useful corollary for this ADR: **changing the indexed shape of the
facet fields cannot break any existing client of the search
service**, because no client could successfully read them. The behavior changes
discussed below are therefore additive in a literal sense; nothing
that works today stops working as a result.
## Decision Drivers
* **Predictable OpenCloud API behavior independent of backend.**
Consumers of the search service should be able to rely on the
documented behavior of the API, not on which backend happens to
be configured. Today the same query can give different results
depending on whether bleve or OpenSearch is wired in (bleve's
dynamic default is `keyword`, exact match; OpenSearch's dynamic
default is `text + keyword`, also matches sub-tokens of a
string). That is backend-implementation leakage, and trying to
keep the two implicit defaults synchronized has not worked.
* Single source of truth for the indexed schema, so the two backends
cannot drift silently again.
* Reduce the per-facet cost so future facets (motionPhoto and
whatever comes next) can be added with minimal boilerplate.
* Establish a single place to hook index-type-specific behavior, so
a new capability needs to be implemented at most once per backend
and then becomes available for any field uniformly.
* A one-time reindex is an acceptable upgrade path. Both bleve and
OpenSearch store their mapping alongside the data; existing
indexes keep serving queries against their stored shape without
any automatic reshaping. Benefiting from the new behavior is done
by creating a fresh index and re-ingesting, which is the normal
reindex flow, rather than by inventing migration tooling.
## Considered Options
### Option 1: Do nothing, keep relying on implicit backend defaults
Accept that bleve and OpenSearch each fall back to their own
dynamic-mapping defaults for whatever is not explicitly declared,
and treat the observable search behavior of OpenCloud as "whatever
the configured backend happens to do". Adding a facet stays a
copy-paste coordination across half a dozen sites; the existing
divergence between bleve (keyword) and OpenSearch (`text + keyword`
multi-field plus auto-date detection) stays silently in place
until a working query actually reaches the diverging field and
returns different answers on the two backends.
Low upfront work, but it makes the OpenCloud API behavior a
function of the backend rather than a contract, and it keeps the
per-facet boilerplate cost for every new field.
### Option 2: Generate one backend's mapping from the other
Treat one backend as canonical (likely bleve, because Go types) and
derive the other. Partial answer; it still does not help the reader
path or the graph walker, and still leaves per-facet boilerplate in
non-mapping code.
### Option 3: A struct-driven mapping (chosen)
Let the Go struct that represents an indexed document, together
with a small overrides map, be the single source of truth. A
reflection-based helper walks the struct via json tags and emits
each backend's index mapping. The same definition drives the
write-time path, the hit-decoding path, and the query compiler's
case-folding rules. Any future field follows one declaration in
one place and falls through the whole pipeline consistently.
## Decision Outcome
Adopt Option 3. The Go struct that represents an indexed document,
together with a small overrides map, becomes the single source of
truth for the search index. The bleve and OpenSearch index
mappings, the write-time conversion, the hit-decoding path, and
the query compiler's case-folding rules are all derived from that
same definition. Drift between backends is prevented by
construction, because there is no second place to edit.
The overrides surface stays small. Each entry declares one of a
handful of things per field: a semantic type for fields whose
intent cannot be inferred from the Go type (for example a path-
analyzed field, a fulltext field, a geopoint field), or search-
behavior flags (case-insensitivity, word breaking, inclusion in
the catch-all field). Any field that needs something beyond the
inferred defaults gets one line in the overrides map and that one
line flows through every derived piece. Overrides are validated at
startup so a typo fails loudly instead of silently disabling a
setting.
A practical consequence of having one place to hook things: when a
new capability is needed (a geopoint representation, a sibling
field for a different aggregation behavior, a different analyzer
for a class of fields, ...) it can be implemented once per backend
in the central pipeline. After that, turning the capability on for
a specific field is a single override entry, and both backends
adopt it the same way. This ADR does not decide which capabilities
to add, only that they will land in this uniform shape rather than
through coordinated per-site edits.
### Facet values are indexed as case-preserving keywords
All facet sub-fields, meaning any leaf inside `audio`, `photo`, `image`, `location` and the facets that followed (`video`, `motionPhoto`, `livePhoto`), keep a case-preserving keyword as their stored base field on both backends. The raw value the extractor saw, or the CS3 ArbitraryMetadata string, is what lands in the index, and it is what returning, sorting and aggregations read.
This is the single intended semantic for facets across bleve and
OpenSearch, and it is driven by what aggregations need.
Aggregation buckets ("group all files by `audio.artist`", "list
distinct `photo.cameraMake`") return bucket keys drawn from the
indexed terms. If the indexing analyzer lowercases (OpenSearch's
default `text + keyword` multi-field against the text leg, or a
`lowercaseKeyword`-style analyzer), the buckets come back lower-
cased: a distinct-artists query would answer `motörhead` and
`queen` instead of the original display casings, and two tag
writers using `Motörhead` versus `MOTÖRHEAD` would collapse into a
single bucket labelled `motörhead`. For a metadata display use
case (thumbnails, facet filters in the UI, distinct lists) that
behavior is not what we want.
Searching is layered on top as exactly the strict superset the proposal reserved for later, and it shipped with the implementation: every keyword field additionally gets search-only sibling fields derived from the same definition, a `_lowercase` keyword sibling (doc values disabled; serves wildcards and `=` whole-value matches) and a `_words` text sibling (`words` analyzer: dots to spaces, unicode tokenization, lowercasing, no stemming; serves token and phrase matches). Case-insensitive, word-broken search is the default for every keyword field including facets; fields opt out per override where that is wrong: opaque ids (`ID`, `RootID`, `ParentID`, `Favorites`, `livePhoto.contentId`), the POSIX `Path`, the normalized `MimeType`, and `Content`, which is a fulltext field of its own. Aggregation buckets keep their display casing because they read the base field, never the siblings.
The query side derives from the same source: the shared lowering pass resolves field names case-insensitively, folds values and routes each match to the right sibling (wildcards to `_lowercase`, tokens and phrases to `_words`, `=` as a whole-value term on `_lowercase`), and both backend compilers consume that one decision. The engine parity suite pins the resulting behavior against bleve and OpenSearch, so a divergence fails CI instead of surfacing in production. The case-sensitivity alignment started in #2633 is completed by deriving both sides from the same source.
### Schema versioning and upgrades
Index names carry a schema version derived from the single `search.SchemaVersion` constant (`opencloud-resource-v4`, `bleve-v4`). On startup the service classifies the stored mapping against the code: additive changes (new fields, unchanged analyzers) are reconciled in place without a version bump, breaking changes make the service refuse to start and name the reindex steps. The upgrade path is a plain reindex (`opencloud search index --all-spaces`) into the new versioned index; older indexes stay untouched and can be deleted afterwards (services/search/MIGRATION.md). Golden mapping tests on both backends pin the rendered mappings and reuse the same classifier to tell a contributor whether a change needs only a golden regeneration or a version bump.
### Known trade-off
The write-time pipeline produces the document as a generic map via
a json round-trip. The OpenSearch write path already does the
equivalent today via the same json-based conversion helper, so
that path is unchanged. The bleve write path, which previously
handed the struct directly to bleve's reflective indexer, now goes
through the same map-producing step and pays roughly the same
cost. On hot paths (initial indexing of a large space) this is
measurable but not significant; if it ever matters, a direct
reflection walker can replace the json round-trip without changing
any call site.
### Follow-ups out of scope for this ADR
- **WebDAV REPORT facet exposure.** The current webdav search
endpoint renders none of the facet fields back to the client.
This is a missing feature, not a regression of the proposal;
its natural resolution is to let the graph-search endpoint
(proposed in #3211) take over once graph search lands.
- **Graph search hit conversion.** Graph search (#3211) translates
proto hits back into libregraph DriveItems with the same
facet-copy helper the search service uses internally.
- **reva's PROPFIND facet listing** uses its own hand-maintained
per-facet key lists. reva deliberately does not depend on the
libregraph Go types, so unifying those key sets is a reva-side
decision tracked separately.
- **Write-path performance.** The json round-trip in the bleve
write path is an optional optimisation target with no call-site
impact when it lands.
+61 -62
View File
@@ -1,17 +1,17 @@
module github.com/opencloud-eu/opencloud
go 1.25.8
go 1.25.9
require (
dario.cat/mergo v1.0.2
github.com/CiscoM31/godata v1.0.11
github.com/KimMachineGun/automemlimit v0.7.5
github.com/KimMachineGun/automemlimit v1.0.0
github.com/Masterminds/semver v1.5.0
github.com/MicahParks/keyfunc/v2 v2.1.0
github.com/Nerzal/gocloak/v13 v13.9.0
github.com/bbalet/stopwords v1.0.0
github.com/beevik/etree v1.7.0
github.com/blevesearch/bleve/v2 v2.6.0
github.com/beevik/etree v1.7.1
github.com/blevesearch/bleve/v2 v2.6.1
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.20.0
github.com/cs3org/go-cs3apis v0.0.0-20260424072047-8d9ef7076ae9
@@ -20,9 +20,8 @@ require (
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
github.com/gabriel-vasile/mimetype v1.4.15
github.com/ggwhite/go-masker v1.1.0
github.com/go-chi/chi/v5 v5.3.1
github.com/go-chi/chi/v5 v5.3.2
github.com/go-chi/render v1.0.3
github.com/go-jose/go-jose/v3 v3.0.5
github.com/go-ldap/ldap/v3 v3.4.14
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
github.com/go-micro/plugins/v4/client/grpc v1.2.1
@@ -43,48 +42,49 @@ require (
github.com/google/uuid v1.6.0
github.com/gookit/config/v2 v2.2.9
github.com/gorilla/mux v1.8.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0
github.com/invopop/validation v0.8.0
github.com/jellydator/ttlcache/v2 v2.11.1
github.com/jellydator/ttlcache/v3 v3.4.1
github.com/jinzhu/now v1.1.5
github.com/justinas/alice v1.2.0
github.com/kovidgoyal/imaging v1.8.22
github.com/kovidgoyal/imaging v1.8.23
github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69
github.com/libregraph/idm v0.5.0
github.com/libregraph/lico v0.67.0
github.com/mna/pigeon v1.3.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/nats-io/nats-server/v2 v2.14.4
github.com/nats-io/nats.go v1.52.0
github.com/nats-io/nats-server/v2 v2.14.5
github.com/nats-io/nats.go v1.53.1
github.com/olekukonko/tablewriter v1.1.4
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.32.0
github.com/onsi/ginkgo/v2 v2.32.1
github.com/onsi/gomega v1.42.1
github.com/open-policy-agent/opa v1.19.0
github.com/open-policy-agent/opa v1.19.1
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d
github.com/opencloud-eu/reva/v2 v2.48.0
github.com/opensearch-project/opensearch-go/v4 v4.6.0
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067
github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774
github.com/opensearch-project/opensearch-go/v4 v4.7.3
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1
github.com/pkg/xattr v0.4.12
github.com/prometheus/client_golang v1.24.1
github.com/prometheus/client_model v0.6.2
github.com/r3labs/sse/v2 v2.10.0
github.com/riandyrn/otelchi v0.12.3
github.com/rogpeppe/go-internal v1.15.0
github.com/rogpeppe/go-internal v1.16.0
github.com/rs/cors v1.11.1
github.com/rs/zerolog v1.35.1
github.com/shamaton/msgpack/v2 v2.4.1
github.com/sirupsen/logrus v1.9.4
github.com/sirupsen/logrus v1.10.1
github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.12.1
github.com/test-go/testify v1.1.4
github.com/testcontainers/testcontainers-go v0.43.0
github.com/testcontainers/testcontainers-go/modules/opensearch v0.43.0
github.com/testcontainers/testcontainers-go v0.44.0
github.com/testcontainers/testcontainers-go/modules/opensearch v0.44.0
github.com/thejerf/suture/v4 v4.0.6
github.com/tidwall/gjson v1.19.0
github.com/tidwall/sjson v1.2.5
@@ -94,25 +94,25 @@ require (
github.com/xhit/go-simple-mail/v2 v2.16.0
go-micro.dev/v4 v4.11.0
go.etcd.io/bbolt v1.5.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0
go.opentelemetry.io/contrib/zpages v0.69.0
go.opentelemetry.io/otel v1.44.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0
go.opentelemetry.io/otel/sdk v1.44.0
go.opentelemetry.io/otel/trace v1.44.0
golang.org/x/crypto v0.54.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0
go.opentelemetry.io/contrib/zpages v0.71.0
go.opentelemetry.io/otel v1.46.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0
go.opentelemetry.io/otel/sdk v1.46.0
go.opentelemetry.io/otel/trace v1.46.0
golang.org/x/crypto v0.55.0
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/image v0.44.0
golang.org/x/net v0.57.0
golang.org/x/image v0.45.0
golang.org/x/net v0.58.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.22.0
golang.org/x/term v0.45.0
golang.org/x/text v0.40.0
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa
google.golang.org/grpc v1.83.0
google.golang.org/protobuf v1.36.11
golang.org/x/text v0.41.0
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d
google.golang.org/grpc v1.83.2
google.golang.org/protobuf v1.36.12
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2
@@ -141,13 +141,13 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bits-and-blooms/bitset v1.24.2 // indirect
github.com/blevesearch/bleve_index_api v1.3.11 // indirect
github.com/blevesearch/geo v0.2.5 // indirect
github.com/blevesearch/go-faiss v1.1.0 // indirect
github.com/blevesearch/bleve_index_api v1.4.1 // indirect
github.com/blevesearch/geo v0.2.6 // indirect
github.com/blevesearch/go-faiss v1.1.5 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.2.0 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.4.10 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
@@ -158,7 +158,7 @@ require (
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
github.com/blevesearch/zapx/v17 v17.1.2 // indirect
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
@@ -190,23 +190,24 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/ebitengine/purego v0.10.1 // indirect
github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/emvi/iso-639-1 v1.1.1 // indirect
github.com/evanphx/json-patch/v5 v5.5.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/gdexlab/go-render v1.0.1 // indirect
github.com/go-acme/lego/v4 v4.4.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-git/go-git/v5 v5.19.1 // indirect
github.com/go-git/go-git/v5 v5.19.2 // indirect
github.com/go-jose/go-jose/v3 v3.0.5 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
@@ -215,7 +216,7 @@ require (
github.com/go-micro/plugins/v4/events/natsjs v1.2.2 // indirect
github.com/go-micro/plugins/v4/store/nats-js v1.2.1 // indirect
github.com/go-micro/plugins/v4/store/redis v1.2.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
@@ -255,7 +256,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/juliangruber/go-intersect v1.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.19.1 // indirect
github.com/klauspost/compress v1.19.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/kovidgoyal/go-parallel v1.1.1 // indirect
@@ -271,7 +272,7 @@ require (
github.com/libregraph/oidc-go v1.1.0 // indirect
github.com/longsleep/go-metrics v1.0.0 // indirect
github.com/longsleep/rndm v1.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
@@ -290,10 +291,10 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/api v1.54.2 // indirect
github.com/moby/moby/client v0.4.0 // indirect
github.com/moby/moby/api v1.55.0 // indirect
github.com/moby/moby/client v0.5.0 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/sequential v0.7.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
@@ -323,8 +324,6 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect
github.com/prometheus/alertmanager v0.33.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/prometheus/statsd_exporter v0.22.8 // indirect
@@ -344,7 +343,7 @@ require (
github.com/sergi/go-diff v1.4.0 // indirect
github.com/sethvargo/go-diceware v0.6.0 // indirect
github.com/sethvargo/go-password v0.4.0 // indirect
github.com/shirou/gopsutil/v4 v4.26.5 // indirect
github.com/shirou/gopsutil/v4 v4.26.6 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 // indirect
@@ -356,8 +355,8 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/tklauser/go-sysconf v0.4.0 // indirect
github.com/tklauser/numcpus v0.12.0 // indirect
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
github.com/trustelem/zxcvbn v1.0.1 // indirect
github.com/urfave/cli/v2 v2.27.7 // indirect
@@ -377,19 +376,19 @@ require (
go.etcd.io/etcd/client/v3 v3.6.13 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.37.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.47.0 // indirect
golang.org/x/tools v0.48.0 // indirect
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/ini.v1 v1.67.3 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
+121 -120
View File
@@ -70,8 +70,8 @@ github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CiscoM31/godata v1.0.11 h1:w7y8twuW02LdH6mak3/GJ5i0GrCv2IoZUJVqa/g5Yeo=
github.com/CiscoM31/godata v1.0.11/go.mod h1:ZMiT6JuD3Rm83HEtiTx4JEChsd25YCrxchKGag/sdTc=
github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk=
github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
github.com/KimMachineGun/automemlimit v1.0.0 h1:+MqlvDE/pkJNjk1rU+O14QsH8k10nJAD0frB0lsxyvw=
github.com/KimMachineGun/automemlimit v1.0.0/go.mod h1:n+BSXxQWDFS1DKh67Rqo0lgTsowsg6x65ak5uyngML0=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
@@ -132,8 +132,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W
github.com/aws/aws-sdk-go v1.37.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/bbalet/stopwords v1.0.0 h1:0TnGycCtY0zZi4ltKoOGRFIlZHv0WqpoIGUsObjztfo=
github.com/bbalet/stopwords v1.0.0/go.mod h1:sAWrQoDMfqARGIn4s6dp7OW7ISrshUD8IP2q3KoqPjc=
github.com/beevik/etree v1.7.0 h1:xjBk9O4p4x7D1YajePjfLzdaFC4/uYUENA7P0pv6gXA=
github.com/beevik/etree v1.7.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=
github.com/beevik/etree v1.7.1 h1:xBlSFAjlMfZkrCNKCEdZ2MSN9pfQYeRBaISDnAVi4Ek=
github.com/beevik/etree v1.7.1/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -146,22 +146,22 @@ github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngE
github.com/bits-and-blooms/bitset v1.24.2 h1:M7/NzVbsytmtfHbumG+K2bremQPMJuqv1JD3vOaFxp0=
github.com/bits-and-blooms/bitset v1.24.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blevesearch/bleve/v2 v2.6.0 h1:Cyd3dd4q5tCbOV8MnKUVRUDYMHOir9xn12NZzXVSEd4=
github.com/blevesearch/bleve/v2 v2.6.0/go.mod h1:gLmI8lWgHgrIYf7UpUX7JISI1CaqC6VScu46mHThuAY=
github.com/blevesearch/bleve_index_api v1.3.11 h1:x29vbV8OjWfLcrDVd7Lr1q+BkLNS0JWNEig0MCVnKH4=
github.com/blevesearch/bleve_index_api v1.3.11/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
github.com/blevesearch/geo v0.2.5 h1:yJg9FX1oRwLnjXSXF+ECHfXFTF4diF02Ca/qUGVjJhE=
github.com/blevesearch/geo v0.2.5/go.mod h1:Jhq7WE2K6mJTx1xS44M2pUO6Io+wjCSHh1+co3YOgH4=
github.com/blevesearch/go-faiss v1.1.0 h1:xM7Jc0ZUCv5lssG9Ohj3Jv0SdTpxcUABU1dDt9XVsc4=
github.com/blevesearch/go-faiss v1.1.0/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/bleve/v2 v2.6.1 h1:47vLskRTqxvQEtxVPYHjf5KpOgzD2msslXFjvUQCgWQ=
github.com/blevesearch/bleve/v2 v2.6.1/go.mod h1:Dvvx6ZoEBTOj6RSzfk0lEz0wce/qhe2yOUubXeuzd2c=
github.com/blevesearch/bleve_index_api v1.4.1 h1:CYIyecFlI+/RYjzUm+NmDjYbSvk870Bb7f+Vl4b12q8=
github.com/blevesearch/bleve_index_api v1.4.1/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
github.com/blevesearch/geo v0.2.6 h1:7K1oyQKYlauC+mJuo2AfNPyjN/4mihEoJMfyClVH1Mo=
github.com/blevesearch/geo v0.2.6/go.mod h1:6qzVUiB4BK47QkSZcRqiXEP2W3EeXuzM5XFTF8AdZ8A=
github.com/blevesearch/go-faiss v1.1.5 h1:/IU5lkOahH9Ghfk9n3F6N0XD7PYVXZJWmNDc9TtXuco=
github.com/blevesearch/go-faiss v1.1.5/go.mod h1:w3W9AiWsFRGVaMG+/cmJi7iHEAuGyC6blsgO1EzCK/M=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
github.com/blevesearch/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI=
github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0=
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 h1:GlMzW08hcsM3DnLUxhyF/1PcDal1qtvvIuytuph5djw=
github.com/blevesearch/scorch_segment_api/v2 v2.4.7/go.mod h1://IJ7tG3QCf0cWW/aVSXqy77tc1AvLu3fcJLYEvOAFs=
github.com/blevesearch/scorch_segment_api/v2 v2.4.10 h1:C3873+iWZ0YJM2ijaSHhJJzSvD4x1k+5UaQdGygZVhM=
github.com/blevesearch/scorch_segment_api/v2 v2.4.10/go.mod h1:WUUkAocbkDlNK/kgAE13NvS9oxe+u618mYZ8sOvcCc4=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
@@ -182,8 +182,8 @@ github.com/blevesearch/zapx/v15 v15.4.3 h1:iJiMJOHrz216jyO6lS0m9RTCEkprUnzvqAI2l
github.com/blevesearch/zapx/v15 v15.4.3/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaTbp0Po0=
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
github.com/blevesearch/zapx/v17 v17.1.2 h1:avbOk2igaASNoiy0BE/jPgcxAnRI2PGeydeP4hg7Ikk=
github.com/blevesearch/zapx/v17 v17.1.2/go.mod h1:WQObxKrqUX7cd0G1GMvDfc/bmZzQvoy7APOPimx7DiI=
github.com/blevesearch/zapx/v17 v17.2.3 h1:UYYJPAt5b2tVxldx5h0jmv23RMsg8/UZKFVya7v92po=
github.com/blevesearch/zapx/v17 v17.2.3/go.mod h1:r7mb4QWbDQSkbAnOjCb9iCfkcrzajB4yBdJpuBIo/fE=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
@@ -302,8 +302,8 @@ github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -313,8 +313,8 @@ github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e/go.mod h1:Byz
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc h1:6IxmRbXV8WXVkcYcTzkU219A3UZeNMX/e6X2sve1wXA=
github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc/go.mod h1:FdVN2WHg7zOHhJ7kZQdDorfFhIfqZaHttjAzDDvAXHE=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
@@ -336,8 +336,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
@@ -371,8 +371,8 @@ github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-chi/chi/v5 v5.3.2 h1:5YQkICvTCSZ25hoRsyJazN0scjzKGiu4VAUc7H1o1nY=
github.com/go-chi/chi/v5 v5.3.2/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
@@ -383,8 +383,8 @@ github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmm
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -436,8 +436,9 @@ github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0 h1:UWBUYtMXC
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0/go.mod h1:8BYxs/wEE4ZJayHZQffw4A8s9rcPumyoNms0hYoNocM=
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0 h1:e2hgtWMNqJ3DmbMt9ZxzmH/BkVAw9Xg23l6CHrXQfKw=
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0/go.mod h1:BBqL7ckGNb7rFfk3vU2Yj/CILVsz/WF19CkAyveQl8A=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -547,7 +548,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -607,8 +607,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vb
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@@ -713,8 +713,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -730,8 +730,8 @@ github.com/kovidgoyal/go-parallel v1.1.1 h1:1OzpNjtrUkBPq3UaqrnvOoB2F9RttSt811ui
github.com/kovidgoyal/go-parallel v1.1.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
github.com/kovidgoyal/go-shm v1.0.0 h1:HJEel9D1F9YhULvClEHJLawoRSj/1u/EDV7MJbBPgQo=
github.com/kovidgoyal/go-shm v1.0.0/go.mod h1:Yzb80Xf9L3kaoB2RGok9hHwMIt7Oif61kT6t3+VnZds=
github.com/kovidgoyal/imaging v1.8.22 h1:CtpoRXQpS79xxJsKu8+LUJJE/0i4FLquJZy0QH+QNlM=
github.com/kovidgoyal/imaging v1.8.22/go.mod h1:y8wo4JTv4D+skbtQf6fHg8nA1qtagvCcn8J2Nu5k2Jg=
github.com/kovidgoyal/imaging v1.8.23 h1:4WsboyQ/E8tic2kX49P93Rvscg33SPsCwTBG5EZZF54=
github.com/kovidgoyal/imaging v1.8.23/go.mod h1:k3Iot3H0v2rSWXIxQISfT3e9wIryqzAHLHCuhG7z9lM=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -781,8 +781,8 @@ github.com/longsleep/go-metrics v1.0.0 h1:o2A6Dbu4MhLpZuL444WFoZzM7X7igewrj2Mouw
github.com/longsleep/go-metrics v1.0.0/go.mod h1:w6QO1LBkVla70FZrrF6XcB0YN+jTEYugjkn3+6RYTSM=
github.com/longsleep/rndm v1.2.0 h1:wPl+kIMyIUTUFW5+2b327DmM1Rlj+gmexsiyOTB7rzM=
github.com/longsleep/rndm v1.2.0/go.mod h1:5qyvM6CXNteKgz6djqqwZOP4+KcPsewrfKyLWd1dCFY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
@@ -859,14 +859,14 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw=
github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g=
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc=
github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s=
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
@@ -892,10 +892,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nats-io/jwt/v2 v2.8.2 h1:XXRgB60MSTnqsRwejQurVDs/hcv2dkt+86GjI+I/bMc=
github.com/nats-io/jwt/v2 v2.8.2/go.mod h1:Ag/56sq9OblL4JgdYufDd16Egb17Kr/8WwwuO/forVc=
github.com/nats-io/nats-server/v2 v2.14.4 h1:efgjZ8cdExAKRuqSg8UPJFprb+l7NlBtSDPhDlw3rO4=
github.com/nats-io/nats-server/v2 v2.14.4/go.mod h1:BltdpOYestjbtQSnVO2zGHdg5SGBZjt+GYTgB9LZq/I=
github.com/nats-io/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc=
github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
github.com/nats-io/nats-server/v2 v2.14.5 h1:M6yeo/Xb7khi97RSEVELof3DForDqmYza3P4tHCPFWw=
github.com/nats-io/nats-server/v2 v2.14.5/go.mod h1:1D3iocrisKvWaD1B/imqarTqmaGrWMqALMLbEDo3v7Q=
github.com/nats-io/nats.go v1.53.1 h1:Otsq3uLc/kLdjmkNHkXH0jBqwUquwdKFoe3fq6/3/Xo=
github.com/nats-io/nats.go v1.53.1/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg=
github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
@@ -927,31 +927,31 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E=
github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw=
github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
github.com/open-policy-agent/opa v1.19.0 h1:+j2OCsjMezZEML2T1lI9giJdGJS/PL1XFKgkHPGIhpo=
github.com/open-policy-agent/opa v1.19.0/go.mod h1:pb6Y6klyf7X7X8uXNDflruA9dQC2gMqWROXI5w/kvv0=
github.com/open-policy-agent/opa v1.19.1 h1:aB1nOncChnTbQurjRQVJnjTJxditt8VqszlbaM3GGKU=
github.com/open-policy-agent/opa v1.19.1/go.mod h1:pb6Y6klyf7X7X8uXNDflruA9dQC2gMqWROXI5w/kvv0=
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M=
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+lP5lUUIzjRGDg93WrQfZJZCaV1ZP3KeyXi8bzY=
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d h1:JcqGDiyrcaQwVyV861TUyQgO7uEmsjkhfm7aQd84dOw=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.48.0 h1:G/4Jbv0DWWOfA5u5DtV0CB75pi9Wwtj7JkJQOEBvErs=
github.com/opencloud-eu/reva/v2 v2.48.0/go.mod h1:ZCo/xQM6if+upZa7rJCmdifZ/Y5XHLCrscHytC39yI4=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067 h1:UkNMKauyJAzY6RE6mmthz9bQZLYkbvBuApm7ZDCparE=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774 h1:8N42Pwzyafdxq66zYP6kJ1CH/QBRpxovQXq/0Kt6bJY=
github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774/go.mod h1:Frg+UWnVcSy+412UB3l2LcD0KY8ZNu1samimwkNywbg=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opensearch-project/opensearch-go/v4 v4.6.0 h1:Ac8aLtDSmLEyOmv0r1qhQLw3b4vcUhE42NE9k+Z4cRc=
github.com/opensearch-project/opensearch-go/v4 v4.6.0/go.mod h1:3iZtb4SNt3IzaxavKq0dURh1AmtVgYW71E4XqmYnIiQ=
github.com/opensearch-project/opensearch-go/v4 v4.7.3 h1:JzETy7bYnnSDj4gueUh8t4EYBhs9rhKsgeVsoul77rA=
github.com/opensearch-project/opensearch-go/v4 v4.7.3/go.mod h1:+iikkyLrVC8ZvyfKv2sua1Ze1LbpWL7eZe4IkHqNGtg=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
@@ -1001,8 +1001,6 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:Om
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/alertmanager v0.33.1 h1:PJHGvTdb8Q0ZEpJnWff120WxB2kxIScLa707AaIudTo=
github.com/prometheus/alertmanager v0.33.1/go.mod h1:V06Uc8EZ5X5wLOJRGhtXx+EE2LgrinFIADbKWMVm1RY=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@@ -1063,8 +1061,8 @@ github.com/riandyrn/otelchi v0.12.3 h1:KW9gA+97d6mExk8vbh0FRwb2biUvpyYlc8YuxP1Oa
github.com/riandyrn/otelchi v0.12.3/go.mod h1:weZZeUJURvtCcbWsdb7Y6F8KFZGedJlSrgUjq9VirV8=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -1108,15 +1106,15 @@ github.com/shamaton/msgpack/v2 v2.4.1 h1:JtJ141QoQ3NqgPDsjq2v9VXlaON8SiQOwEaoNLE
github.com/shamaton/msgpack/v2 v2.4.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM=
github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q=
github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
@@ -1173,8 +1171,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@@ -1183,10 +1182,10 @@ github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhg
github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/testcontainers/testcontainers-go v0.43.0 h1:oEQx5MW2DGd9z3AeEQfB2lPM0eLs7ztyaGRu75bFo5A=
github.com/testcontainers/testcontainers-go v0.43.0/go.mod h1:+VxkT2NQnKOZPKi6praMuMKYHYyOGXr0XSBSlSMCzFo=
github.com/testcontainers/testcontainers-go/modules/opensearch v0.43.0 h1:a1ipjF7d/VxPX1dgVPIk4F+t6YkgMbE2OtBuRQCHJt8=
github.com/testcontainers/testcontainers-go/modules/opensearch v0.43.0/go.mod h1:OWSeUDiGMUy30iMsAltIJIo9uh/CleLv6KyxjYOsgR8=
github.com/testcontainers/testcontainers-go v0.44.0 h1:/Fwh6HY1mIikhnm9e7HwoxGycx0lzRAE0f5VQpjFxzI=
github.com/testcontainers/testcontainers-go v0.44.0/go.mod h1:IcnwQrYTO86xHXu5bvMaBH7ATlbS3Qn1M1QWW3c66rE=
github.com/testcontainers/testcontainers-go/modules/opensearch v0.44.0 h1:iuBDo8U8Nwt0z+bxEmU+c7jS9hw81nPL9uGkkFOcXTo=
github.com/testcontainers/testcontainers-go/modules/opensearch v0.44.0/go.mod h1:8Qw29ffsCPzhomxqoeGhpj3J3Lbsrv0FsOUWeOjTl+I=
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
@@ -1205,10 +1204,10 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
@@ -1235,8 +1234,8 @@ github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs=
github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ=
github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM=
github.com/wI2L/jsondiff v0.7.1 h1:Fg9+yj+1/x3UtPBJhR91TKEzRkrEEWcAcLbg9dzEaNM=
github.com/wI2L/jsondiff v0.7.1/go.mod h1:yAt2W7U6Jd4HK0RA8DGSGk0zDtfEtOUUJVnH/xICpjo=
github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8=
github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
@@ -1292,30 +1291,30 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
go.opentelemetry.io/contrib/zpages v0.69.0 h1:YQC1PumJq6lUGQNrLW14ID9a0dXSBcbC/aC6VRSIARU=
go.opentelemetry.io/contrib/zpages v0.69.0/go.mod h1:FGvUcMGN5atRzUIgUsqOi+MiMvlQsfbuYATBHPP4cGs=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 h1:bl2S7Ubua0Nms+D/gAmznQTd4dxxMA93aKbcpKqiTCs=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0 h1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0/go.mod h1:DqEFwLumhzMBDQv9PcWbyoDxHI/4lAk6CM4nJBH39sc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU=
go.opentelemetry.io/contrib/zpages v0.71.0 h1:OrQSEmdlfknIusEyGj/t30uBR7MnbmGcCRBt5ov8/rA=
go.opentelemetry.io/contrib/zpages v0.71.0/go.mod h1:C3IIpqkQbjswcSREqxQLyEapF0SI9bqwJpxpjwtzFrE=
go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 h1:lsA/S1bxgdbyFGkTj+3meEdJ6ADVU7QoFstV6MXgE68=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0/go.mod h1:L7u+MirGoB1bjeLH66+xDykF4RC8C3RN7lIFpBiewUo=
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM=
go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE=
go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4=
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -1333,8 +1332,9 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1354,8 +1354,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1370,8 +1370,8 @@ golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJk
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1393,8 +1393,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1445,8 +1445,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1545,6 +1545,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1576,8 +1577,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1638,8 +1639,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1701,10 +1702,10 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 h1:mJiOtnGp0k/BcSgdu03G2NwnscCfCH+h2QKUBZr18KI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc=
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1720,8 +1721,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU=
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -1738,8 +1739,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
@@ -0,0 +1,44 @@
package eventstest
import (
"encoding/json"
"reflect"
"github.com/google/uuid"
rev "github.com/opencloud-eu/reva/v2/pkg/events"
microevents "go-micro.dev/v4/events"
)
func NewTestBus() TestBus {
return TestBus(make(chan rev.Event))
}
type TestBus chan rev.Event
func (tb TestBus) Consume(_ string, _ ...microevents.ConsumeOption) (<-chan microevents.Event, error) {
ch := make(chan microevents.Event)
go func() {
for ev := range tb {
b, _ := json.Marshal(ev.Event)
ch <- microevents.Event{
Payload: b,
Metadata: map[string]string{
rev.MetadatakeyEventID: ev.ID,
rev.MetadatakeyEventType: ev.Type,
},
}
}
}()
return ch, nil
}
func (tb TestBus) Publish(e any) string {
ev := rev.Event{
ID: uuid.New().String(),
Type: reflect.TypeOf(e).String(),
Event: e,
}
tb <- ev
return ev.ID
}
@@ -0,0 +1,143 @@
package metricstest
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// copied and adapted from Prometheus testutil.ToFloat64(), since we don't import that package
func collect(c prometheus.Collector) []prometheus.Metric {
result := []prometheus.Metric{}
ch := make(chan prometheus.Metric)
done := make(chan struct{})
go func() {
for m := range ch {
result = append(result, m)
}
close(done)
}()
c.Collect(ch)
close(ch)
<-done
return result
}
func RequireIsNotSet(t require.TestingT, c prometheus.Collector, msgAndArgs ...any) {
if h, ok := t.(interface{ Helper() }); ok {
h.Helper()
}
if !IsNotSet(t, c, msgAndArgs) {
t.FailNow()
}
}
func IsNotSet(t assert.TestingT, c prometheus.Collector, msgAndArgs ...any) bool {
if h, ok := t.(interface{ Helper() }); ok {
h.Helper()
}
m := collect(c)
if len(m) > 0 {
return assert.Fail(t, "Metric exists while expected to not exist", msgAndArgs)
} else {
return true
}
}
func RequireEqual(t require.TestingT, expected float64, c prometheus.Collector, msgAndArgs ...any) {
if h, ok := t.(interface{ Helper() }); ok {
h.Helper()
}
if !Equal(t, expected, c, msgAndArgs) {
t.FailNow()
}
}
// copied and adapted from Prometheus testutil.ToFloat64(), since we don't import that package
func Equal(t assert.TestingT, expected float64, c prometheus.Collector, msgAndArgs ...any) bool {
if h, ok := t.(interface{ Helper() }); ok {
h.Helper()
}
m := collect(c)
if !assert.Len(t, m, 1, msgAndArgs...) {
return false
}
pb := &dto.Metric{}
err := m[0].Write(pb)
if !assert.NoError(t, err, msgAndArgs...) {
return false
}
if pb.Gauge != nil {
return assert.Equal(t, expected, pb.Gauge.GetValue(), msgAndArgs...)
} else if pb.Counter != nil {
return assert.Equal(t, expected, pb.Counter.GetValue(), msgAndArgs...)
} else if pb.Untyped != nil {
return assert.Equal(t, expected, pb.Untyped.GetValue(), msgAndArgs...)
} else {
return assert.Fail(t, fmt.Sprintf("collected a non-gauge/counter/untyped metric: %s", pb), msgAndArgs...)
}
}
func RequireEqualWithLabels(t require.TestingT, expectedValue float64, expectedLabels map[string]string, c prometheus.Collector, msgAndArgs ...any) {
if h, ok := t.(interface{ Helper() }); ok {
h.Helper()
}
if !EqualWithLabels(t, expectedValue, expectedLabels, c, msgAndArgs) {
t.FailNow()
}
}
func EqualWithLabels(t assert.TestingT, expectedValue float64, expectedLabels map[string]string, c prometheus.Collector, msgAndArgs ...any) bool {
if h, ok := t.(interface{ Helper() }); ok {
h.Helper()
}
m := collect(c)
if !assert.Len(t, m, 1, "collected %d metrics instead of exactly 1", len(m)) {
return false
}
pb := &dto.Metric{}
err := m[0].Write(pb)
if !assert.NoError(t, err) {
return false
}
if pb.Gauge != nil {
if !assert.Equal(t, expectedValue, pb.Gauge.GetValue()) {
return false
}
} else if pb.Counter != nil {
if !assert.Equal(t, expectedValue, pb.Counter.GetValue()) {
return false
}
} else if pb.Untyped != nil {
if !assert.Equal(t, expectedValue, pb.Untyped.GetValue()) {
return false
}
} else {
return assert.Fail(t, "collected a non-gauge/counter/untyped metric: %s", pb)
}
if !assert.NotNil(t, pb.Label) {
return false
}
actualLabels := map[string]string{}
for _, label := range pb.Label {
if !assert.NotNil(t, label) {
return false
}
if !assert.NotNil(t, label.Name) {
return false
}
if !assert.NotNil(t, label.Value) {
return false
}
actualLabels[*label.Name] = *label.Value
}
return assert.Equal(t, expectedLabels, actualLabels, msgAndArgs)
}
+138
View File
@@ -0,0 +1,138 @@
[env]
GO_VERSION = "{{ exec(command='./.mise-go-version.sh') }}"
[tools]
go = "{{ env.GO_VERSION }}"
node = "24"
pnpm = "11.1.3"
"go:github.com/go-delve/delve/cmd/dlv" = "1.27.1"
"aqua:nats-io/natscli" = "0.4.0"
k6 = "2.2.0"
ginkgo = "latest"
[tasks.build]
description = "build"
run = "make -C opencloud build"
[tasks."build:debug"]
description = "build with debug symbols"
run = "make -C opencloud build-debug"
[tasks."docker:build"]
description = "docker image opencloudeu/opencloud:dev"
run = "make -C opencloud dev-docker"
[tasks."docker:build:multiarch"]
description = "docker image for amd64 + arm64"
depends = ["gen"]
run = "make -C opencloud dev-docker-multiarch"
[tasks."docker:build:debug"]
description = "docker image with delve"
run = "make -C opencloud debug-docker"
[tasks.serve]
description = "start the server"
depends = ["build"]
run = "opencloud/bin/opencloud server"
[tasks."serve:init"]
description = "create the local config"
depends = ["build"]
run = "opencloud/bin/opencloud init"
[tasks."serve:debug"]
description = "start the server under delve"
depends = ["build:debug"]
run = "dlv exec opencloud/bin/opencloud-debug -- server"
[tasks.test]
description = "go test"
run = "go test -tags disable_crypt ./..."
[tasks."test:changed"]
description = "go test, changed packages"
run = """
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
files=$(git diff --name-only --diff-filter=ACM $(git merge-base "$base" HEAD) -- '*.go' | grep -v vendor/ || true)
[ -n "$files" ] || exit 0
go test -tags disable_crypt $(echo "$files" | xargs -n1 dirname | sort -u | sed 's|^|./|')
"""
[tasks."test:race"]
description = "go test + race"
run = "go test -race -tags disable_crypt ./..."
[tasks."test:coverage"]
description = "go test + coverage"
run = ["make test", "make go-coverage"]
[tasks.check]
description = "all checks"
depends = ["check:fmt", "check:lint", "check:vendor", "check:env-vars", "test"]
[tasks."check:fmt"]
description = "gofmt"
run = """
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
files=$(git diff --name-only --diff-filter=ACM $(git merge-base "$base" HEAD) -- '*.go' | grep -v vendor/ || true)
[ -n "$files" ] || exit 0
out=$(gofmt -s -l $files)
[ -z "$out" ] || { echo "$out"; exit 1; }
"""
[tasks."check:lint"]
description = "golangci-lint"
run = """
set -e
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
gobin=$(go env GOBIN)
[ -n "$gobin" ] || gobin="$(go env GOPATH)/bin"
bin="$gobin/golangci-lint-$(awk '/golangci-lint v/ {print $3}' .bingo/golangci-lint.mod)"
[ -x "$bin" ] || make -s golangci-lint >/dev/null
exec "$bin" run --modules-download-mode vendor --timeout 15m0s --new-from-merge-base "$base"
"""
[tasks."check:vendor"]
description = "vendor matches go.mod"
run = "GOWORK=off go list -mod=vendor ./... >/dev/null"
[tasks."check:env-vars"]
description = "env var annotations"
run = "make check-env-var-annotations"
[tasks.fix]
description = "gofmt + lint fixes"
run = [{ task = "fix:fmt" }, { task = "fix:lint" }]
[tasks."fix:fmt"]
description = "gofmt -w"
run = """
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
files=$(git diff --name-only --diff-filter=ACM $(git merge-base "$base" HEAD) -- '*.go' | grep -v vendor/ || true)
[ -z "$files" ] || gofmt -s -w $files
"""
[tasks."fix:lint"]
description = "golangci-lint --fix"
run = "make golangci-lint-fix"
[tasks."pre:commit"]
description = "before commit"
run = [{ task = "fix:fmt" }, { task = "check:lint" }, { task = "test:changed" }]
[tasks."pre:push"]
description = "before push"
run = [{ task = "tidy" }, { task = "fix:fmt" }, { task = "check" }]
[tasks.gen]
description = "assets & mocks"
run = "make generate"
[tasks."gen:protobuf"]
description = "protobuf"
run = "make protobuf"
[tasks.tidy]
description = "tidy + vendor"
run = ["GOWORK=off go mod tidy", "GOWORK=off go mod vendor"]
+4 -1
View File
@@ -11,7 +11,10 @@ RUN --mount=type=bind,target=/build,rw \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache \
GOOS="${TARGETOS:-linux}" GOARCH="${TARGETARCH:-amd64}" ; \
make -C ${SRCDIR:-.}/opencloud release-linux-docker-${TARGETARCH} ENABLE_VIPS=true DIST=/dist
make -C ${SRCDIR:-.}/opencloud release-linux-docker-${TARGETARCH} \
ENABLE_VIPS=true DIST=/dist \
VERSION="${VERSION}" EDITION="${EDITION}" \
${STRING:+STRING="${STRING}"}
FROM alpine:3.24
ARG VERSION
+188 -76
View File
@@ -55,11 +55,23 @@ func init() {
// scanCmd performs a posixfs id cache warmup scan
func scanCmd(ocCfg *config.Config) *cobra.Command {
cmd := &cobra.Command{
Use: "scan",
scanCmd := &cobra.Command{
Use: "scan [path ...]",
Short: "Perform a filesystem scan and update the ID and filemetadata cache",
PreRunE: func(cmd *cobra.Command, args []string) error {
Long: `Perform a filesystem scan and update the ID and filemetadata cache.
You can specify one or more paths to limit the scope of the scan.
If no path is provided, the whole storage is checked, starting at the storage root directory.
The provided arguments determines the scope of the check:
- a storage root: the whole storage (all personal and project spaces) is scanned
- a space root: only that space is scanned
- a file or directory: only that single resource is scanned (and its children, if it is a directory)
Any specified file or directory must be underneath the storage root directory and if that is not the case,
the command is aborted with an error before performing any scanning.`,
Args: cobra.ArbitraryArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := parser.ParseConfig(ocCfg, true); err != nil {
return configlog.ReturnError(err)
}
@@ -76,91 +88,110 @@ func scanCmd(ocCfg *config.Config) *cobra.Command {
os.Exit(1)
}
haltOnError, err := cmd.Flags().GetBool("halt-on-error")
if err != nil {
return err
}
storageRoot := cfg.Drivers.Posix.Root
root := storageRoot
defaultRoot := true
if v, err := cmd.Flags().GetString("basepath"); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse command-line parameter '--basepath': %v\n", err)
os.Exit(1)
} else if v != "" {
root = v
if !filepath.IsAbs(v) {
if v, err = filepath.Abs(v); err != nil {
fmt.Fprintf(os.Stderr, "Failed to make the basepath mentioned using '--basepath' absolute: %v\n", err)
os.Exit(1)
} else {
root = v
paths := []string{storageRoot}
if len(args) > 0 {
paths = []string{}
for _, v := range args {
path := v
if !filepath.IsAbs(path) {
if v, err := filepath.Abs(path); err != nil {
fmt.Fprintf(os.Stderr, "Failed to make the specified path %q absolute: %v\n", v, err)
os.Exit(1)
} else {
path = v
}
}
} else {
root = v
}
root = filepath.Clean(root)
defaultRoot = false
}
// ensure that, if a basepath has been indicated, it is under the storage root
if !defaultRoot {
if contained, err := filepathx.IsSameOrContainedBy(storageRoot, root); err != nil {
fmt.Fprintf(os.Stderr, "Failed to determine whether the specified basepath %q is contained by the storage root %q: %v\n", root, storageRoot, err)
os.Exit(1)
} else if !contained {
fmt.Fprintf(os.Stderr, "The specified basepath %q is neither the storage root %q, nor a subdirectory thereof, nor a file underneath it\n", root, storageRoot)
os.Exit(1)
// not ensuring whether the path is under the storage root here, will be done when iterating over them
path = filepath.Clean(path)
paths = append(paths, path)
}
}
// We want to initialize the driver but disable scanfs on boot, so we can trigger it manually afterwards
drivers := revaconfig.StorageProviderDrivers(cfg)
drivers["posix"] = revaconfig.Posix(cfg, false, false)
var scan func(path string) error = nil
{
// We want to initialize the driver but disable scanfs on boot, so we can trigger it manually afterwards
drivers := revaconfig.StorageProviderDrivers(cfg)
drivers["posix"] = revaconfig.Posix(cfg, false, false)
var fsStream events.Stream
var err error
fsStream, err = event.NewStream(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create event stream for posix driver: %v\n", err)
os.Exit(1)
}
log := logger("posixfs")
var fsStream events.Stream
var err error
fsStream, err = event.NewStream(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create event stream for posix driver: %v\n", err)
os.Exit(1)
}
log := logger("posixfs")
if !defaultRoot {
log = log.With().Str("basepath", root).Logger()
f, ok := registry.NewFuncs["posix"]
if !ok {
fmt.Fprintf(os.Stderr, "posix driver not found in registry\n")
os.Exit(1)
}
fs, err := f(drivers["posix"].(map[string]any), fsStream, &log)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize filesystem driver '%s': %v\n", cfg.Driver, err)
return err
}
cacher, ok := fs.(IDCacher)
if !ok {
fmt.Fprintf(os.Stderr, "The posix driver does not expose WarmupIDCache.\n")
os.Exit(1)
}
scan = func(path string) error {
err := cacher.WarmupIDCache(path, true, false)
if err != nil {
logFailure("Error scanning path '%s': %v", path, err)
}
return err
}
}
f, ok := registry.NewFuncs["posix"]
if !ok {
fmt.Fprintf(os.Stderr, "posix driver not found in registry\n")
os.Exit(1)
}
errors := processPosixFsResources(paths, !haltOnError,
func(path string) error {
fmt.Println("Scanning personal spaces...")
return scan(path)
},
func(path string) error {
fmt.Println("Scanning project spaces...")
return scan(path)
},
func(path string) error {
fmt.Printf("Scanning space '%s'...\n", path)
return scan(path)
},
func(path string) error {
fmt.Printf("Scanning '%s'...\n", path)
return scan(path)
},
)
fs, err := f(drivers["posix"].(map[string]any), fsStream, &log)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize filesystem driver '%s': %v\n", cfg.Driver, err)
return err
}
cacher, ok := fs.(IDCacher)
if !ok {
fmt.Fprintf(os.Stderr, "The posix driver does not expose WarmupIDCache.\n")
os.Exit(1)
}
if defaultRoot {
fmt.Println("Starting posixfs scan...")
if len(errors) == 0 {
fmt.Println("Scan completed successfully.")
return nil
} else {
fmt.Printf("Starting posixfs scan at '%s'...\n", root)
plural := "s"
if len(errors) == 1 {
plural = ""
}
verb := "completed"
if haltOnError {
verb = "aborted"
}
return fmt.Errorf("scan %s with %d error%s", verb, len(errors), plural)
}
err = cacher.WarmupIDCache(root, true, false)
if err != nil {
fmt.Fprintf(os.Stderr, "Scan failed: %v\n", err)
return err
}
fmt.Println("Scan completed successfully.")
return nil
},
}
cmd.Flags().StringP("basepath", "p", "", "the root under which to scan files, which may be a directory or a file (when omitted, detaults to using the storage root)")
return cmd
scanCmd.Flags().BoolP("halt-on-error", "E", false, "Halt at once when an error occurs when processing one of the paths (default behaviour is to keep going and attempt to process all paths).")
return scanCmd
}
// consistencyCmd returns a command to check the consistency of the posixfs storage.
@@ -195,7 +226,10 @@ The provided arguments determines the scope of the check:
args = []string{cfg.Drivers.Posix.Root}
}
log := logger("posixfs")
recalculateChecksums, _ := cmd.Flags().GetBool("fix-checksums")
recalculateChecksums, err := cmd.Flags().GetBool("fix-checksums")
if err != nil {
return err
}
drivers := revaconfig.StorageProviderDrivers(cfg)
drivers["posix"] = revaconfig.Posix(cfg, false, false)
@@ -215,7 +249,6 @@ The provided arguments determines the scope of the check:
},
}
consCmd.Flags().Bool("fix-checksums", false, "Recalculate and fix the file checksums. This reads every file and can be slow on large storages.")
return consCmd
}
@@ -254,3 +287,82 @@ func isSpaceRoot(path string) bool {
spaceID, err := xattr.Get(path, prefixes.SpaceIDAttr)
return err == nil && len(spaceID) > 0
}
// iterates over a list of paths and processes them all, using the appropriate function
// depending on the type of resource
//
// note that whenever an error occurs, it collects that error and continues processing
// subsequent paths, and then returns a slice of errors at the end (or an empty slice
// if no errors occured)
func processPosixFsResources(paths []string,
keepGoing bool,
personalSpaceDir func(string) error,
projectSpaceDir func(string) error,
spaceRoot func(string) error,
entity func(string) error,
) []error {
// no need to guard this with a mutex for now, since the implementation is not parallelized
errors := []error{}
for _, path := range paths {
rootPath, err := findStorageRoot(path)
if err != nil {
errors = append(errors, err)
logFailure("error: %s", err)
if keepGoing {
continue
} else {
return errors
}
}
path = filepath.Clean(path)
if _, err := os.Stat(path); err != nil {
errors = append(errors, err)
logFailure("error accessing '%s': %w", path, err)
if keepGoing {
continue
} else {
return errors
}
}
contained, _ := filepathx.IsSameOrContainedBy(rootPath, path)
switch {
case path == rootPath:
if err := personalSpaceDir(filepath.Join(path, "users")); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
if err := projectSpaceDir(filepath.Join(path, "projects")); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
case isSpaceRoot(path):
if err := spaceRoot(path); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
case contained:
if err := entity(path); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
default:
err := fmt.Errorf("error: the provided path '%s' is neither a space root nor contained by the storage root '%s'", path, rootPath)
errors = append(errors, err)
logFailure(err.Error())
if !keepGoing {
return errors
}
}
}
return errors
}
+17 -25
View File
@@ -14,7 +14,6 @@ import (
"strings"
"time"
"github.com/opencloud-eu/opencloud/pkg/x/path/filepathx"
storageUsersConfig "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
@@ -35,38 +34,31 @@ type consistencyChecker struct {
// given path determines the scope of the check: the whole storage, a single
// space or a single entity within a space.
func (c *consistencyChecker) Check(paths []string) error {
for _, path := range paths {
rootPath, err := findStorageRoot(path)
if err != nil {
return err
}
path = filepath.Clean(path)
if _, err := os.Stat(path); err != nil {
return fmt.Errorf("error accessing '%s': %w", path, err)
}
contained, _ := filepathx.IsSameOrContainedBy(rootPath, path)
switch {
case path == rootPath:
_ = processPosixFsResources(paths, true,
func(path string) error {
fmt.Println("Checking personal spaces...")
c.checkSpaces(filepath.Join(path, "users"))
c.checkSpaces(path)
return nil
},
func(path string) error {
fmt.Println("Checking project spaces...")
c.checkSpaces(filepath.Join(path, "projects"))
case isSpaceRoot(path):
c.checkSpaces(path)
return nil
},
func(path string) error {
fmt.Printf("Checking space '%s'...\n", path)
c.checkSpace(path)
case contained:
return nil
},
func(path string) error {
if c.ignorer.IsIgnored(path) {
continue
return nil
}
fmt.Printf("Checking '%s'...\n", path)
c.checkEntity(path)
default:
return fmt.Errorf("the provided path '%s' is neither a space root nor contained by the storage root '%s'", path, rootPath)
}
}
return nil
},
)
if c.restartRequired {
fmt.Println("\n\n ⚠️ Please restart your openCloud instance to apply changes.")
}
+46 -17
View File
@@ -25,6 +25,8 @@ var (
_nodesGlobPattern = "spaces/*/*/nodes/"
)
const posixDriver = "posix"
// RevisionsCommand is the entrypoint for the revisions command.
func RevisionsCommand(cfg *config.Config) *cobra.Command {
revCmd := &cobra.Command{
@@ -86,23 +88,28 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command {
mechanism = "glob"
}
var posix = cfg.StorageUsers.Driver == posixDriver
var ch <-chan string
switch mechanism {
default:
fallthrough
case "glob":
p := generatePath(basePath, rid)
p := generatePath(basePath, rid, posix)
if rid.GetOpaqueId() == "" {
p = filepath.Join(p, "*/*/*/*/*")
}
ch = revisions.Glob(p)
case "workers":
p := generatePath(basePath, rid)
p := generatePath(basePath, rid, posix)
ch = revisions.GlobWorkers(p, "/*", "/*/*/*/*")
case "list":
p := filepath.Join(basePath, "spaces")
p := basePath
if !posix {
p = filepath.Join(basePath, "spaces")
}
if rid != nil {
p = generatePath(basePath, rid)
p = generatePath(basePath, rid, posix)
}
ch = revisions.List(p, 10)
}
@@ -144,22 +151,44 @@ func printResults(countFiles, countBlobs, countRevisions int, dryRun bool) {
}
}
func generatePath(basePath string, rid *provider.ResourceId) string {
if rid == nil {
return filepath.Join(basePath, _nodesGlobPattern)
}
func generatePath(basePath string, rid *provider.ResourceId, posix bool) string {
// decomposedfs and posix store the revisions of a node at different
// locations on disk, so the path has to be built per driver:
// - decomposedfs: <basePath>/spaces/<pathified spaceID>/nodes/<pathified nodeID>.REV.<ts>
// - posix: <basePath>/<users|projects>/<spaceID>/.oc-nodes/<pathified nodeID>.REV.<ts>
if posix {
if rid == nil {
return filepath.Join(basePath, "*", "*", ".oc-nodes")
}
sid := lookup.Pathify(rid.GetSpaceId(), 1, 2)
if sid == "" {
return ""
}
nid := lookup.Pathify(rid.GetOpaqueId(), 4, 2)
if nid != "" {
return filepath.Join(basePath, "*", "*", ".oc-nodes", nid+"*")
}
nid := lookup.Pathify(rid.GetOpaqueId(), 4, 2)
if nid == "" {
return filepath.Join(basePath, "spaces", sid, "nodes")
}
if rid.GetSpaceId() == "" {
return ""
}
return filepath.Join(basePath, "*", rid.GetSpaceId(), ".oc-nodes")
} else {
// decomposedfs
if rid == nil {
return filepath.Join(basePath, "spaces", sid, "nodes", nid+"*")
return filepath.Join(basePath, _nodesGlobPattern)
}
sid := lookup.Pathify(rid.GetSpaceId(), 1, 2)
if sid == "" {
return ""
}
nid := lookup.Pathify(rid.GetOpaqueId(), 4, 2)
if nid == "" {
return filepath.Join(basePath, "spaces", sid, "nodes")
}
return filepath.Join(basePath, "spaces", sid, "nodes", nid+"*")
}
}
func init() {
+22 -6
View File
@@ -1,7 +1,9 @@
package command
import (
"context"
"errors"
"time"
"github.com/spf13/viper"
@@ -21,6 +23,14 @@ import (
"github.com/spf13/cobra"
)
// need to be discussed, for now I will let it here
//
// Problem:
// in reva on CleanupStaleShares call there is migrations invokation, which at leat in tests takes some time,
// reva code was modified to wait until migrations are done, to prevent cases when migrations are stuck and the
// this executions is not returned this timeout is needed
const cleanupTimeout = 1 * time.Minute
// SharesCommand is the entrypoint for the groups command.
func SharesCommand(cfg *config.Config) *cobra.Command {
sharesCmd := &cobra.Command{
@@ -120,7 +130,11 @@ func cleanup(_ *cobra.Command, cfg *config.Config) error {
}
serviceUserCtx = l.WithContext(serviceUserCtx)
mgr.(*jsoncs3.Manager).CleanupStaleShares(serviceUserCtx)
cleanupCtx, cancel := context.WithTimeout(serviceUserCtx, cleanupTimeout)
defer cancel()
if err := mgr.(*jsoncs3.Manager).CleanupStaleShares(cleanupCtx); err != nil {
return configlog.ReturnError(err)
}
return nil
}
@@ -158,11 +172,13 @@ func revaShareConfig(cfg *sharing.Config) map[string]any {
"machine_auth_apikey": cfg.UserSharingDrivers.CS3.SystemUserAPIKey,
},
"jsoncs3": map[string]any{
"gateway_addr": cfg.Reva.Address,
"provider_addr": cfg.UserSharingDrivers.JSONCS3.ProviderAddr,
"service_user_id": cfg.UserSharingDrivers.JSONCS3.SystemUserID,
"service_user_idp": cfg.UserSharingDrivers.JSONCS3.SystemUserIDP,
"machine_auth_apikey": cfg.UserSharingDrivers.JSONCS3.SystemUserAPIKey,
"gateway_addr": cfg.Reva.Address,
"provider_addr": cfg.UserSharingDrivers.JSONCS3.ProviderAddr,
"system_user_id": cfg.UserSharingDrivers.JSONCS3.SystemUserID,
"system_user_idp": cfg.UserSharingDrivers.JSONCS3.SystemUserIDP,
"machine_auth_apikey": cfg.UserSharingDrivers.JSONCS3.SystemUserAPIKey,
"service_account_id": cfg.ServiceAccount.ServiceAccountID,
"service_account_secret": cfg.ServiceAccount.ServiceAccountSecret,
},
}
}
+2 -1
View File
@@ -36,7 +36,8 @@ func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
basePath, _ := cmd.Flags().GetString("basepath")
dryRun, _ := cmd.Flags().GetBool("dry-run")
if err := trash.PurgeTrashEmptyPaths(basePath, dryRun); err != nil {
posix := cfg.StorageUsers.Driver == posixDriver
if err := trash.PurgeTrashEmptyPaths(basePath, dryRun, posix); err != nil {
fmt.Println(err)
return err
}
+45 -6
View File
@@ -10,12 +10,23 @@ import (
const (
// _trashGlobPattern is the glob pattern to find all trash items
_trashGlobPattern = "spaces/*/*/trash/*/*/*/*"
// _trashRootPattern is the glob pattern of the trash container root
_trashRootPattern = "spaces/*/*/trash"
// _posixTrashGlobPattern is the glob pattern to find all trash items on posix
_posixTrashGlobPattern = "*/*/.Trash/files/*"
// _posixTrashRootPattern is the glob pattern of the trash container root on posix
_posixTrashRootPattern = "*/*/.Trash/files"
)
// PurgeTrashEmptyPaths purges empty paths in the trash
func PurgeTrashEmptyPaths(p string, dryRun bool) error {
func PurgeTrashEmptyPaths(p string, dryRun bool, posix bool) error {
pattern := _trashGlobPattern
if posix {
pattern = _posixTrashGlobPattern
}
// we have all trash nodes in all spaces now
dirs, err := filepath.Glob(filepath.Join(p, _trashGlobPattern))
dirs, err := filepath.Glob(filepath.Join(p, pattern))
if err != nil {
return err
}
@@ -25,15 +36,26 @@ func PurgeTrashEmptyPaths(p string, dryRun bool) error {
}
for _, d := range dirs {
if err := removeEmptyFolder(d, dryRun); err != nil {
if err := removeEmptyFolder(d, dryRun, posix, p); err != nil {
return err
}
}
return nil
}
func removeEmptyFolder(path string, dryRun bool) error {
func removeEmptyFolder(path string, dryRun bool, posix bool, basePath string) error {
if dryRun {
if posix {
// on posix the ".trashitem" entries are the actual data and can be
// files, which are skipped, so we need to check here if the path
// is the actual dir, the same check for real removal part
fi, err := os.Stat(path)
if err != nil || !fi.IsDir() {
return nil
}
}
f, err := os.ReadDir(path)
if err != nil {
return err
@@ -43,6 +65,14 @@ func removeEmptyFolder(path string, dryRun bool) error {
}
return nil
}
if posix {
fi, err := os.Stat(path)
if err != nil || !fi.IsDir() {
return nil
}
}
if err := os.Remove(path); err != nil {
// we do not really care about the error here
// if the folder is not empty we will get an error,
@@ -50,8 +80,17 @@ func removeEmptyFolder(path string, dryRun bool) error {
return nil
}
nd := filepath.Dir(path)
if filepath.Base(nd) == "trash" {
if isTrashRoot(nd, basePath, posix) {
return nil
}
return removeEmptyFolder(nd, dryRun)
return removeEmptyFolder(nd, dryRun, posix, basePath)
}
func isTrashRoot(path, basePath string, posix bool) bool {
rootPattern := _trashRootPattern
if posix {
rootPattern = _posixTrashRootPattern
}
matched, _ := filepath.Match(filepath.Join(basePath, rootPattern), path)
return matched
}
+109
View File
@@ -0,0 +1,109 @@
package trash
import (
"os"
"testing"
"github.com/test-go/testify/require"
)
func TestIsTrashRootDecomposed(t *testing.T) {
storageRoot := "test_temp_" + t.Name()
defer os.RemoveAll(storageRoot)
require.True(t, isTrashRoot(storageRoot+"/spaces/id/id/trash", storageRoot, false))
require.False(t, isTrashRoot(storageRoot+"/spaces/id/id/trash/s1/s2/s3/node", storageRoot, false))
require.False(t, isTrashRoot(storageRoot+"/spaces/id/id/trash/s1/s2/trash/node", storageRoot, false))
}
func TestIsTrashRootPosix(t *testing.T) {
storageRoot := "test_temp_" + t.Name()
defer os.RemoveAll(storageRoot)
require.True(t, isTrashRoot(storageRoot+"/users/alice/.Trash/files", storageRoot, true))
require.False(t, isTrashRoot(storageRoot+"/users/alice/.Trash/files/item.trashitem", storageRoot, true))
require.False(t, isTrashRoot(storageRoot+"/users/alice/.Trash/files/item.trashitem/files", storageRoot, true))
}
func TestRemoveEmptyFolderPosix(t *testing.T) {
storageRoot := "test_temp_" + t.Name()
base := storageRoot + "/users/alice/.Trash/files"
defer os.RemoveAll(storageRoot)
emptyChain := base + "/empty.trashitem/sub/subsub"
require.NoError(t, os.MkdirAll(emptyChain, os.ModePerm))
nonEmpty := base + "/keep.trashitem"
require.NoError(t, os.MkdirAll(nonEmpty, os.ModePerm))
require.NoError(t, os.WriteFile(nonEmpty+"/file.txt", []byte("some text"), os.ModePerm))
require.NoError(t, removeEmptyFolder(emptyChain, false, true, storageRoot))
assertNoDirExists(t, emptyChain)
assertNoDirExists(t, base+"/empty.trashitem")
assertDirExists(t, base)
assertDirExists(t, nonEmpty)
}
func TestRemoveEmptyFolderPosixUserDirNamedFiles(t *testing.T) {
storageRoot := "test_temp_" + t.Name()
base := storageRoot + "/users/alice/.Trash/files"
defer os.RemoveAll(storageRoot)
nestedFilesDir := base + "/folder.trashitem/files/files"
require.NoError(t, os.MkdirAll(nestedFilesDir, os.ModePerm))
require.NoError(t, removeEmptyFolder(nestedFilesDir, false, true, storageRoot))
assertNoDirExists(t, nestedFilesDir)
assertNoDirExists(t, base+"/folder.trashitem/files")
assertNoDirExists(t, base+"/folder.trashitem")
assertDirExists(t, base)
}
func TestRemoveEmptyFolderDecomposed(t *testing.T) {
storageRoot := "test_temp_" + t.Name()
base := storageRoot + "/spaces/id/id/trash"
defer os.RemoveAll(storageRoot)
emptyChain := base + "/s1/s2/s3/node"
require.NoError(t, os.MkdirAll(emptyChain, os.ModePerm))
require.NoError(t, removeEmptyFolder(emptyChain, false, false, storageRoot))
assertNoDirExists(t, emptyChain)
assertNoDirExists(t, base+"/s1/s2/s3")
assertNoDirExists(t, base+"/s1/s2")
assertNoDirExists(t, base+"/s1")
assertDirExists(t, base)
}
func TestRemoveEmptyFolderDecomposedUserDirNamedTrash(t *testing.T) {
storageRoot := "test_temp_" + t.Name()
base := storageRoot + "/spaces/id/id/trash"
defer os.RemoveAll(storageRoot)
nestedTrashDir := base + "/s1/trash/s2/node"
require.NoError(t, os.MkdirAll(nestedTrashDir, os.ModePerm))
require.NoError(t, removeEmptyFolder(nestedTrashDir, false, false, storageRoot))
assertNoDirExists(t, nestedTrashDir)
assertNoDirExists(t, base+"/s1/trash/s2")
assertNoDirExists(t, base+"/s1/trash")
assertNoDirExists(t, base+"/s1")
assertDirExists(t, base)
}
func assertNoDirExists(t *testing.T, path string) {
t.Helper()
_, err := os.Stat(path)
require.True(t, os.IsNotExist(err))
}
func assertDirExists(t *testing.T, path string) {
t.Helper()
fi, err := os.Stat(path)
require.NoError(t, err)
require.True(t, fi.IsDir())
}
+12
View File
@@ -43,6 +43,10 @@ type StringNode struct {
*Base
Key string
Value string
Exact bool
// CaseInsensitive marks a case-insensitive restriction; set by the search
// lowering pass, a backend routes it to the field's lowercased form.
CaseInsensitive bool
}
// BooleanNode represents a bool value
@@ -60,6 +64,14 @@ type DateTimeNode struct {
Value time.Time
}
// NumberNode represents a numeric value
type NumberNode struct {
*Base
Key string
Operator *OperatorNode
Value float64
}
// OperatorNode represents an operator value like
// AND, OR, NOT, =, <= ... and so on
type OperatorNode struct {
+1
View File
@@ -21,6 +21,7 @@ func DiffAst(x, y any, opts ...cmp.Option) string {
cmpopts.IgnoreFields(ast.GroupNode{}, "Base"),
cmpopts.IgnoreFields(ast.BooleanNode{}, "Base"),
cmpopts.IgnoreFields(ast.DateTimeNode{}, "Base"),
cmpopts.IgnoreFields(ast.NumberNode{}, "Base"),
)...,
)
}
+12 -3
View File
@@ -2,11 +2,11 @@ package kql
import (
"fmt"
"strconv"
"time"
"github.com/jinzhu/now"
"github.com/opencloud-eu/opencloud/pkg/ast"
"github.com/opencloud-eu/opencloud/services/search/pkg/query"
)
func toNode[T ast.Node](in any) (T, error) {
@@ -84,7 +84,7 @@ func toTimeRange(in any) (*time.Time, *time.Time, error) {
value, err := toString(in)
if err != nil {
return &from, &to, &query.UnsupportedTimeRangeError{}
return &from, &to, &UnsupportedTimeRangeError{}
}
c := &now.Config{
@@ -131,8 +131,17 @@ func toTimeRange(in any) (*time.Time, *time.Time, error) {
}
if from.IsZero() || to.IsZero() {
return nil, nil, &query.UnsupportedTimeRangeError{}
return nil, nil, &UnsupportedTimeRangeError{}
}
return &from, &to, nil
}
func toFloat(v any) (float64, error) {
value, err := toString(v)
if err != nil {
return 0, err
}
return strconv.ParseFloat(value, 64)
}
+23 -4
View File
@@ -40,6 +40,7 @@ GroupNode <-
PropertyRestrictionNodes <-
YesNoPropertyRestrictionNode /
DateTimeRestrictionNode /
NumberRestrictionNode /
TextPropertyRestrictionNode
YesNoPropertyRestrictionNode <-
@@ -69,9 +70,22 @@ DateTimeRestrictionNode <-
return buildNaturalLanguageDateTimeNodes(k, v, c.text, c.pos)
}
NumberRestrictionNode <-
k:Key o:(
OperatorGreaterOrEqualNode /
OperatorLessOrEqualNode /
OperatorGreaterNode /
OperatorLessNode
) '"'? v:Number '"'? {
return buildNumberNode(k, o, v, c.text, c.pos)
}
TextPropertyRestrictionNode <-
k:Key (OperatorColonNode / OperatorEqualNode) v:(String / [^ ()]+){
return buildStringNode(k, v, c.text, c.pos)
k:Key OperatorEqualNode v:(String / [^ ()]+) {
return buildStringNode(k, v, true, c.text, c.pos)
} /
k:Key OperatorColonNode v:(String / [^ ()]+) {
return buildStringNode(k, v, false, c.text, c.pos)
}
////////////////////////////////////////////////////////
@@ -84,12 +98,12 @@ FreeTextKeywordNodes <-
PhraseNode <-
OperatorColonNode? _ v:String _ OperatorColonNode? {
return buildStringNode("", v, c.text, c.pos)
return buildStringNode("", v, false, c.text, c.pos)
}
WordNode <-
OperatorColonNode? _ v:[^ :()]+ _ OperatorColonNode? {
return buildStringNode("", v, c.text, c.pos)
return buildStringNode("", v, false, c.text, c.pos)
}
////////////////////////////////////////////////////////
@@ -229,6 +243,11 @@ String <-
return v, nil
}
Number <-
[0-9]+ ("." [0-9]+)? {
return string(c.text), nil
}
Digit <-
[0-9] {
return c.text, nil
+928 -483
View File
File diff suppressed because it is too large. Load diff
+80 -11
View File
@@ -9,7 +9,6 @@ import (
"github.com/opencloud-eu/opencloud/pkg/ast"
"github.com/opencloud-eu/opencloud/pkg/ast/test"
"github.com/opencloud-eu/opencloud/pkg/kql"
"github.com/opencloud-eu/opencloud/services/search/pkg/query"
tAssert "github.com/stretchr/testify/assert"
)
@@ -34,13 +33,13 @@ func TestParse_Spec(t *testing.T) {
},
{
name: `AND`,
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolAND},
},
},
{
name: `AND cat AND dog`,
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolAND},
},
},
@@ -80,13 +79,13 @@ func TestParse_Spec(t *testing.T) {
},
{
name: `OR`,
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolOR},
},
},
{
name: `OR cat AND dog`,
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolOR},
},
},
@@ -145,6 +144,22 @@ func TestParse_Spec(t *testing.T) {
},
},
},
{
name: `author="John Smith"`,
ast: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "author", Value: "John Smith", Exact: true},
},
},
},
{
name: `filename=budget.xlsx`,
ast: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "filename", Value: "budget.xlsx", Exact: true},
},
},
},
// 3.2.3 Implicit Operator for Property Restriction
{
name: `author:"John Smith" filetype:docx`,
@@ -423,6 +438,60 @@ func TestParse_Spec(t *testing.T) {
}
}
func TestParse_NumberRestrictionNode(t *testing.T) {
tests := []testCase{
{
name: "format",
query: join([]string{
`size>100`,
`size>"100"`,
`size>=15.5`,
`size<100`,
`size<=100`,
}),
ast: &ast.Ast{
Nodes: []ast.Node{
&ast.NumberNode{
Key: "size",
Operator: &ast.OperatorNode{Value: ">"},
Value: 100,
},
&ast.OperatorNode{Value: kql.BoolAND},
&ast.NumberNode{
Key: "size",
Operator: &ast.OperatorNode{Value: ">"},
Value: 100,
},
&ast.OperatorNode{Value: kql.BoolAND},
&ast.NumberNode{
Key: "size",
Operator: &ast.OperatorNode{Value: ">="},
Value: 15.5,
},
&ast.OperatorNode{Value: kql.BoolAND},
&ast.NumberNode{
Key: "size",
Operator: &ast.OperatorNode{Value: "<"},
Value: 100,
},
&ast.OperatorNode{Value: kql.BoolAND},
&ast.NumberNode{
Key: "size",
Operator: &ast.OperatorNode{Value: "<="},
Value: 100,
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
testKQL(t, tc)
})
}
}
func TestParse_DateTimeRestrictionNode(t *testing.T) {
tests := []testCase{
{
@@ -860,37 +929,37 @@ func TestParse_Errors(t *testing.T) {
tests := []testCase{
{
query: "animal:(mammal:cat mammal:dog reptile:turtle)",
error: query.NamedGroupInvalidNodesError{
error: kql.NamedGroupInvalidNodesError{
Node: &ast.StringNode{Key: "mammal", Value: "cat"},
},
},
{
query: "animal:(cat mammal:dog turtle)",
error: query.NamedGroupInvalidNodesError{
error: kql.NamedGroupInvalidNodesError{
Node: &ast.StringNode{Key: "mammal", Value: "dog"},
},
},
{
query: "animal:(AND cat)",
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolAND},
},
},
{
query: "animal:(OR cat)",
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolOR},
},
},
{
query: "(AND cat)",
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolAND},
},
},
{
query: "(OR cat)",
error: query.StartsWithBinaryOperatorError{
error: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolOR},
},
},
-11
View File
@@ -1,11 +0,0 @@
package kql
import (
"time"
)
// PatchTimeNow is here to patch the package time now func,
// which is used in the test suite
func PatchTimeNow(t func() time.Time) {
timeNow = t
}
@@ -1,6 +1,7 @@
package query
package kql
import (
"errors"
"fmt"
"github.com/opencloud-eu/opencloud/pkg/ast"
@@ -38,10 +39,17 @@ func (e UnsupportedTimeRangeError) Error() string {
return fmt.Sprintf("unable to convert '%v' to a time range", e.Value)
}
// IsValidationError reports whether err is one of the KQL parse/validation
// errors produced by this package, i.e. the query itself is at fault and the
// caller should treat it as a bad request.
func IsValidationError(err error) bool {
switch err.(type) {
case *StartsWithBinaryOperatorError, *NamedGroupInvalidNodesError, *UnsupportedTimeRangeError:
return true
}
return false
var (
startsWithBinaryOperator *StartsWithBinaryOperatorError
namedGroupInvalidNodes *NamedGroupInvalidNodesError
unsupportedTimeRange *UnsupportedTimeRangeError
)
return errors.As(err, &startsWithBinaryOperator) ||
errors.As(err, &namedGroupInvalidNodes) ||
errors.As(err, &unsupportedTimeRange)
}
+32 -1
View File
@@ -50,7 +50,7 @@ func buildAST(n any, text []byte, pos position) (*ast.Ast, error) {
return a, nil
}
func buildStringNode(k, v any, text []byte, pos position) (*ast.StringNode, error) {
func buildStringNode(k, v any, exact bool, text []byte, pos position) (*ast.StringNode, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
@@ -70,6 +70,7 @@ func buildStringNode(k, v any, text []byte, pos position) (*ast.StringNode, erro
Base: b,
Key: key,
Value: value,
Exact: exact,
}, nil
}
@@ -101,6 +102,36 @@ func buildDateTimeNode(k, o, v any, text []byte, pos position) (*ast.DateTimeNod
Value: value,
}, nil
}
func buildNumberNode(k, o, v any, text []byte, pos position) (*ast.NumberNode, error) {
b, err := base(text, pos)
if err != nil {
return nil, err
}
operator, err := toNode[*ast.OperatorNode](o)
if err != nil {
return nil, err
}
key, err := toString(k)
if err != nil {
return nil, err
}
value, err := toFloat(v)
if err != nil {
return nil, err
}
return &ast.NumberNode{
Base: b,
Key: key,
Operator: operator,
Value: value,
}, nil
}
func buildNaturalLanguageDateTimeNodes(k, v any, text []byte, pos position) ([]ast.Node, error) {
b, err := base(text, pos)
if err != nil {
+6
View File
@@ -47,3 +47,9 @@ func (b Builder) Build(q string) (*ast.Ast, error) {
// timeNow mirrors time.Now by default, the only reason why this exists
// is to monkey patch it from the tests. See PatchTimeNow
var timeNow = time.Now
// PatchTimeNow pins the clock the natural language dates resolve against,
// so a test can hold "today" still while it runs
func PatchTimeNow(t func() time.Time) {
timeNow = t
}
+1 -2
View File
@@ -5,7 +5,6 @@ import (
"github.com/opencloud-eu/opencloud/pkg/ast"
"github.com/opencloud-eu/opencloud/pkg/kql"
"github.com/opencloud-eu/opencloud/services/search/pkg/query"
tAssert "github.com/stretchr/testify/assert"
)
@@ -22,7 +21,7 @@ func TestNewAST(t *testing.T) {
{
name: "error",
givenQuery: kql.BoolAND,
expectedError: query.StartsWithBinaryOperatorError{
expectedError: kql.StartsWithBinaryOperatorError{
Node: &ast.OperatorNode{Value: kql.BoolAND},
},
},
+3 -4
View File
@@ -2,7 +2,6 @@ package kql
import (
"github.com/opencloud-eu/opencloud/pkg/ast"
"github.com/opencloud-eu/opencloud/services/search/pkg/query"
)
func validateAst(a *ast.Ast) error {
@@ -10,7 +9,7 @@ func validateAst(a *ast.Ast) error {
case *ast.OperatorNode:
switch node.Value {
case BoolAND, BoolOR:
return &query.StartsWithBinaryOperatorError{Node: node}
return &StartsWithBinaryOperatorError{Node: node}
}
}
return nil
@@ -21,14 +20,14 @@ func validateGroupNode(n *ast.GroupNode) error {
case *ast.OperatorNode:
switch node.Value {
case BoolAND, BoolOR:
return &query.StartsWithBinaryOperatorError{Node: node}
return &StartsWithBinaryOperatorError{Node: node}
}
}
if n.Key != "" {
for _, node := range n.Nodes {
if ast.NodeKey(node) != "" {
return &query.NamedGroupInvalidNodesError{Node: node}
return &NamedGroupInvalidNodesError{Node: node}
}
}
}
+189
View File
@@ -0,0 +1,189 @@
package metrics
import (
"fmt"
"reflect"
"strings"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/version"
"github.com/prometheus/client_golang/prometheus"
)
type BuildInfoMetric = *prometheus.GaugeVec
// Create a BuildInfo metric for the specified namespace and subsystem.
func BuildInfo(namespace, subsystem string) BuildInfoMetric {
return prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "build_info",
Help: "Build information",
}, []string{"version"})
}
// Determine the fully qualified name of a metric.
//
// Beware that this requires storing a value into the metric in order to make it
// visible in a temporary registry.
// If the metric is a MetricVec, it will be Reset().
func describe(metric prometheus.Collector, initialize func() error) (string, error) {
reg := prometheus.NewRegistry()
if err := reg.Register(metric); err != nil {
return "", err
}
if err := initialize(); err != nil {
return "", err
}
if resettable, ok := metric.(*prometheus.MetricVec); ok {
defer resettable.Reset()
}
fams, err := reg.Gather()
if err != nil {
return "", err
}
if len(fams) == 0 {
return "", fmt.Errorf("no metric families gathered")
}
return fams[0].GetName(), nil
}
// Take a struct that contains metrics as attributes and register all of them
// with the specified Registerer.
func RegisterAll(registerer prometheus.Registerer, m any, logger *log.Logger) error {
// we go over all of them, use this to keep track of succeesses and failures
total := 0
succeeded := []string{}
failed := map[string]error{}
// we need to use reflection here to iterate over the public metric attributes
// that are contained in it
r := reflect.ValueOf(m)
if r.Kind() == reflect.Pointer {
r = r.Elem()
}
for i := 0; i < r.NumField(); i++ {
t := r.Type().Field(i)
n := t.Name // the name of the attribute (not the name of the metric)
f := r.Field(i)
if !f.CanInterface() {
continue // we won't be able to process that one, most probably because it's not exported
}
v := f.Interface()
switch c := v.(type) {
case prometheus.Collector:
total++
if err := registerer.Register(c); err != nil {
switch err.(type) {
case prometheus.AlreadyRegisteredError:
// silently ignore this error, as this case can happen when the suture service decides to restart
err = nil
succeeded = append(succeeded, n)
default:
failed[n] = err
}
} else {
succeeded = append(succeeded, n)
// special post-treatment for the BuildInfo metric, as we have that one pretty much
// everywhere: set its value with the current version so we don't need to do that every time
switch buildInfo := c.(type) {
case BuildInfoMetric:
if name, err := describe(buildInfo, func() error { buildInfo.WithLabelValues("0").Set(0.0); return nil }); err != nil {
failed[n] = err
} else if strings.HasSuffix(name, "_build_info") {
buildInfo.Reset()
buildInfo.WithLabelValues(version.GetString()).Set(1)
}
}
}
case *prometheus.Desc,
prometheus.GaugeOpts,
prometheus.CounterOpts,
prometheus.HistogramOpts,
prometheus.SummaryOpts,
prometheus.UntypedOpts:
// skip these
default:
failed[n] = fmt.Errorf("unsupported metric '%s' of type %T", n, c)
}
}
if len(failed) > 0 {
failedMsgs := []string{}
for name, err := range failed {
failedMsgs = append(failedMsgs, fmt.Sprintf("'%s' (%v)", name, err))
}
msg := strings.Join(failedMsgs, ", ")
if logger != nil {
logger.Warn().Msgf("registered %d/%d metrics successfully (%d failed): %s", len(succeeded), total, len(failed), msg)
}
return fmt.Errorf("failed to register metrics: %s", msg)
} else {
if logger != nil {
logger.Debug().Msgf("registered %d/%d metrics successfully (%d failed)", len(succeeded), total, len(failed))
}
return nil
}
}
// Register all the metrics that are contained as public attributes in the struct,
// and log any errors that might occur while doing so.
func Register[M any](reg prometheus.Registerer, m M, logger *log.Logger) (M, error) {
lr := NewLoggingPrometheusRegisterer(reg, logger)
err := RegisterAll(lr, m, logger)
return m, err
}
// Register a single metric.
func RegisterMetric[M prometheus.Collector](reg prometheus.Registerer, m M, logger *log.Logger) error {
return NewLoggingPrometheusRegisterer(reg, logger).Register(m)
}
// Prometheus Registerer wrapper that logs every error that occurs when registering
// a metric, and delegates to an actual Registerer.
type LoggingPrometheusRegisterer struct {
delegate prometheus.Registerer
logger *log.Logger
}
// Instantiate a Prometheus Registerer wrapper that logs every error that occurs when registering
// a metric, and that delegates to an actual Registerer specified here.
func NewLoggingPrometheusRegisterer(delegate prometheus.Registerer, logger *log.Logger) *LoggingPrometheusRegisterer {
return &LoggingPrometheusRegisterer{
delegate: delegate,
logger: logger,
}
}
func (r *LoggingPrometheusRegisterer) Register(c prometheus.Collector) error {
err := r.delegate.Register(c)
if err != nil {
switch err.(type) {
case prometheus.AlreadyRegisteredError:
// silently ignore this error, as this case can happen when the suture service decides to restart
err = nil
default:
if r.logger != nil {
r.logger.Warn().Err(err).Msgf("failed to register metric")
}
}
}
return err
}
func (r *LoggingPrometheusRegisterer) MustRegister(collectors ...prometheus.Collector) {
for _, c := range collectors {
if err := r.Register(c); err != nil {
if r.logger != nil {
r.logger.Error().Err(err).Msg("failed to register metrics collector")
}
}
}
}
func (r *LoggingPrometheusRegisterer) Unregister(c prometheus.Collector) bool {
return r.delegate.Unregister(c)
}
var _ prometheus.Registerer = &LoggingPrometheusRegisterer{}
+153
View File
@@ -0,0 +1,153 @@
package metrics
import (
"fmt"
"math/rand/v2"
"testing"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/version"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
func randName() string {
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
n := 8 + rand.IntN(33)
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.IntN(len(letterBytes))]
}
return string(b)
}
func TestBuildInfo(t *testing.T) {
require := require.New(t)
namespace := "name-" + randName()
subsystem := "sub-" + randName()
expectedName := fmt.Sprintf("%s_%s_build_info", namespace, subsystem)
version := fmt.Sprintf("%d.%d.%d", rand.IntN(10), rand.IntN(10), rand.IntN(10))
g := BuildInfo(namespace, subsystem)
reg := prometheus.NewRegistry()
require.NoError(reg.Register(g))
{
mfs, err := reg.Gather()
require.NoError(err)
require.Len(mfs, 0)
}
g.WithLabelValues(version).Set(1)
{
mfs, err := reg.Gather()
require.NoError(err)
found := false
for _, mf := range mfs {
if mf.GetName() == expectedName {
found = true
ms := mf.GetMetric()
require.Len(ms, 1)
labels := ms[0].GetLabel()
require.Len(labels, 1)
require.NotNil(labels[0].Name)
require.Equal("version", *labels[0].Name)
require.NotNil(labels[0].Value)
require.Equal(version, *labels[0].Value)
require.Equal(1.0, ms[0].GetGauge().GetValue())
} else {
t.Fatalf("unexpected metric family %q", mf.GetName())
}
}
require.True(found, "failed to find metric %q", expectedName)
}
}
func TestRegisterAll(t *testing.T) {
require := require.New(t)
reg := prometheus.NewRegistry()
logger := log.NewLogger()
namespace := "name-" + randName()
subsystem := "sub-" + randName()
m := struct {
BuildInfo *prometheus.GaugeVec
Foo *prometheus.GaugeVec
Bar prometheus.Counter
}{
BuildInfo: BuildInfo(namespace, subsystem),
Foo: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "foo",
ConstLabels: prometheus.Labels{
"f": "oo",
"fo": "o",
},
}, []string{"oof"}),
Bar: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "bar",
}),
}
expectedNameForBuildInfo := namespace + "_" + subsystem + "_build_info"
expectedNameForFoo := namespace + "_" + subsystem + "_foo"
expectedNameForBar := namespace + "_" + subsystem + "_bar"
{
mfs, err := reg.Gather()
require.NoError(err)
require.Len(mfs, 0)
}
require.NoError(RegisterAll(reg, m, &logger))
{
mfs, err := reg.Gather()
require.NoError(err)
require.Len(mfs, 3)
found := 0
for _, mf := range mfs {
switch mf.GetName() {
case expectedNameForBuildInfo:
found++
ms := mf.GetMetric()
require.Len(ms, 1)
labels := ms[0].GetLabel()
require.Len(labels, 1)
require.NotNil(labels[0].Name)
require.Equal("version", *labels[0].Name)
require.NotNil(labels[0].Value)
require.Equal(version.GetString(), *labels[0].Value)
require.Equal(1.0, ms[0].GetGauge().GetValue())
case expectedNameForFoo:
found++
ms := mf.GetMetric()
require.Len(ms, 1)
labels := ms[0].GetLabel()
require.Len(labels, 3)
require.NotNil(labels[0].Name)
require.Equal("f", *labels[0].Name)
require.NotNil(labels[0].Value)
require.Equal("oo", *labels[0].Value)
require.NotNil(labels[1].Name)
require.Equal("fo", *labels[1].Name)
require.NotNil(labels[1].Value)
require.Equal("o", *labels[1].Value)
require.Equal(0.0, ms[0].GetGauge().GetValue())
case expectedNameForBar:
found++
ms := mf.GetMetric()
require.Len(ms, 1)
labels := ms[0].GetLabel()
require.Len(labels, 0)
require.Equal(0.0, ms[0].GetGauge().GetValue())
default:
t.Fatalf("unexpected metric family %q", mf.GetName())
}
}
require.Equal(3, found, "failed to find expected metrics")
}
}
+11 -1
View File
@@ -2,6 +2,7 @@ package middleware
import (
"context"
"errors"
"net/http"
"strings"
"sync"
@@ -61,7 +62,10 @@ func OidcAuth(opts ...Option) func(http.Handler) http.Handler {
provider, err = providerFunc()
}
initializeProviderLock.Unlock()
if err != nil {
if err != nil || provider == nil {
if err == nil {
err = errors.New("OIDC provider initialization returned nil")
}
opt.Logger.Error().Err(err).Msg("could not initialize OIDC provider")
w.WriteHeader(http.StatusInternalServerError)
return
@@ -82,6 +86,12 @@ func OidcAuth(opts ...Option) func(http.Handler) http.Handler {
w.WriteHeader(http.StatusUnauthorized)
return
}
if userInfo == nil {
opt.Logger.Error().Msg("OIDC provider returned empty user info")
w.Header().Add("WWW-Authenticate", `Bearer`)
w.WriteHeader(http.StatusUnauthorized)
return
}
claims := map[string]any{}
err = userInfo.Claims(&claims)
if err != nil {
+8
View File
@@ -101,3 +101,11 @@ func MissingURLSigningSecret(service string) error {
"the config/corresponding environment variable).",
service, defaults.BaseConfigPath())
}
func AllComponentsDisabledError(service string) error {
return fmt.Errorf("All request handlers and event consumers are disabled for %s; at least one component must be enabled."+
"Make sure your %s config contains the proper values "+
"(e.g. by using 'opencloud init --diff' and applying the patch or setting a value manually in "+
"the config/corresponding environment variable).",
service, defaults.BaseConfigPath())
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
// we init the memlimit here to include it for OpenCloud als well as individual service binaries
func init() {
slog.SetLogLoggerLevel(slog.LevelError)
_, _ = memlimit.SetGoMemLimitWithOpts(
_, _ = memlimit.Set(
memlimit.WithLogger(slog.Default()),
)
}
+1 -1
View File
@@ -34,7 +34,7 @@ var (
// LatestTag is the latest released version plus the dev meta version.
// Will be overwritten by the release pipeline
// Needs a manual change for every tagged release
LatestTag = "7.4.0+dev"
LatestTag = "7.5.0+dev"
// Date indicates the build date.
// This has been removed, it looks like you can only replace static strings with recent go versions
@@ -535,6 +535,267 @@ func (x *Photo) GetTakenDateTime() *timestamppb.Timestamp {
return nil
}
type Video struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AudioBitsPerSample *int32 `protobuf:"varint,1,opt,name=audioBitsPerSample,proto3,oneof" json:"audioBitsPerSample,omitempty"`
AudioChannels *int32 `protobuf:"varint,2,opt,name=audioChannels,proto3,oneof" json:"audioChannels,omitempty"`
AudioFormat *string `protobuf:"bytes,3,opt,name=audioFormat,proto3,oneof" json:"audioFormat,omitempty"`
AudioSamplesPerSecond *int32 `protobuf:"varint,4,opt,name=audioSamplesPerSecond,proto3,oneof" json:"audioSamplesPerSecond,omitempty"`
Bitrate *int32 `protobuf:"varint,5,opt,name=bitrate,proto3,oneof" json:"bitrate,omitempty"`
Duration *int64 `protobuf:"varint,6,opt,name=duration,proto3,oneof" json:"duration,omitempty"`
FourCC *string `protobuf:"bytes,7,opt,name=fourCC,proto3,oneof" json:"fourCC,omitempty"`
FrameRate *float64 `protobuf:"fixed64,8,opt,name=frameRate,proto3,oneof" json:"frameRate,omitempty"`
Height *int32 `protobuf:"varint,9,opt,name=height,proto3,oneof" json:"height,omitempty"`
Width *int32 `protobuf:"varint,10,opt,name=width,proto3,oneof" json:"width,omitempty"`
}
func (x *Video) Reset() {
*x = Video{}
if protoimpl.UnsafeEnabled {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Video) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Video) ProtoMessage() {}
func (x *Video) ProtoReflect() protoreflect.Message {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Video.ProtoReflect.Descriptor instead.
func (*Video) Descriptor() ([]byte, []int) {
return file_opencloud_messages_search_v0_search_proto_rawDescGZIP(), []int{6}
}
func (x *Video) GetAudioBitsPerSample() int32 {
if x != nil && x.AudioBitsPerSample != nil {
return *x.AudioBitsPerSample
}
return 0
}
func (x *Video) GetAudioChannels() int32 {
if x != nil && x.AudioChannels != nil {
return *x.AudioChannels
}
return 0
}
func (x *Video) GetAudioFormat() string {
if x != nil && x.AudioFormat != nil {
return *x.AudioFormat
}
return ""
}
func (x *Video) GetAudioSamplesPerSecond() int32 {
if x != nil && x.AudioSamplesPerSecond != nil {
return *x.AudioSamplesPerSecond
}
return 0
}
func (x *Video) GetBitrate() int32 {
if x != nil && x.Bitrate != nil {
return *x.Bitrate
}
return 0
}
func (x *Video) GetDuration() int64 {
if x != nil && x.Duration != nil {
return *x.Duration
}
return 0
}
func (x *Video) GetFourCC() string {
if x != nil && x.FourCC != nil {
return *x.FourCC
}
return ""
}
func (x *Video) GetFrameRate() float64 {
if x != nil && x.FrameRate != nil {
return *x.FrameRate
}
return 0
}
func (x *Video) GetHeight() int32 {
if x != nil && x.Height != nil {
return *x.Height
}
return 0
}
func (x *Video) GetWidth() int32 {
if x != nil && x.Width != nil {
return *x.Width
}
return 0
}
type MotionPhoto struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Version *int32 `protobuf:"varint,1,opt,name=version,proto3,oneof" json:"version,omitempty"`
PresentationTimestampUs *int64 `protobuf:"varint,2,opt,name=presentationTimestampUs,proto3,oneof" json:"presentationTimestampUs,omitempty"`
VideoSize *int64 `protobuf:"varint,3,opt,name=videoSize,proto3,oneof" json:"videoSize,omitempty"`
}
func (x *MotionPhoto) Reset() {
*x = MotionPhoto{}
if protoimpl.UnsafeEnabled {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MotionPhoto) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MotionPhoto) ProtoMessage() {}
func (x *MotionPhoto) ProtoReflect() protoreflect.Message {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MotionPhoto.ProtoReflect.Descriptor instead.
func (*MotionPhoto) Descriptor() ([]byte, []int) {
return file_opencloud_messages_search_v0_search_proto_rawDescGZIP(), []int{7}
}
func (x *MotionPhoto) GetVersion() int32 {
if x != nil && x.Version != nil {
return *x.Version
}
return 0
}
func (x *MotionPhoto) GetPresentationTimestampUs() int64 {
if x != nil && x.PresentationTimestampUs != nil {
return *x.PresentationTimestampUs
}
return 0
}
func (x *MotionPhoto) GetVideoSize() int64 {
if x != nil && x.VideoSize != nil {
return *x.VideoSize
}
return 0
}
type LivePhoto struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ContentId *string `protobuf:"bytes,1,opt,name=contentId,proto3,oneof" json:"contentId,omitempty"`
StillImageTimeUs *int64 `protobuf:"varint,2,opt,name=stillImageTimeUs,proto3,oneof" json:"stillImageTimeUs,omitempty"`
Auto *bool `protobuf:"varint,3,opt,name=auto,proto3,oneof" json:"auto,omitempty"`
VitalityScore *float64 `protobuf:"fixed64,4,opt,name=vitalityScore,proto3,oneof" json:"vitalityScore,omitempty"`
VitalityScoringVersion *int64 `protobuf:"varint,5,opt,name=vitalityScoringVersion,proto3,oneof" json:"vitalityScoringVersion,omitempty"`
}
func (x *LivePhoto) Reset() {
*x = LivePhoto{}
if protoimpl.UnsafeEnabled {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LivePhoto) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LivePhoto) ProtoMessage() {}
func (x *LivePhoto) ProtoReflect() protoreflect.Message {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LivePhoto.ProtoReflect.Descriptor instead.
func (*LivePhoto) Descriptor() ([]byte, []int) {
return file_opencloud_messages_search_v0_search_proto_rawDescGZIP(), []int{8}
}
func (x *LivePhoto) GetContentId() string {
if x != nil && x.ContentId != nil {
return *x.ContentId
}
return ""
}
func (x *LivePhoto) GetStillImageTimeUs() int64 {
if x != nil && x.StillImageTimeUs != nil {
return *x.StillImageTimeUs
}
return 0
}
func (x *LivePhoto) GetAuto() bool {
if x != nil && x.Auto != nil {
return *x.Auto
}
return false
}
func (x *LivePhoto) GetVitalityScore() float64 {
if x != nil && x.VitalityScore != nil {
return *x.VitalityScore
}
return 0
}
func (x *LivePhoto) GetVitalityScoringVersion() int64 {
if x != nil && x.VitalityScoringVersion != nil {
return *x.VitalityScoringVersion
}
return 0
}
type Entity struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -560,12 +821,15 @@ type Entity struct {
Image *Image `protobuf:"bytes,18,opt,name=image,proto3" json:"image,omitempty"`
Photo *Photo `protobuf:"bytes,19,opt,name=photo,proto3" json:"photo,omitempty"`
Favorites []string `protobuf:"bytes,20,rep,name=favorites,proto3" json:"favorites,omitempty"`
MotionPhoto *MotionPhoto `protobuf:"bytes,21,opt,name=motionPhoto,proto3" json:"motionPhoto,omitempty"`
Video *Video `protobuf:"bytes,22,opt,name=video,proto3" json:"video,omitempty"`
LivePhoto *LivePhoto `protobuf:"bytes,23,opt,name=livePhoto,proto3" json:"livePhoto,omitempty"`
}
func (x *Entity) Reset() {
*x = Entity{}
if protoimpl.UnsafeEnabled {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[6]
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -578,7 +842,7 @@ func (x *Entity) String() string {
func (*Entity) ProtoMessage() {}
func (x *Entity) ProtoReflect() protoreflect.Message {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[6]
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -591,7 +855,7 @@ func (x *Entity) ProtoReflect() protoreflect.Message {
// Deprecated: Use Entity.ProtoReflect.Descriptor instead.
func (*Entity) Descriptor() ([]byte, []int) {
return file_opencloud_messages_search_v0_search_proto_rawDescGZIP(), []int{6}
return file_opencloud_messages_search_v0_search_proto_rawDescGZIP(), []int{9}
}
func (x *Entity) GetRef() *Reference {
@@ -734,6 +998,27 @@ func (x *Entity) GetFavorites() []string {
return nil
}
func (x *Entity) GetMotionPhoto() *MotionPhoto {
if x != nil {
return x.MotionPhoto
}
return nil
}
func (x *Entity) GetVideo() *Video {
if x != nil {
return x.Video
}
return nil
}
func (x *Entity) GetLivePhoto() *LivePhoto {
if x != nil {
return x.LivePhoto
}
return nil
}
type Match struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -748,7 +1033,7 @@ type Match struct {
func (x *Match) Reset() {
*x = Match{}
if protoimpl.UnsafeEnabled {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[7]
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -761,7 +1046,7 @@ func (x *Match) String() string {
func (*Match) ProtoMessage() {}
func (x *Match) ProtoReflect() protoreflect.Message {
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[7]
mi := &file_opencloud_messages_search_v0_search_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -774,7 +1059,7 @@ func (x *Match) ProtoReflect() protoreflect.Message {
// Deprecated: Use Match.ProtoReflect.Descriptor instead.
func (*Match) Descriptor() ([]byte, []int) {
return file_opencloud_messages_search_v0_search_proto_rawDescGZIP(), []int{7}
return file_opencloud_messages_search_v0_search_proto_rawDescGZIP(), []int{10}
}
func (x *Match) GetEntity() *Entity {
@@ -908,62 +1193,141 @@ var file_opencloud_messages_search_v0_search_proto_rawDesc = []byte{
0x6c, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x69, 0x73, 0x6f, 0x42,
0x0e, 0x0a, 0x0c, 0x5f, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42,
0x10, 0x0a, 0x0e, 0x5f, 0x74, 0x61, 0x6b, 0x65, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
0x65, 0x22, 0xfa, 0x06, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x03,
0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73,
0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,
0x63, 0x65, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x38, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20,
0x65, 0x22, 0x9b, 0x04, 0x0a, 0x05, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x12, 0x33, 0x0a, 0x12, 0x61,
0x75, 0x64, 0x69, 0x6f, 0x42, 0x69, 0x74, 0x73, 0x50, 0x65, 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x12, 0x61, 0x75, 0x64, 0x69, 0x6f,
0x42, 0x69, 0x74, 0x73, 0x50, 0x65, 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x88, 0x01, 0x01,
0x12, 0x29, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x6f,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x61,
0x75, 0x64, 0x69, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x48, 0x02, 0x52, 0x0b, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x88,
0x01, 0x01, 0x12, 0x39, 0x0a, 0x15, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
0x05, 0x48, 0x03, 0x52, 0x15, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65,
0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a,
0x07, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x48, 0x04,
0x52, 0x07, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08,
0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x48, 0x05,
0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a,
0x06, 0x66, 0x6f, 0x75, 0x72, 0x43, 0x43, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52,
0x06, 0x66, 0x6f, 0x75, 0x72, 0x43, 0x43, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x66, 0x72,
0x61, 0x6d, 0x65, 0x52, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x48, 0x07, 0x52,
0x09, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a,
0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x48, 0x08, 0x52,
0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x77, 0x69,
0x64, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x09, 0x52, 0x05, 0x77, 0x69, 0x64,
0x74, 0x68, 0x88, 0x01, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x42,
0x69, 0x74, 0x73, 0x50, 0x65, 0x72, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x42, 0x10, 0x0a, 0x0e,
0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x42, 0x0e,
0x0a, 0x0c, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x42, 0x18,
0x0a, 0x16, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x50,
0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74,
0x72, 0x61, 0x74, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x66, 0x6f, 0x75, 0x72, 0x43, 0x43, 0x42, 0x0c, 0x0a, 0x0a,
0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x61, 0x74, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x22,
0xc4, 0x01, 0x0a, 0x0b, 0x4d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x12,
0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
0x48, 0x00, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x3d,
0x0a, 0x17, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x55, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48,
0x01, 0x52, 0x17, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x55, 0x73, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a,
0x09, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x48, 0x02, 0x52, 0x09, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x69, 0x7a, 0x65, 0x88, 0x01, 0x01,
0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x1a, 0x0a, 0x18,
0x5f, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x55, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x76, 0x69, 0x64,
0x65, 0x6f, 0x53, 0x69, 0x7a, 0x65, 0x22, 0xb9, 0x02, 0x0a, 0x09, 0x4c, 0x69, 0x76, 0x65, 0x50,
0x68, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x65,
0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x10, 0x73, 0x74, 0x69, 0x6c, 0x6c,
0x49, 0x6d, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x03, 0x48, 0x01, 0x52, 0x10, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x54,
0x69, 0x6d, 0x65, 0x55, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x6f,
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x04, 0x61, 0x75, 0x74, 0x6f, 0x88, 0x01,
0x01, 0x12, 0x29, 0x0a, 0x0d, 0x76, 0x69, 0x74, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x63, 0x6f,
0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x48, 0x03, 0x52, 0x0d, 0x76, 0x69, 0x74, 0x61,
0x6c, 0x69, 0x74, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x88, 0x01, 0x01, 0x12, 0x3b, 0x0a, 0x16,
0x76, 0x69, 0x74, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x48, 0x04, 0x52, 0x16,
0x76, 0x69, 0x74, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x63, 0x6f,
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x74, 0x69, 0x6c,
0x6c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x73, 0x42, 0x07, 0x0a, 0x05,
0x5f, 0x61, 0x75, 0x74, 0x6f, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x76, 0x69, 0x74, 0x61, 0x6c, 0x69,
0x74, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x19, 0x0a, 0x17, 0x5f, 0x76, 0x69, 0x74, 0x61,
0x6c, 0x69, 0x74, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x22, 0xc9, 0x08, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x39, 0x0a,
0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65,
0x6e, 0x63, 0x65, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x38, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x02,
0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x74, 0x61, 0x67, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x65, 0x74, 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69,
0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x48,
0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f,
0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69,
0x66, 0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65,
0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d,
0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d,
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x65, 0x52, 0x6f,
0x6f, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x68,
0x61, 0x72, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x09, 0x70,
0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28,
0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09,
0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x69, 0x67, 0x68,
0x6c, 0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x18,
0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75,
0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2e, 0x76, 0x30, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69,
0x6f, 0x12, 0x48, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x76, 0x30, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65,
0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x0e, 0x72,
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e,
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e,
0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x02, 0x69,
0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x74, 0x61, 0x67, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x65, 0x74, 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a,
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x48, 0x0a,
0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66,
0x69, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65,
0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09,
0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x65, 0x52, 0x6f, 0x6f,
0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x68, 0x61,
0x72, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x09, 0x70, 0x61,
0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e,
0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49,
0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52,
0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67,
0x68, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x69, 0x67, 0x68, 0x6c,
0x69, 0x67, 0x68, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x18, 0x0f,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x0c, 0x72,
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x05, 0x69,
0x6d, 0x61, 0x67, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52,
0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x18,
0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75,
0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x2e, 0x76, 0x30, 0x2e, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x74,
0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x18, 0x14,
0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x66, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x12,
0x4b, 0x0a, 0x0b, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x18, 0x15,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x76, 0x30, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f,
0x12, 0x48, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76,
0x30, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73,
0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x0e, 0x72, 0x65,
0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76,
0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x0c, 0x72, 0x65,
0x6d, 0x6f, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x05, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73,
0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x05,
0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x18, 0x13,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68,
0x2e, 0x76, 0x30, 0x2e, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x74, 0x6f,
0x12, 0x1c, 0x0a, 0x09, 0x66, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x18, 0x14, 0x20,
0x03, 0x28, 0x09, 0x52, 0x09, 0x66, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x22, 0x5b,
0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x52,
0x0b, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x12, 0x39, 0x0a, 0x05,
0x76, 0x69, 0x64, 0x65, 0x6f, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70,
0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73,
0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f,
0x52, 0x05, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x12, 0x45, 0x0a, 0x09, 0x6c, 0x69, 0x76, 0x65, 0x50,
0x68, 0x6f, 0x74, 0x6f, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e,
0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x4c, 0x69, 0x76, 0x65, 0x50, 0x68,
0x6f, 0x74, 0x6f, 0x52, 0x09, 0x6c, 0x69, 0x76, 0x65, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x22, 0x5b,
0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x3c, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c,
0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61,
@@ -990,7 +1354,7 @@ func file_opencloud_messages_search_v0_search_proto_rawDescGZIP() []byte {
return file_opencloud_messages_search_v0_search_proto_rawDescData
}
var file_opencloud_messages_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_opencloud_messages_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_opencloud_messages_search_v0_search_proto_goTypes = []interface{}{
(*ResourceID)(nil), // 0: opencloud.messages.search.v0.ResourceID
(*Reference)(nil), // 1: opencloud.messages.search.v0.Reference
@@ -998,28 +1362,34 @@ var file_opencloud_messages_search_v0_search_proto_goTypes = []interface{}{
(*Image)(nil), // 3: opencloud.messages.search.v0.Image
(*GeoCoordinates)(nil), // 4: opencloud.messages.search.v0.GeoCoordinates
(*Photo)(nil), // 5: opencloud.messages.search.v0.Photo
(*Entity)(nil), // 6: opencloud.messages.search.v0.Entity
(*Match)(nil), // 7: opencloud.messages.search.v0.Match
(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
(*Video)(nil), // 6: opencloud.messages.search.v0.Video
(*MotionPhoto)(nil), // 7: opencloud.messages.search.v0.MotionPhoto
(*LivePhoto)(nil), // 8: opencloud.messages.search.v0.LivePhoto
(*Entity)(nil), // 9: opencloud.messages.search.v0.Entity
(*Match)(nil), // 10: opencloud.messages.search.v0.Match
(*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
}
var file_opencloud_messages_search_v0_search_proto_depIdxs = []int32{
0, // 0: opencloud.messages.search.v0.Reference.resource_id:type_name -> opencloud.messages.search.v0.ResourceID
8, // 1: opencloud.messages.search.v0.Photo.takenDateTime:type_name -> google.protobuf.Timestamp
11, // 1: opencloud.messages.search.v0.Photo.takenDateTime:type_name -> google.protobuf.Timestamp
1, // 2: opencloud.messages.search.v0.Entity.ref:type_name -> opencloud.messages.search.v0.Reference
0, // 3: opencloud.messages.search.v0.Entity.id:type_name -> opencloud.messages.search.v0.ResourceID
8, // 4: opencloud.messages.search.v0.Entity.last_modified_time:type_name -> google.protobuf.Timestamp
11, // 4: opencloud.messages.search.v0.Entity.last_modified_time:type_name -> google.protobuf.Timestamp
0, // 5: opencloud.messages.search.v0.Entity.parent_id:type_name -> opencloud.messages.search.v0.ResourceID
2, // 6: opencloud.messages.search.v0.Entity.audio:type_name -> opencloud.messages.search.v0.Audio
4, // 7: opencloud.messages.search.v0.Entity.location:type_name -> opencloud.messages.search.v0.GeoCoordinates
0, // 8: opencloud.messages.search.v0.Entity.remote_item_id:type_name -> opencloud.messages.search.v0.ResourceID
3, // 9: opencloud.messages.search.v0.Entity.image:type_name -> opencloud.messages.search.v0.Image
5, // 10: opencloud.messages.search.v0.Entity.photo:type_name -> opencloud.messages.search.v0.Photo
6, // 11: opencloud.messages.search.v0.Match.entity:type_name -> opencloud.messages.search.v0.Entity
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
7, // 11: opencloud.messages.search.v0.Entity.motionPhoto:type_name -> opencloud.messages.search.v0.MotionPhoto
6, // 12: opencloud.messages.search.v0.Entity.video:type_name -> opencloud.messages.search.v0.Video
8, // 13: opencloud.messages.search.v0.Entity.livePhoto:type_name -> opencloud.messages.search.v0.LivePhoto
9, // 14: opencloud.messages.search.v0.Match.entity:type_name -> opencloud.messages.search.v0.Entity
15, // [15:15] is the sub-list for method output_type
15, // [15:15] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
15, // [15:15] is the sub-list for extension extendee
0, // [0:15] is the sub-list for field type_name
}
func init() { file_opencloud_messages_search_v0_search_proto_init() }
@@ -1101,7 +1471,7 @@ func file_opencloud_messages_search_v0_search_proto_init() {
}
}
file_opencloud_messages_search_v0_search_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Entity); i {
switch v := v.(*Video); i {
case 0:
return &v.state
case 1:
@@ -1113,6 +1483,42 @@ func file_opencloud_messages_search_v0_search_proto_init() {
}
}
file_opencloud_messages_search_v0_search_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MotionPhoto); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_opencloud_messages_search_v0_search_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LivePhoto); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_opencloud_messages_search_v0_search_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Entity); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_opencloud_messages_search_v0_search_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Match); i {
case 0:
return &v.state
@@ -1129,13 +1535,16 @@ func file_opencloud_messages_search_v0_search_proto_init() {
file_opencloud_messages_search_v0_search_proto_msgTypes[3].OneofWrappers = []interface{}{}
file_opencloud_messages_search_v0_search_proto_msgTypes[4].OneofWrappers = []interface{}{}
file_opencloud_messages_search_v0_search_proto_msgTypes[5].OneofWrappers = []interface{}{}
file_opencloud_messages_search_v0_search_proto_msgTypes[6].OneofWrappers = []interface{}{}
file_opencloud_messages_search_v0_search_proto_msgTypes[7].OneofWrappers = []interface{}{}
file_opencloud_messages_search_v0_search_proto_msgTypes[8].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_opencloud_messages_search_v0_search_proto_rawDesc,
NumEnums: 0,
NumMessages: 8,
NumMessages: 11,
NumExtensions: 0,
NumServices: 0,
},
@@ -226,6 +226,42 @@ func (m *Photo) UnmarshalJSON(b []byte) error {
var _ json.Unmarshaler = (*Photo)(nil)
// VideoJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of Video. This struct is safe to replace or modify but
// should not be done so concurrently.
var VideoJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *Video) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := VideoJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*Video)(nil)
// VideoJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of Video. This struct is safe to replace or modify but
// should not be done so concurrently.
var VideoJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *Video) UnmarshalJSON(b []byte) error {
return VideoJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*Video)(nil)
// EntityJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of Entity. This struct is safe to replace or modify but
// should not be done so concurrently.
@@ -298,6 +298,15 @@
"items": {
"type": "string"
}
},
"motionPhoto": {
"$ref": "#/definitions/v0MotionPhoto"
},
"video": {
"$ref": "#/definitions/v0Video"
},
"livePhoto": {
"$ref": "#/definitions/v0LivePhoto"
}
}
},
@@ -376,6 +385,29 @@
}
}
},
"v0LivePhoto": {
"type": "object",
"properties": {
"contentId": {
"type": "string"
},
"stillImageTimeUs": {
"type": "string",
"format": "int64"
},
"auto": {
"type": "boolean"
},
"vitalityScore": {
"type": "number",
"format": "double"
},
"vitalityScoringVersion": {
"type": "string",
"format": "int64"
}
}
},
"v0Match": {
"type": "object",
"properties": {
@@ -390,6 +422,23 @@
}
}
},
"v0MotionPhoto": {
"type": "object",
"properties": {
"version": {
"type": "integer",
"format": "int32"
},
"presentationTimestampUs": {
"type": "string",
"format": "int64"
},
"videoSize": {
"type": "string",
"format": "int64"
}
}
},
"v0Photo": {
"type": "object",
"properties": {
@@ -531,6 +580,49 @@
"format": "int32"
}
}
},
"v0Video": {
"type": "object",
"properties": {
"audioBitsPerSample": {
"type": "integer",
"format": "int32"
},
"audioChannels": {
"type": "integer",
"format": "int32"
},
"audioFormat": {
"type": "string"
},
"audioSamplesPerSecond": {
"type": "integer",
"format": "int32"
},
"bitrate": {
"type": "integer",
"format": "int32"
},
"duration": {
"type": "string",
"format": "int64"
},
"fourCC": {
"type": "string"
},
"frameRate": {
"type": "number",
"format": "double"
},
"height": {
"type": "integer",
"format": "int32"
},
"width": {
"type": "integer",
"format": "int32"
}
}
}
},
"externalDocs": {
@@ -59,6 +59,33 @@ message Photo {
optional google.protobuf.Timestamp takenDateTime = 9;
}
message Video {
optional int32 audioBitsPerSample = 1;
optional int32 audioChannels = 2;
optional string audioFormat = 3;
optional int32 audioSamplesPerSecond = 4;
optional int32 bitrate = 5;
optional int64 duration = 6;
optional string fourCC = 7;
optional double frameRate = 8;
optional int32 height = 9;
optional int32 width = 10;
}
message MotionPhoto {
optional int32 version = 1;
optional int64 presentationTimestampUs = 2;
optional int64 videoSize = 3;
}
message LivePhoto {
optional string contentId = 1;
optional int64 stillImageTimeUs = 2;
optional bool auto = 3;
optional double vitalityScore = 4;
optional int64 vitalityScoringVersion = 5;
}
message Entity {
Reference ref = 1;
ResourceID id = 2;
@@ -80,6 +107,9 @@ message Entity {
Image image = 18;
Photo photo = 19;
repeated string favorites = 20;
MotionPhoto motionPhoto = 21;
Video video = 22;
LivePhoto livePhoto = 23;
}
message Match {
+132 -30
View File
@@ -3,17 +3,18 @@ package command
import (
"context"
"fmt"
"strings"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/runner"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/nats-io/nats.go"
"github.com/olekukonko/errors"
"github.com/spf13/cobra"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/generators"
"github.com/opencloud-eu/opencloud/pkg/log"
natspkg "github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/registry"
"github.com/opencloud-eu/opencloud/pkg/runner"
ogrpc "github.com/opencloud-eu/opencloud/pkg/service/grpc"
"github.com/opencloud-eu/opencloud/pkg/tracing"
"github.com/opencloud-eu/opencloud/pkg/version"
@@ -24,6 +25,12 @@ import (
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/metrics"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/server/debug"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/server/http"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/activitylog"
svcEvents "github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/events"
svcHttp "github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/http"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
)
var _registeredEvents = []events.Unmarshaller{
@@ -62,19 +69,11 @@ func Server(cfg *config.Config) *cobra.Command {
gr := runner.NewGroup()
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
mtrcs := metrics.New()
mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1)
defer cancel()
connName := generators.GenerateConnectionName(cfg.Service.Name, generators.NTypeBus)
evStream, err := stream.NatsFromConfig(connName, false, stream.NatsConfig(cfg.Events))
if err != nil {
logger.Error().Err(err).Msg("Failed to initialize event stream")
return err
}
tm, err := pool.StringToTLSMode(cfg.GRPCClientTLS.Mode)
if err != nil {
logger.Error().Err(err).Msg("Failed to parse tls mode")
@@ -99,28 +98,101 @@ func Server(cfg *config.Config) *cobra.Command {
return err
}
hClient := ehsvc.NewEventHistoryService("eu.opencloud.api.eventhistory", grpcClient)
vClient := settingssvc.NewValueService("eu.opencloud.api.settings", grpcClient)
kv, err := ConnectNatsKV(cfg.Store)
if err != nil {
return err
}
activityLog, err := activitylog.New(kv,
activitylog.Logger(logger),
activitylog.MaxActivities(cfg.MaxActivities),
activitylog.WriteBufferDuration(cfg.WriteBufferDuration),
)
if err != nil {
logger.Error().Err(err).Msg("Failed to initialize activity log")
return err
}
{
svc, err := http.Server(
http.Logger(logger),
http.Config(cfg),
http.Context(ctx), // NOTE: not passing this "option" leads to a panic in go-micro
http.TraceProvider(tracerProvider),
http.Stream(evStream),
http.GatewaySelector(gatewaySelector),
http.HistoryClient(hClient),
http.ValueClient(vClient),
http.RegisteredEvents(_registeredEvents),
if !cfg.HTTP.Disabled {
hClient := ehsvc.NewEventHistoryService("eu.opencloud.api.eventhistory", grpcClient)
svc, err := svcHttp.New(
activityLog,
svcHttp.Logger(logger),
svcHttp.GatewaySelector(gatewaySelector),
svcHttp.RegisteredEvents(_registeredEvents),
//svcHttp.TraceProvider(tracerProvider),
svcHttp.HistoryClient(hClient),
)
if err != nil {
logger.Error().Err(err).Str("transport", "http").Msg("Failed to initialize server")
logger.Error().Err(err).Msg("handler init")
return err
}
// TODO svc = service.NewInstrument(svc, metrics)
// TODO svc = service.NewLogging(svc, logger) // this logs service specific data
// TODO svc = service.NewTracing(svc, traceProvider)
vClient := settingssvc.NewValueService("eu.opencloud.api.settings", grpcClient)
server, err := http.Server(
http.ValueClient(vClient),
http.Logger(logger),
http.Context(ctx),
http.Config(cfg),
http.Service(svc),
)
if err != nil {
logger.Info().
Err(err).
Str("transport", "http").
Msg("Failed to initialize server")
return err
}
gr.Add(runner.NewGoMicroHttpServerRunner(cfg.Service.Name+".http", svc))
gr.Add(runner.NewGoMicroHttpServerRunner(cfg.Service.Name+".http", server))
} else {
logger.Info().Msg("HTTP server disabled, not starting HTTP service")
}
if !cfg.Events.Disabled {
connName := generators.GenerateConnectionName(cfg.Service.Name, generators.NTypeBus)
evStream, err := stream.NatsFromConfig(connName, false, stream.NatsConfig{
Endpoint: cfg.Events.Endpoint,
Cluster: cfg.Events.Cluster,
EnableTLS: cfg.Events.EnableTLS,
TLSInsecure: cfg.Events.TLSInsecure,
TLSRootCACertificate: cfg.Events.TLSRootCACertificate,
AuthUsername: cfg.Events.AuthUsername,
AuthPassword: cfg.Events.AuthPassword,
})
if err != nil {
logger.Error().Err(err).Msg("Failed to initialize event stream")
return err
}
eventSvc, err := svcEvents.New(
activityLog,
evStream,
svcEvents.Context(ctx),
svcEvents.Logger(logger),
svcEvents.ServiceAccount(cfg.ServiceAccount),
svcEvents.GatewaySelector(gatewaySelector),
svcEvents.RegisteredEvents(_registeredEvents),
svcEvents.NumConsumers(cfg.NumConsumers),
)
if err != nil {
logger.Error().Err(err).Str("transport", "event").Msg("Failed to initialize server")
return err
}
gr.Add(runner.New(cfg.Service.Name+".svc", func() error {
return eventSvc.Run()
}, func() {
eventSvc.Close()
}))
} else {
logger.Info().Msg("event listening disabled, not starting event service")
}
{
@@ -149,3 +221,33 @@ func Server(cfg *config.Config) *cobra.Command {
},
}
}
func ConnectNatsKV(cfg config.Store) (nats.KeyValue, error) {
// Connect to NATS servers
secureOption := natspkg.Secure(cfg.EnableTLS, cfg.TLSInsecure, cfg.TLSRootCACertificate)
conn, err := nats.Connect(strings.Join(cfg.Nodes, ","), secureOption, nats.UserInfo(cfg.AuthUsername, cfg.AuthPassword))
if err != nil {
return nil, err
}
js, err := conn.JetStream()
if err != nil {
return nil, err
}
kv, err := js.KeyValue(cfg.Database)
if err != nil {
if !errors.Is(err, nats.ErrBucketNotFound) {
return nil, errors.Wrapf(err, "Failed to get bucket (%s)", cfg.Database)
}
kv, err = js.CreateKeyValue(&nats.KeyValueConfig{
Bucket: cfg.Database,
})
if err != nil {
return nil, errors.Wrapf(err, "Failed to create bucket (%s)", cfg.Database)
}
}
return kv, nil
}
@@ -35,10 +35,12 @@ type Config struct {
WriteBufferDuration time.Duration `yaml:"write_buffer_duration" env:"ACTIVITYLOG_WRITE_BUFFER_DURATION" desc:"The duration to wait before flushing the write buffer. This is used to reduce the number of writes to the store." introductionVersion:"4.0.0"`
MaxActivities int `yaml:"max_activities" env:"ACTIVITYLOG_MAX_ACTIVITIES" desc:"The maximum number of activities to keep in the store per resource. If the number of activities exceeds this value, the oldest activities will be removed." introductionVersion:"4.0.0"`
NumConsumers int `yaml:"num_consumers" env:"ACTIVITYLOG_NUM_CONSUMERS" desc:"The amount of concurrent event consumers to start. Event consumers are used for updating the list of activities. Multiple consumers increase parallelisation, but will also increase CPU and memory demands." introductionVersion:"%NEXT%"`
}
// Events combines the configuration options for the event bus.
type Events struct {
Disabled bool `yaml:"disabled" env:"ACTIVITYLOG_EVENTS_DISABLED" desc:"Disables listening for events. Set this to true if the service should only handle HTTP requests." introductionVersion:"%NEXT%"`
Endpoint string `yaml:"endpoint" env:"OC_EVENTS_ENDPOINT" desc:"The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture." introductionVersion:"1.0.0"`
Cluster string `yaml:"cluster" env:"OC_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system." introductionVersion:"1.0.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"1.0.0"`
@@ -77,6 +79,7 @@ type CORS struct {
// HTTP defines the available http configuration.
type HTTP struct {
Disabled bool `yaml:"disabled" env:"ACTIVITYLOG_HTTP_DISABLED" desc:"Disables the HTTP service. Set this to true if the service should only handle events." introductionVersion:"1.0.0"`
Addr string `yaml:"addr" env:"ACTIVITYLOG_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"1.0.0"`
Namespace string `yaml:"-"`
Root string `yaml:"root" env:"ACTIVITYLOG_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"1.0.0"`
@@ -52,7 +52,9 @@ func DefaultConfig() *config.Config {
},
},
WriteBufferDuration: 10 * time.Second,
MaxActivities: 6000,
// Nats runs into max payload exceeded errors at around 7k activities. Let's keep a buffer.
MaxActivities: 6000,
NumConsumers: 1,
}
}
@@ -4,6 +4,7 @@ import (
"errors"
occfg "github.com/opencloud-eu/opencloud/pkg/config"
"github.com/opencloud-eu/opencloud/pkg/shared"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config/defaults"
@@ -34,5 +35,8 @@ func ParseConfig(cfg *config.Config) error {
// Validate validates the config
func Validate(cfg *config.Config) error {
if cfg.Events.Disabled && cfg.HTTP.Disabled {
return shared.AllComponentsDisabledError(cfg.Service.Name)
}
return nil
}
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Ivan Fustero, 2025\n"
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jörn Friedrich Dreyer <jfd@butonic.de>, 2025\n"
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
"Language-Team: Greek (https://app.transifex.com/opencloud-eu/teams/204053/el/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Elías Martín, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/opencloud-eu/teams/204053/fi/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: mitibor, 2026\n"
"Language-Team: Hungarian (https://app.transifex.com/opencloud-eu/teams/204053/hu/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Simone Broglia, 2025\n"
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: iikaka88, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/opencloud-eu/teams/204053/ja/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: 4580f07cef35810b72f8a1e8fd521c9a_6684a19 <07c3e3571dc5eebd81f2c3a10488f503_1171750>, 2025\n"
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: BoneNI, 2026\n"
"Language-Team: Lao (https://app.transifex.com/opencloud-eu/teams/204053/lo/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Matt, 2026\n"
"Language-Team: Polish (https://app.transifex.com/opencloud-eu/teams/204053/pl/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Mário Machado, 2025\n"
"Language-Team: Portuguese (https://app.transifex.com/opencloud-eu/teams/204053/pt/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Lulufox, 2025\n"
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Daniel Nylander <po@danielnylander.se>, 2025\n"
"Language-Team: Swedish (https://app.transifex.com/opencloud-eu/teams/204053/sv/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Quan Tran, 2025\n"
"Language-Team: Vietnamese (https://app.transifex.com/opencloud-eu/teams/204053/vi/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-07-19 23:15+0000\n"
"POT-Creation-Date: 2026-08-28 23:16+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: YQS Yang, 2025\n"
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"
+23 -71
View File
@@ -3,18 +3,12 @@ package http
import (
"context"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/log"
ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0"
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/metrics"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/spf13/pflag"
"go-micro.dev/v4/store"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
// Option defines a single option function.
@@ -22,19 +16,15 @@ type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
Metrics *metrics.Metrics
Flags []pflag.Flag
Namespace string
Store store.Store
Stream events.Stream
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
TraceProvider trace.TracerProvider
HistoryClient ehsvc.EventHistoryService
ValueClient settingssvc.ValueService
RegisteredEvents []events.Unmarshaller
Name string
Namespace string
Logger log.Logger
Context context.Context
Config *config.Config
Flags []pflag.Flag
Service ActivityLogService
TraceProvider trace.TracerProvider
ValueClient settingssvc.ValueService
}
// newOptions initializes the available default options.
@@ -69,10 +59,10 @@ func Config(val *config.Config) Option {
}
}
// Metrics provides a function to set the metrics option.
func Metrics(val *metrics.Metrics) Option {
// Service provides a function to set the service option.
func Service(val ActivityLogService) Option {
return func(o *Options) {
o.Metrics = val
o.Service = val
}
}
@@ -83,58 +73,20 @@ func Flags(flags ...pflag.Flag) Option {
}
}
// Namespace provides a function to set the Namespace option.
func Namespace(val string) Option {
// TraceProvider provides a function to configure the trace provider
func TraceProvider(traceProvider trace.TracerProvider) Option {
return func(o *Options) {
o.Namespace = val
if traceProvider != nil {
o.TraceProvider = traceProvider
} else {
o.TraceProvider = noop.NewTracerProvider()
}
}
}
// Store provides a function to configure the store
func Store(store store.Store) Option {
// ValueClient adds a grpc client for the value service
func ValueClient(vs settingssvc.ValueService) Option {
return func(o *Options) {
o.Store = store
}
}
// Stream provides a function to configure the stream
func Stream(stream events.Stream) Option {
return func(o *Options) {
o.Stream = stream
}
}
// GatewaySelector provides a function to configure the gateway client selector
func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option {
return func(o *Options) {
o.GatewaySelector = gatewaySelector
}
}
// HistoryClient provides a function to configure the event history client
func HistoryClient(h ehsvc.EventHistoryService) Option {
return func(o *Options) {
o.HistoryClient = h
}
}
// RegisteredEvents provides a function to register events
func RegisteredEvents(evs []events.Unmarshaller) Option {
return func(o *Options) {
o.RegisteredEvents = evs
}
}
// TraceProvider provides a function to set the TracerProvider option
func TraceProvider(val trace.TracerProvider) Option {
return func(o *Options) {
o.TraceProvider = val
}
}
// ValueClient provides a function to set the ValueClient options
func ValueClient(val settingssvc.ValueService) Option {
return func(o *Options) {
o.ValueClient = val
o.ValueClient = vs
}
}
+103 -46
View File
@@ -1,49 +1,63 @@
package http
import (
"fmt"
stdhttp "net/http"
"context"
"embed"
"encoding/json"
"errors"
"net/http"
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
"github.com/opencloud-eu/opencloud/pkg/account"
"github.com/opencloud-eu/opencloud/pkg/cors"
"github.com/opencloud-eu/opencloud/pkg/l10n"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/middleware"
"github.com/opencloud-eu/opencloud/pkg/service/http"
"github.com/opencloud-eu/opencloud/pkg/tracing"
ohttp "github.com/opencloud-eu/opencloud/pkg/service/http"
"github.com/opencloud-eu/opencloud/pkg/version"
svc "github.com/opencloud-eu/opencloud/services/activitylog/pkg/service"
"github.com/riandyrn/otelchi"
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
activityloghttp "github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/http"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"go-micro.dev/v4"
"google.golang.org/grpc/metadata"
)
// Service is the service interface
type Service any
var (
//go:embed l10n/locale
_localeFS embed.FS
// subfolder where the translation files are stored
_localeSubPath = "l10n/locale"
// domain of the activitylog service (transifex)
_domain = "activitylog"
)
// Server initializes the http service and server.
func Server(opts ...Option) (http.Service, error) {
func Server(opts ...Option) (ohttp.Service, error) {
options := newOptions(opts...)
service := options.Service
service, err := http.NewService(
http.TLSConfig(options.Config.HTTP.TLS),
http.Logger(options.Logger),
http.Namespace(options.Config.HTTP.Namespace),
http.Name(options.Config.Service.Name),
http.Version(version.GetString()),
http.Address(options.Config.HTTP.Addr),
http.Context(options.Context),
http.Flags(options.Flags...),
http.TraceProvider(options.TraceProvider),
newService, err := ohttp.NewService(
ohttp.TLSConfig(options.Config.HTTP.TLS),
ohttp.Logger(options.Logger),
ohttp.Namespace(options.Config.HTTP.Namespace),
ohttp.Name(options.Config.Service.Name),
ohttp.Version(version.GetString()),
ohttp.Address(options.Config.HTTP.Addr),
ohttp.Context(options.Context),
ohttp.Flags(options.Flags...),
)
if err != nil {
options.Logger.Error().
Err(err).
Msg("Error initializing http service")
return http.Service{}, fmt.Errorf("could not initialize http service: %w", err)
return ohttp.Service{}, err
}
middlewares := []func(stdhttp.Handler) stdhttp.Handler{
middlewares := []func(http.Handler) http.Handler{
chimiddleware.RequestID,
middleware.Version(
options.Config.Service.Name,
@@ -52,6 +66,7 @@ func Server(opts ...Option) (http.Service, error) {
middleware.Logger(
options.Logger,
),
middleware.TraceContext,
middleware.ExtractAccountUUID(
account.Logger(options.Logger),
account.JWTSecret(options.Config.TokenManager.JWTSecret),
@@ -68,33 +83,75 @@ func Server(opts ...Option) (http.Service, error) {
mux := chi.NewMux()
mux.Use(middlewares...)
mux.Use(
otelchi.Middleware(
"actitivylog",
otelchi.WithChiRoutes(mux),
otelchi.WithTracerProvider(options.TraceProvider),
otelchi.WithPropagators(tracing.GetPropagator()),
),
)
t := l10n.NewTranslatorFromCommonConfig(options.Config.DefaultLanguage, _domain, options.Config.TranslationPath, _localeFS, _localeSubPath)
mux.Route(options.Config.HTTP.Root, func(r chi.Router) {
r.Get("/graph/v1beta1/extensions/org.libregraph/activities", GetItemActivitiesHandler(options.Logger, service, options.ValueClient, t))
})
handle, err := svc.New(
svc.Logger(options.Logger),
svc.Stream(options.Stream),
svc.Mux(mux),
svc.Config(options.Config),
svc.GatewaySelector(options.GatewaySelector),
svc.TraceProvider(options.TraceProvider),
svc.HistoryClient(options.HistoryClient),
svc.ValueClient(options.ValueClient),
svc.RegisteredEvents(options.RegisteredEvents),
)
err = micro.RegisterHandler(newService.Server(), mux)
if err != nil {
return http.Service{}, err
options.Logger.Fatal().Err(err).Msg("failed to register the handler")
}
if err := micro.RegisterHandler(service.Server(), handle); err != nil {
return http.Service{}, err
}
newService.Init()
return newService, nil
return service, nil
}
// Service defines the business logic implementations need to provide.
type ActivityLogService interface {
GetItemActivities(ctx context.Context, query, loc string, t l10n.Translator) ([]libregraph.Activity, error)
}
// GetActivitiesResponse is the response on GET activities requests
type GetActivitiesResponse struct {
Activities []libregraph.Activity `json:"value"`
}
func GetItemActivitiesHandler(log log.Logger, s ActivityLogService, vc settingssvc.ValueService, t l10n.Translator) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, r.Header.Get(revactx.TokenHeader))
activeUser, ok := revactx.ContextGetUser(ctx)
if !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
loc := l10n.MustGetUserLocale(ctx, activeUser.GetId().GetOpaqueId(), r.Header.Get(l10n.HeaderAcceptLanguage), vc)
activities, err := s.GetItemActivities(ctx, r.URL.Query().Get("kql"), loc, t)
if err != nil {
switch {
case errors.Is(err, activityloghttp.ErrBadRequest):
log.Debug().Str("query", r.URL.Query().Get("kql")).Err(err).Msg("error getting activities")
w.WriteHeader(http.StatusBadRequest)
return
case errors.Is(err, activityloghttp.ErrForbidden):
log.Debug().Err(err).Msg("error getting activities")
w.WriteHeader(http.StatusForbidden)
return
default:
log.Error().Err(err).Msg("error getting activities")
w.WriteHeader(http.StatusInternalServerError)
return
}
}
res := GetActivitiesResponse{
Activities: activities,
}
w.Header().Set("Content-Type", "application/json; odata.metadata=minimal")
w.Header().Set("OData-Version", "4.0")
if reqID := chimiddleware.GetReqID(ctx); reqID != "" {
w.Header().Set("request-id", reqID)
}
w.Header().Set("Cache-Control", "no-cache")
if err := json.NewEncoder(w).Encode(res); err != nil {
log.Error().Err(err).Msg("error encoding activities")
w.WriteHeader(http.StatusInternalServerError)
return
}
}
}
@@ -0,0 +1,366 @@
package activitylog
import (
"context"
"encoding/base32"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
"sync"
"time"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/jellydator/ttlcache/v2"
"github.com/nats-io/nats.go"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/vmihailenco/msgpack/v5"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
var tracer trace.Tracer
func init() {
tracer = otel.Tracer("github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/activitylog")
}
var (
_maxActivitiesDefault = 6000
_writeBufferDuration = 10 * time.Second
)
// Activitylog stores and retrieves activities for resources and their parents from a nats kv
type ActivityLog struct {
log log.Logger
// FIXME the lock does not protect agains concurrent resource activities on multiple instances
// known since https://github.com/owncloud/ocis/pull/9361#pullrequestreview-2135350157
// current ocis discussion in https://github.com/owncloud/ocis/issues/12475
lock sync.RWMutex
debouncer *Debouncer
parentIdCache *ttlcache.Cache
natskv nats.KeyValue
maxActivities int
}
type batchInfo struct {
key string
count int
timestamp time.Time
}
// New creates a new ActivitylogService
func New(kv nats.KeyValue, opts ...Option) (*ActivityLog, error) {
o := &Options{
MaxActivities: _maxActivitiesDefault,
WriteBufferDuration: _writeBufferDuration,
Logger: log.NopLogger(),
}
for _, opt := range opts {
opt(o)
}
cache := ttlcache.NewCache()
err := cache.SetTTL(30 * time.Second)
if err != nil {
return nil, err
}
s := &ActivityLog{
log: o.Logger,
lock: sync.RWMutex{},
parentIdCache: cache,
maxActivities: o.MaxActivities,
natskv: kv,
}
s.debouncer = NewDebouncer(o.WriteBufferDuration, s.StoreActivity)
// run migrations
err = s.runMigrations(context.Background(), kv)
if err != nil {
return nil, err
}
return s, nil
}
// RemoveResource removes the resource from the store
func (a *ActivityLog) RemoveResource(rid *provider.ResourceId) error {
if rid == nil {
return fmt.Errorf("resource id is required")
}
a.lock.Lock()
defer a.lock.Unlock()
err := a.natskv.Delete(storagespace.FormatResourceID(rid))
if err != nil {
return fmt.Errorf("could not delete resource %s: %w", rid.OpaqueId, err)
}
return nil
}
// RawActivity represents an activity as it is stored in the activitylog store
type RawActivity struct {
EventID string `json:"event_id"`
Depth int `json:"depth"`
Timestamp time.Time `json:"timestamp"`
}
func (a *ActivityLog) AddActivity(ctx context.Context, initRef *provider.Reference, parentId *provider.ResourceId, eventID string, timestamp time.Time, getResource func(context.Context, *provider.Reference) (*provider.ResourceInfo, error)) error {
var (
err error
depth int
ref = initRef
)
ctx, span := tracer.Start(ctx, "AddActivity")
defer span.End()
for {
var info *provider.ResourceInfo
id := ref.GetResourceId()
if ref.Path != "" {
// Path based reference, we need to resolve the resource id
ctx, span = tracer.Start(ctx, "AddActivity.getResource")
info, err = getResource(ctx, ref)
span.End()
if err != nil {
// TODO If the resource was deleted should we still log an activity in the parent?
return fmt.Errorf("could not get resource info for reference %v: %w", ref, err)
}
id = info.GetId()
}
if id == nil {
return fmt.Errorf("resource id is required")
}
key := storagespace.FormatResourceID(id)
a.debouncer.Debounce(key, RawActivity{
EventID: eventID,
Depth: depth,
Timestamp: timestamp,
})
if id.OpaqueId == id.SpaceId {
// we are at the root of the space, no need to go further
break
}
// check if parent id is cached
// parent id is cached in the format <storageid>$<spaceid>!<resourceid>
// if it is not cached, get the resource info and cache it
if parentId == nil {
if v, err := a.parentIdCache.Get(key); err != nil {
if info == nil {
ctx, span := tracer.Start(ctx, "AddActivity.getResource parent")
info, err = getResource(ctx, ref)
span.End()
if err != nil || info.GetParentId() == nil || info.GetParentId().GetOpaqueId() == "" {
return fmt.Errorf("could not get parent id: %w", err)
}
}
parentId = info.GetParentId()
a.parentIdCache.Set(key, parentId)
} else {
parentId = v.(*provider.ResourceId)
}
} else {
a.log.Debug().Msg("parent id is cached")
}
depth++
ref = &provider.Reference{ResourceId: parentId}
parentId = nil // reset parent id so it's not reused in the next iteration
}
return nil
}
func (a *ActivityLog) StoreActivity(resourceID string, activities []RawActivity) error {
a.lock.Lock()
defer a.lock.Unlock()
ctx, span := tracer.Start(context.Background(), "storeActivity")
defer span.End()
_, subspan := tracer.Start(ctx, "storeActivity.Marshal")
b, err := msgpack.Marshal(activities)
if err != nil {
return err
}
subspan.End()
_, subspan = tracer.Start(ctx, "storeActivity.natskv.Put")
key := natsKey(resourceID, len(activities))
_, err = a.natskv.Put(key, b)
if err != nil {
return err
}
subspan.End()
ctx, subspan = tracer.Start(ctx, "storeActivity.enforceMaxActivities")
a.enforceMaxActivities(ctx, resourceID)
subspan.End()
return nil
}
func (a *ActivityLog) enforceMaxActivities(ctx context.Context, resourceID string) {
if a.maxActivities <= 0 {
return
}
key := fmt.Sprintf("%s.>", base32.StdEncoding.EncodeToString([]byte(resourceID)))
_, subspan := tracer.Start(ctx, "enforceMaxActivities.watch")
watcher, err := a.natskv.Watch(key, nats.IgnoreDeletes())
if err != nil {
a.log.Error().Err(err).Str("resourceID", resourceID).Msg("could not watch")
return
}
defer watcher.Stop()
var keys []string
for update := range watcher.Updates() {
if update == nil {
break
}
var batchActivities []RawActivity
if err := msgpack.Unmarshal(update.Value(), &batchActivities); err != nil {
a.log.Debug().Err(err).Str("resourceID", resourceID).Msg("could not unmarshal messagepack, trying json")
}
keys = append(keys, update.Key())
}
subspan.End()
_, subspan = tracer.Start(ctx, "enforceMaxActivities.compile")
// Parse keys into batches
batches := make([]batchInfo, 0)
var activitiesCount int
for _, k := range keys {
parts := strings.SplitN(k, ".", 3)
if len(parts) < 3 {
a.log.Warn().Str("key", k).Msg("skipping key, not enough parts")
continue
}
c, err := strconv.Atoi(parts[1])
if err != nil {
a.log.Warn().Str("key", k).Msg("skipping key, can not parse count")
continue
}
// parse timestamp
nano, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil {
a.log.Warn().Str("key", k).Msg("skipping key, can not parse timestamp")
continue
}
batches = append(batches, batchInfo{
key: k,
count: c,
timestamp: time.Unix(0, nano),
})
activitiesCount += c
}
// sort batches by timestamp
sort.Slice(batches, func(i, j int) bool {
return batches[i].timestamp.Before(batches[j].timestamp)
})
subspan.End()
_, subspan = tracer.Start(ctx, "enforceMaxActivities.delete")
// remove oldest keys until we are at max activities
for _, b := range batches {
if activitiesCount-b.count < a.maxActivities {
break
}
activitiesCount -= b.count
err = a.natskv.Delete(b.key)
if err != nil {
a.log.Error().Err(err).Str("key", b.key).Msg("could not delete key")
break
}
}
subspan.End()
}
func (a *ActivityLog) InvalidateCachedParentID(purgeId *provider.ResourceId) {
// The parent id cache is populated lazily and its entries expire, so a
// missing key is the expected case rather than an error.
if err := a.parentIdCache.Remove(storagespace.FormatResourceID(purgeId)); err != nil {
a.log.Debug().Interface("event", purgeId).Err(err).Msg("could not delete parent id cache")
}
}
func natsKey(resourceID string, activitiesCount int) string {
return fmt.Sprintf("%s.%d.%d",
base32.StdEncoding.EncodeToString([]byte(resourceID)),
activitiesCount,
time.Now().UnixNano())
}
func (a *ActivityLog) Activities(rid *provider.ResourceId) ([]RawActivity, error) {
a.lock.RLock()
defer a.lock.RUnlock()
return a.activities(rid)
}
func (a *ActivityLog) activities(rid *provider.ResourceId) ([]RawActivity, error) {
resourceID := storagespace.FormatResourceID(rid)
glob := fmt.Sprintf("%s.>", base32.StdEncoding.EncodeToString([]byte(resourceID)))
watcher, err := a.natskv.Watch(glob, nats.IgnoreDeletes())
if err != nil {
return nil, err
}
defer watcher.Stop()
var activities []RawActivity
for update := range watcher.Updates() {
if update == nil {
break
}
var batchActivities []RawActivity
if err := msgpack.Unmarshal(update.Value(), &batchActivities); err != nil {
a.log.Debug().Err(err).Str("resourceID", resourceID).Msg("could not unmarshal messagepack")
}
activities = append(activities, batchActivities...)
}
return activities, nil
}
// RemoveActivities removes the activities from the given resource
func (a *ActivityLog) RemoveActivities(rid *provider.ResourceId, toDelete map[string]struct{}) error {
a.lock.Lock()
defer a.lock.Unlock()
curActivities, err := a.activities(rid)
if err != nil {
return err
}
var acts []RawActivity
for _, a := range curActivities {
if _, ok := toDelete[a.EventID]; !ok {
acts = append(acts, a)
}
}
b, err := json.Marshal(acts)
if err != nil {
return err
}
_, err = a.natskv.Put(storagespace.FormatResourceID(rid), b)
return err
}
@@ -0,0 +1,13 @@
package activitylog_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestActivitylog(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Activitylog Suite")
}
@@ -1,4 +1,4 @@
package service
package activitylog_test
import (
"context"
@@ -8,15 +8,13 @@ import (
"time"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
nserver "github.com/nats-io/nats-server/v2/server"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/command"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
eventsmocks "github.com/opencloud-eu/reva/v2/pkg/events/mocks"
"github.com/test-go/testify/mock"
"go.opentelemetry.io/otel/trace/noop"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/activitylog"
)
var (
@@ -65,31 +63,25 @@ var _ = SynchronizedAfterSuite(func() {
var _ = Describe("ActivitylogService", func() {
var (
alog *ActivitylogService
alog *activitylog.ActivityLog
getResource func(_ context.Context, ref *provider.Reference) (*provider.ResourceInfo, error)
writebufferduration = 100 * time.Millisecond
)
JustBeforeEach(func() {
var err error
stream := &eventsmocks.Stream{}
stream.EXPECT().Consume(mock.Anything, mock.Anything).Return(nil, nil)
alog, err = New(
Config(&config.Config{
Service: config.Service{
Name: "activitylog-test",
},
Store: config.Store{
Store: "nats-js-kv",
Nodes: []string{server.Addr().String()},
Database: "activitylog-test-" + uuid.New().String(),
},
MaxActivities: 4,
WriteBufferDuration: writebufferduration,
}),
Stream(stream),
TraceProvider(noop.NewTracerProvider()),
Mux(chi.NewMux()),
db := "activitylog-test-" + uuid.New().String()
kv, err := command.ConnectNatsKV(config.Store{
Nodes: []string{server.Addr().String()},
Database: db,
})
Expect(err).ToNot(HaveOccurred())
alog, err = activitylog.New(
kv,
activitylog.MaxActivities(4),
activitylog.WriteBufferDuration(writebufferduration),
)
Expect(err).ToNot(HaveOccurred())
})
@@ -104,7 +96,7 @@ var _ = Describe("ActivitylogService", func() {
Name string
Tree map[string]*provider.ResourceInfo
Activities map[string]string
Expected map[string][]RawActivity
Expected map[string][]activitylog.RawActivity
}
testCases := []testCase{
@@ -118,7 +110,7 @@ var _ = Describe("ActivitylogService", func() {
Activities: map[string]string{
"activity": "base",
},
Expected: map[string][]RawActivity{
Expected: map[string][]activitylog.RawActivity{
"base": activitites("activity", 0),
"parent": activitites("activity", 1),
"spaceid": activitites("activity", 2),
@@ -135,7 +127,7 @@ var _ = Describe("ActivitylogService", func() {
"activity1": "base",
"activity2": "base",
},
Expected: map[string][]RawActivity{
Expected: map[string][]activitylog.RawActivity{
"base": activitites("activity1", 0, "activity2", 0),
"parent": activitites("activity1", 1, "activity2", 1),
"spaceid": activitites("activity1", 2, "activity2", 2),
@@ -152,7 +144,7 @@ var _ = Describe("ActivitylogService", func() {
}
for k, v := range tc.Activities {
err := alog.addActivity(context.Background(), reference(v), nil, k, time.Time{}, getResource)
err := alog.AddActivity(context.Background(), reference(v), nil, k, time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
}
})
@@ -191,9 +183,9 @@ var _ = Describe("ActivitylogService", func() {
It("debounces activities", func() {
err := alog.addActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
err := alog.AddActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
Eventually(func(g Gomega) {
@@ -204,7 +196,7 @@ var _ = Describe("ActivitylogService", func() {
})
It("adheres to the MaxActivities setting", func() {
err := alog.addActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
err := alog.AddActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
Eventually(func(g Gomega) {
activities, err := alog.Activities(resourceID("base"))
@@ -212,7 +204,7 @@ var _ = Describe("ActivitylogService", func() {
g.Expect(len(activities)).To(Equal(1))
}).Should(Succeed())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
Eventually(func(g Gomega) {
activities, err := alog.Activities(resourceID("base"))
@@ -220,11 +212,11 @@ var _ = Describe("ActivitylogService", func() {
g.Expect(len(activities)).To(Equal(2))
}).Should(Succeed())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity3", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity3", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity4", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity4", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity5", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity5", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
Eventually(func(g Gomega) {
@@ -241,9 +233,9 @@ var _ = Describe("ActivitylogService", func() {
return tree[ref.GetResourceId().GetOpaqueId()], nil
}
err := alog.addActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
err := alog.AddActivity(context.Background(), reference("base"), nil, "activity1", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity2", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
Eventually(func(g Gomega) {
@@ -252,9 +244,9 @@ var _ = Describe("ActivitylogService", func() {
g.Expect(activities).To(ConsistOf(activitites("activity1", 0, "activity2", 0)))
}).Should(Succeed())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity3", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity3", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
err = alog.addActivity(context.Background(), reference("base"), nil, "activity4", time.Time{}, getResource)
err = alog.AddActivity(context.Background(), reference("base"), nil, "activity4", time.Time{}, getResource)
Expect(err).NotTo(HaveOccurred())
Eventually(func(g Gomega) {
@@ -267,9 +259,9 @@ var _ = Describe("ActivitylogService", func() {
})
})
func activitites(acts ...any) []RawActivity {
var activities []RawActivity
act := RawActivity{}
func activitites(acts ...any) []activitylog.RawActivity {
var activities []activitylog.RawActivity
act := activitylog.RawActivity{}
for _, a := range acts {
switch v := a.(type) {
case string:
@@ -0,0 +1,76 @@
package activitylog
import (
"sync"
"time"
)
// Debouncer is used to debounce writes to the activity log store.
type Debouncer struct {
after time.Duration
f func(id string, ra []RawActivity) error
pending sync.Map
inProgress sync.Map
mutex sync.Mutex
}
type queueItem struct {
activities []RawActivity
timer *time.Timer
}
// NewDebouncer returns a new Debouncer instance.
func NewDebouncer(d time.Duration, f func(id string, ra []RawActivity) error) *Debouncer {
return &Debouncer{
after: d,
f: f,
pending: sync.Map{},
inProgress: sync.Map{},
}
}
// Debounce restarts the debounce timer for the given space.
func (d *Debouncer) Debounce(id string, ra RawActivity) {
if d.after == 0 {
d.f(id, []RawActivity{ra})
return
}
d.mutex.Lock()
defer d.mutex.Unlock()
item := &queueItem{
activities: []RawActivity{ra},
}
if i, ok := d.pending.Load(id); ok {
// if the item is already in the queue, append the new activities
item, ok = i.(*queueItem)
if ok {
item.activities = append(item.activities, ra)
}
}
if item.timer == nil {
item.timer = time.AfterFunc(d.after, func() {
if _, ok := d.inProgress.Load(id); ok {
// Reschedule this run for when the previous run has finished
d.mutex.Lock()
if i, ok := d.pending.Load(id); ok {
i.(*queueItem).timer.Reset(d.after)
}
d.mutex.Unlock()
return
}
d.pending.Delete(id)
d.inProgress.Store(id, true)
defer d.inProgress.Delete(id)
d.f(id, item.activities)
})
}
d.pending.Store(id, item)
}
@@ -0,0 +1,121 @@
package activitylog_test
import (
"sync"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/activitylog"
)
var _ = Describe("Debouncer", func() {
var (
mu sync.Mutex
callbacks []activitylog.RawActivity
newCallback func(id string, ra []activitylog.RawActivity) error
)
BeforeEach(func() {
mu.Lock()
callbacks = nil
mu.Unlock()
newCallback = func(id string, ra []activitylog.RawActivity) error {
mu.Lock()
defer mu.Unlock()
callbacks = append(callbacks, ra...)
return nil
}
})
Context("with zero duration", func() {
It("calls the callback immediately", func() {
d := activitylog.NewDebouncer(0, newCallback)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity1"})
Expect(callbacks).To(HaveLen(1))
Expect(callbacks[0].EventID).To(Equal("activity1"))
})
It("calls the callback immediately for each event", func() {
d := activitylog.NewDebouncer(0, newCallback)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity1"})
d.Debounce("space2", activitylog.RawActivity{EventID: "activity2"})
Expect(callbacks).To(HaveLen(2))
})
})
Context("with non-zero duration", func() {
It("batches activities with the same id", func() {
d := activitylog.NewDebouncer(10*time.Millisecond, newCallback)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity1"})
d.Debounce("space1", activitylog.RawActivity{EventID: "activity2"})
d.Debounce("space1", activitylog.RawActivity{EventID: "activity3"})
Eventually(func() int {
mu.Lock()
defer mu.Unlock()
return len(callbacks)
}).Should(Equal(3))
})
It("handles different ids independently", func() {
d := activitylog.NewDebouncer(10*time.Millisecond, newCallback)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity1"})
d.Debounce("space2", activitylog.RawActivity{EventID: "activity2"})
Eventually(func() int {
mu.Lock()
defer mu.Unlock()
return len(callbacks)
}).Should(Equal(2))
})
It("batches activities that arrive within the debounce window", func() {
d := activitylog.NewDebouncer(100*time.Millisecond, newCallback)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity1"})
time.Sleep(20 * time.Millisecond)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity2"})
time.Sleep(20 * time.Millisecond)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity3"})
Eventually(func() int {
mu.Lock()
defer mu.Unlock()
return len(callbacks)
}).Should(Equal(3))
})
It("processes new batch after previous completes", func() {
d := activitylog.NewDebouncer(5*time.Millisecond, newCallback)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity1"})
time.Sleep(20 * time.Millisecond) // let first batch complete
d.Debounce("space1", activitylog.RawActivity{EventID: "activity2"})
Eventually(func() int {
mu.Lock()
defer mu.Unlock()
return len(callbacks)
}).Should(Equal(2))
})
It("skips duplicate write when timer fires during in-progress callback", func() {
slowCallback := func(id string, ra []activitylog.RawActivity) error {
time.Sleep(50 * time.Millisecond) // simulate slow write
mu.Lock()
defer mu.Unlock()
callbacks = append(callbacks, ra...)
return nil
}
d := activitylog.NewDebouncer(10*time.Millisecond, slowCallback)
d.Debounce("space1", activitylog.RawActivity{EventID: "activity1"})
time.Sleep(20 * time.Millisecond) // timer fires while callback is running
Eventually(func() int {
mu.Lock()
defer mu.Unlock()
return len(callbacks)
}).Should(Equal(1))
})
})
})
@@ -1,4 +1,4 @@
package service
package activitylog
import (
"context"
@@ -15,7 +15,7 @@ const currentMigrationVersion = "1"
// RunMigrations checks the activitylog data version and runs migrations if necessary.
// It should be called during service startup, after the NATS KeyValue store is initialized.
func (a *ActivitylogService) runMigrations(ctx context.Context, kv nats.KeyValue) error {
func (a *ActivityLog) runMigrations(ctx context.Context, kv nats.KeyValue) error {
entry, err := kv.Get(activitylogVersionKey)
if err == nats.ErrKeyNotFound {
a.log.Info().Msg("activitylog version key not found. Running migration to V1...")
@@ -40,7 +40,7 @@ func (a *ActivitylogService) runMigrations(ctx context.Context, kv nats.KeyValue
// For each such key, it creates a new key in the format "originalKey.count.timestamp"
// and stores the original list of strings (re-marshalled to messagepack) as its value.
// Finally, it sets the activitylog.version key to "1".
func (a *ActivitylogService) migrateToV1(_ context.Context, kv nats.KeyValue) error {
func (a *ActivityLog) migrateToV1(_ context.Context, kv nats.KeyValue) error {
lister, err := kv.ListKeys()
if err != nil {
return fmt.Errorf("migrateToV1: failed to list keys from NATS KV store: %w", err)
@@ -0,0 +1,35 @@
package activitylog
import (
"time"
"github.com/opencloud-eu/opencloud/pkg/log"
)
// Option for the activitylog service
type Option func(*Options)
// Options for the activitylog service
type Options struct {
Logger log.Logger
MaxActivities int
WriteBufferDuration time.Duration
}
// Logger configures a logger for the activitylog service
func Logger(log log.Logger) Option {
return func(o *Options) {
o.Logger = log
}
}
func MaxActivities(max int) Option {
return func(o *Options) {
o.MaxActivities = max
}
}
func WriteBufferDuration(d time.Duration) Option {
return func(o *Options) {
o.WriteBufferDuration = d
}
}
@@ -0,0 +1,13 @@
package events_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestEvents(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Events Suite")
}
@@ -1,17 +1,14 @@
package service
package events
import (
"context"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/go-chi/chi/v5"
"github.com/opencloud-eu/opencloud/pkg/log"
ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0"
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"go.opentelemetry.io/otel/trace"
)
// Option for the activitylog service
@@ -19,17 +16,20 @@ type Option func(*Options)
// Options for the activitylog service
type Options struct {
Context context.Context
Logger log.Logger
Config *config.Config
TraceProvider trace.TracerProvider
ServiceAccount config.ServiceAccount
Stream events.Stream
RegisteredEvents []events.Unmarshaller
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
Mux *chi.Mux
HistoryClient ehsvc.EventHistoryService
ValueClient settingssvc.ValueService
WriteBufferDuration time.Duration
MaxActivities int
NumConsumers int
}
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Logger configures a logger for the activitylog service
@@ -39,17 +39,10 @@ func Logger(log log.Logger) Option {
}
}
// Config adds the config for the activitylog service
func Config(c *config.Config) Option {
// ServiceAccount configures a service account for the activitylog service
func ServiceAccount(sa config.ServiceAccount) Option {
return func(o *Options) {
o.Config = c
}
}
// TraceProvider adds a tracer provider for the activitylog service
func TraceProvider(tp trace.TracerProvider) Option {
return func(o *Options) {
o.TraceProvider = tp
o.ServiceAccount = sa
}
}
@@ -74,23 +67,8 @@ func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient])
}
}
// Mux defines the muxer for the service
func Mux(m *chi.Mux) Option {
func NumConsumers(num int) Option {
return func(o *Options) {
o.Mux = m
}
}
// HistoryClient adds a grpc client for the eventhistory service
func HistoryClient(hc ehsvc.EventHistoryService) Option {
return func(o *Options) {
o.HistoryClient = hc
}
}
// ValueClient adds a grpc client for the value service
func ValueClient(vs settingssvc.ValueService) Option {
return func(o *Options) {
o.ValueClient = vs
o.NumConsumers = num
}
}
@@ -0,0 +1,302 @@
package events
import (
"context"
"fmt"
"path/filepath"
"sync"
"sync/atomic"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/activitylog"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
var tracer trace.Tracer
func init() {
tracer = otel.Tracer("github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/events")
}
var (
_numConsumersDefault = 1
)
// ActivitylogService logs events per resource
type ActivitylogService struct {
ctx context.Context
sa config.ServiceAccount
log log.Logger
stream events.Stream
gws pool.Selectable[gateway.GatewayAPIClient]
al *activitylog.ActivityLog
numConsumers int
events []events.Unmarshaller
stopCh chan struct{}
stopped *atomic.Bool
}
// New creates a new ActivitylogService
func New(al *activitylog.ActivityLog, stream events.Stream, opts ...Option) (*ActivitylogService, error) {
o := &Options{
NumConsumers: _numConsumersDefault,
}
for _, opt := range opts {
opt(o)
}
s := &ActivitylogService{
ctx: o.Context,
log: o.Logger,
sa: o.ServiceAccount,
stream: stream,
gws: o.GatewaySelector,
events: o.RegisteredEvents,
numConsumers: o.NumConsumers,
al: al,
stopCh: make(chan struct{}, 1),
stopped: new(atomic.Bool),
}
return s, nil
}
// Run to fulfil Runner interface
func (s *ActivitylogService) Run() error {
ch, err := events.Consume(s.stream, "activitylog", s.events...)
if err != nil {
return err
}
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(s.ctx)
defer cancel()
s.log.Debug().Int("worker.count", s.numConsumers).
Str("messaging.consumer.group.name", "activitylog").
Str("messaging.system", "nats").
Str("messaging.operation.name", "receive").
Msg("starting event processing workers")
// start workers
for i := 0; i < s.numConsumers; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case e, ok := <-ch:
if !ok {
return
}
if err := s.processEvent(e); err != nil {
s.log.Error().Err(err).
Int("worker", workerID).
Interface("event", e).
Msg("failed to process event")
}
}
}
}(i)
}
// wait for stop signal
<-s.stopCh
cancel() // signal workers to stop
wg.Wait()
return nil
}
// Close will make the service to stop processing, so the `Run`
// method can finish.
func (s *ActivitylogService) Close() {
if s.stopped.CompareAndSwap(false, true) {
close(s.stopCh)
}
}
func (s *ActivitylogService) processEvent(e events.Event) error {
ctx := e.GetTraceContext(s.ctx)
ctx, span := tracer.Start(ctx, "processEvent")
defer span.End()
s.log.Debug().Interface("event", e).Msg("updating activitylog")
switch ev := e.Event.(type) {
case events.UploadReady:
return s.AddActivity(ctx, ev.FileRef, ev.ParentID, e.ID, utils.TSToTime(ev.Timestamp))
case events.FileTouched:
return s.AddActivity(ctx, ev.Ref, ev.ParentID, e.ID, utils.TSToTime(ev.Timestamp))
// Disabled https://github.com/owncloud/ocis/issues/10293
//case events.FileDownloaded:
// we are only interested in public link downloads - so no need to store others.
//if ev.ImpersonatingUser.GetDisplayName() == "Public" {
// err = a.AddActivity(ev.Ref, e.ID, utils.TSToTime(ev.Timestamp))
//}
case events.ContainerCreated:
return s.AddActivity(ctx, ev.Ref, ev.ParentID, e.ID, utils.TSToTime(ev.Timestamp))
case events.ItemTrashed:
return s.AddActivityTrashed(ctx, ev.ID, ev.Ref, nil, e.ID, utils.TSToTime(ev.Timestamp))
case events.ItemPurged:
return s.al.RemoveResource(ev.ID)
case events.ItemMoved:
// remove the cached parent id for this resource
s.removeCachedParentID(ctx, ev.Ref)
return s.AddActivity(ctx, ev.Ref, nil, e.ID, utils.TSToTime(ev.Timestamp))
case events.ShareCreated:
return s.AddActivity(ctx, toRef(ev.ItemID), nil, e.ID, utils.TSToTime(ev.CTime))
case events.ShareUpdated:
if ev.Sharer != nil && ev.ItemID != nil && ev.Sharer.GetOpaqueId() != ev.ItemID.GetSpaceId() {
return s.AddActivity(ctx, toRef(ev.ItemID), nil, e.ID, utils.TSToTime(ev.MTime))
}
case events.ShareRemoved:
return s.AddActivity(ctx, toRef(ev.ItemID), nil, e.ID, ev.Timestamp)
case events.LinkCreated:
return s.AddActivity(ctx, toRef(ev.ItemID), nil, e.ID, utils.TSToTime(ev.CTime))
case events.LinkUpdated:
if ev.Sharer != nil && ev.ItemID != nil && ev.Sharer.GetOpaqueId() != ev.ItemID.GetSpaceId() {
return s.AddActivity(ctx, toRef(ev.ItemID), nil, e.ID, utils.TSToTime(ev.MTime))
}
case events.LinkRemoved:
return s.AddActivity(ctx, toRef(ev.ItemID), nil, e.ID, utils.TSToTime(ev.Timestamp))
case events.SpaceShared:
return s.AddSpaceActivity(ctx, ev.ID, e.ID, ev.Timestamp)
case events.SpaceUnshared:
return s.AddSpaceActivity(ctx, ev.ID, e.ID, ev.Timestamp)
}
return nil
}
// AddActivity adds the activity to the given resource and all its parents
func (a *ActivitylogService) AddActivity(ctx context.Context, initRef *provider.Reference, parentId *provider.ResourceId, eventID string, timestamp time.Time) error {
ctx, span := tracer.Start(ctx, "AddActivity")
defer span.End()
gwc, err := a.gws.Next()
if err != nil {
return fmt.Errorf("cant get gateway client: %w", err)
}
ctx, err = utils.GetServiceUserContextWithContext(ctx, gwc, a.sa.ServiceAccountID, a.sa.ServiceAccountSecret)
if err != nil {
return fmt.Errorf("cant get service user context: %w", err)
}
return a.al.AddActivity(ctx, initRef, parentId, eventID, timestamp, func(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
return utils.GetResource(ctx, ref, gwc)
})
}
// AddActivityTrashed adds the activity to given trashed resource and all its former parents
func (a *ActivitylogService) AddActivityTrashed(ctx context.Context, resourceID *provider.ResourceId, reference *provider.Reference, parentId *provider.ResourceId, eventID string, timestamp time.Time) error {
ctx, span := tracer.Start(ctx, "AddActivityTrashed")
defer span.End()
gwc, err := a.gws.Next()
if err != nil {
return fmt.Errorf("cant get gateway client: %w", err)
}
ctx, err = utils.GetServiceUserContextWithContext(ctx, gwc, a.sa.ServiceAccountID, a.sa.ServiceAccountSecret)
if err != nil {
return fmt.Errorf("cant get service user context: %w", err)
}
// store activity on trashed item
if err := a.al.StoreActivity(storagespace.FormatResourceID(resourceID), []activitylog.RawActivity{
{
EventID: eventID,
Depth: 0,
Timestamp: timestamp,
},
}); err != nil {
return fmt.Errorf("could not store activity: %w", err)
}
// get previous parent
ref := &provider.Reference{
ResourceId: reference.GetResourceId(),
Path: filepath.Dir(reference.GetPath()),
}
return a.al.AddActivity(ctx, ref, parentId, eventID, timestamp, func(ctx context.Context, ref *provider.Reference) (*provider.ResourceInfo, error) {
return utils.GetResource(ctx, ref, gwc)
})
}
// AddSpaceActivity adds the activity to the given spaceroot
func (a *ActivitylogService) AddSpaceActivity(ctx context.Context, spaceID *provider.StorageSpaceId, eventID string, timestamp time.Time) error {
_, span := tracer.Start(ctx, "AddSpaceActivity")
defer span.End()
// spaceID is in format <providerid>$<spaceid>
// activitylog service uses format <providerid>$<spaceid>!<resourceid>
// lets do some converting, shall we?
rid, err := storagespace.ParseID(spaceID.GetOpaqueId())
if err != nil {
return fmt.Errorf("could not parse space id: %w", err)
}
rid.OpaqueId = rid.GetSpaceId()
err = a.al.StoreActivity(storagespace.FormatResourceID(&rid), []activitylog.RawActivity{
{
EventID: eventID,
Depth: 0,
Timestamp: timestamp,
},
})
if err != nil {
return fmt.Errorf("could not store activity: %w", err)
}
return nil
}
func toRef(r *provider.ResourceId) *provider.Reference {
return &provider.Reference{
ResourceId: r,
}
}
func (a *ActivitylogService) removeCachedParentID(ctx context.Context, ref *provider.Reference) {
var span trace.Span
ctx, span = tracer.Start(ctx, "removeCachedParentID")
defer span.End()
purgeId := ref.GetResourceId()
if ref.GetPath() != "" {
gwc, err := a.gws.Next()
if err != nil {
a.log.Error().Err(err).Msg("could not get gateway client")
return
}
ctx, err = utils.GetServiceUserContextWithContext(ctx, gwc, a.sa.ServiceAccountID, a.sa.ServiceAccountSecret)
if err != nil {
a.log.Error().Err(err).Msg("could not get service user context")
return
}
info, err := utils.GetResource(ctx, ref, gwc)
if err != nil {
a.log.Error().Err(err).Msg("could not get resource info")
return
}
purgeId = info.GetId()
}
a.al.InvalidateCachedParentID(purgeId)
}
@@ -0,0 +1,123 @@
package events_test
import (
"context"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/activitylog"
eventssvc "github.com/opencloud-eu/opencloud/services/activitylog/pkg/service/events"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
)
var _ = Describe("ActivitylogService", func() {
Describe("New", func() {
var (
al *activitylog.ActivityLog
stream events.Stream
)
BeforeEach(func() {
al = &activitylog.ActivityLog{}
stream = nil
})
It("creates a service with minimal options", func() {
svc, err := eventssvc.New(al, stream)
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
It("creates a service with context option", func() {
ctx := context.Background()
svc, err := eventssvc.New(al, stream, eventssvc.Context(ctx))
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
It("creates a service with logger option", func() {
logger := log.NopLogger()
svc, err := eventssvc.New(al, stream, eventssvc.Logger(logger))
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
It("creates a service with service account option", func() {
sa := config.ServiceAccount{
ServiceAccountID: "sa-id",
ServiceAccountSecret: "sa-secret",
}
svc, err := eventssvc.New(al, stream, eventssvc.ServiceAccount(sa))
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
It("creates a service with registered events", func() {
evts := []events.Unmarshaller{&events.UploadReady{}, &events.FileTouched{}}
svc, err := eventssvc.New(al, stream, eventssvc.RegisteredEvents(evts))
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
It("creates a service with gateway selector", func() {
var gs pool.Selectable[gateway.GatewayAPIClient]
svc, err := eventssvc.New(al, stream, eventssvc.GatewaySelector(gs))
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
It("creates a service with num consumers option", func() {
svc, err := eventssvc.New(al, stream, eventssvc.NumConsumers(5))
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
It("creates a service with all options", func() {
ctx := context.Background()
logger := log.NopLogger()
sa := config.ServiceAccount{
ServiceAccountID: "sa-id",
ServiceAccountSecret: "sa-secret",
}
evts := []events.Unmarshaller{&events.UploadReady{}, &events.ContainerCreated{}}
var gs pool.Selectable[gateway.GatewayAPIClient]
svc, err := eventssvc.New(
al,
stream,
eventssvc.Context(ctx),
eventssvc.Logger(logger),
eventssvc.ServiceAccount(sa),
eventssvc.RegisteredEvents(evts),
eventssvc.GatewaySelector(gs),
eventssvc.NumConsumers(3),
)
Expect(err).ToNot(HaveOccurred())
Expect(svc).ToNot(BeNil())
})
})
Describe("Close", func() {
It("can be called without panic on a new service", func() {
al := &activitylog.ActivityLog{}
svc, err := eventssvc.New(al, nil)
Expect(err).ToNot(HaveOccurred())
Expect(func() { svc.Close() }).ToNot(Panic())
})
It("can be called multiple times without panic", func() {
al := &activitylog.ActivityLog{}
svc, err := eventssvc.New(al, nil)
Expect(err).ToNot(HaveOccurred())
svc.Close()
svc.Close()
svc.Close()
})
})
})
@@ -0,0 +1,13 @@
package http_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestHTTP(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "HTTP Suite")
}
@@ -0,0 +1,59 @@
package http
import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/log"
ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
RegisteredEvents []events.Unmarshaller
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
HistoryClient ehsvc.EventHistoryService
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// RegisteredEvents registers the events the service should listen to
func RegisteredEvents(e []events.Unmarshaller) Option {
return func(o *Options) {
o.RegisteredEvents = e
}
}
// GatewaySelector adds a grpc client selector for the gateway service
func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option {
return func(o *Options) {
o.GatewaySelector = gatewaySelector
}
}
// HistoryClient adds a grpc client for the eventhistory service
func HistoryClient(hc ehsvc.EventHistoryService) Option {
return func(o *Options) {
o.HistoryClient = hc
}
}
@@ -1,4 +1,4 @@
package service
package http
import (
"context"
@@ -44,11 +44,6 @@ var (
StrDescription = l10n.Template("description")
)
// GetActivitiesResponse is the response on GET activities requests
type GetActivitiesResponse struct {
Activities []libregraph.Activity `json:"value"`
}
// Resource represents an item such as a file or folder
type Resource struct {
ID string `json:"id"`
@@ -311,7 +306,7 @@ func NewActivity(message string, ts time.Time, eventID string, vars map[string]a
}
// GetVars calls other service to gather the required data for the activity variables
func (s *ActivitylogService) GetVars(ctx context.Context, opts ...ActivityOption) (map[string]any, error) {
func (s *svc) GetVars(ctx context.Context, opts ...ActivityOption) (map[string]any, error) {
gwc, err := s.gws.Next()
if err != nil {
return nil, err
@@ -327,6 +322,12 @@ func (s *ActivitylogService) GetVars(ctx context.Context, opts ...ActivityOption
return vars, nil
}
func toSpace(r *provider.Reference) *provider.StorageSpaceId {
return &provider.StorageSpaceId{
OpaqueId: storagespace.FormatStorageID(r.GetResourceId().GetStorageId(), r.GetResourceId().GetSpaceId()),
}
}
func getFolderName(ctx context.Context, gwc gateway.GatewayAPIClient, ref *provider.Reference) string {
n := filepath.Base(filepath.Dir(ref.GetPath()))
if n == "." || n == "/" {
Loaded 100 of 1540 files, more files were not shown because too many files have changed in this diff. Show more