Compare commits

...
95 Commits
Author SHA1 Message Date
Michael Barz 83360f9c89 Merge pull request #2820 from opencloud-eu/next-release/main
🎉 Release 7.1.0
2026-06-02 12:12:02 +02:00
oauth b3b8461ea9 🎉 Release 7.1.0 2026-06-02 10:01:41 +00:00
oauth 4ba5af3ed8 Merge branch 'origin/main' into 'next-release/main' 2026-06-02 10:01:40 +00:00
Christian Richter 6e021e5d3f bump reva
Signed-off-by: Christian Richter <c.richter@opencloud.eu>
2026-06-02 11:59:12 +02:00
oauth 315256699a 🎉 Release 7.1.0 2026-06-01 18:43:27 +00:00
oauth f9fdff0705 Merge branch 'origin/main' into 'next-release/main' 2026-06-01 18:43:26 +00:00
Michael Barz 95423b6002 Merge pull request #2874 from opencloud-eu/bump-version
chore: bump minor version
2026-06-01 20:41:02 +02:00
Michael Barz 1b8393e9d3 chore: bump minor version 2026-06-01 17:06:47 +02:00
oauth 0cd1efabf8 🎉 Release 7.1.0 2026-06-01 14:53:20 +00:00
oauth b6223069df 🎉 Release 7.1.0 2026-06-01 13:25:32 +00:00
oauth bd931bd69c Merge branch 'origin/main' into 'next-release/main' 2026-06-01 13:25:31 +00:00
Michael Barz 41d0365903 Merge pull request #2870 from opencloud-eu/bump-web-7.1.0
chore: bump web to v7.1.0
2026-06-01 15:20:32 +02:00
Benedikt Kulmann aa2f5d9667 chore: bump web to v7.1.0 2026-06-01 14:18:16 +02:00
Ralf Haferkamp 60fc81c222 Merge pull request #2869 from rhafer/revabump
chore: bump reva to 2.46.1
2026-06-01 11:15:01 +02:00
oauth 31bb970de9 🎉 Release 7.0.1 2026-06-01 08:41:24 +00:00
oauth 5cf62ff817 Merge branch 'origin/main' into 'next-release/main' 2026-06-01 08:41:23 +00:00
Ralf Haferkamp 085f9181aa Merge pull request #2855 from opencloud-eu/dependabot/go_modules/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc-1.44.0
build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.43.0 to 1.44.0
2026-06-01 10:39:04 +02:00
Ralf Haferkamp 31e9aaa522 chore: bump reva to 2.46.1 2026-06-01 10:36:24 +02:00
oauth 36c496c230 🎉 Release 7.0.1 2026-06-01 07:33:58 +00:00
oauth b76f10950b Merge branch 'origin/main' into 'next-release/main' 2026-06-01 07:33:57 +00:00
Ralf Haferkamp f75a27e79c Merge pull request #2854 from opencloud-eu/dependabot/npm_and_yarn/services/idp/license-checker-rseidelsohn-5.0.1
build(deps-dev): bump license-checker-rseidelsohn from 4.4.2 to 5.0.1 in /services/idp
2026-06-01 09:31:36 +02:00
Ralf Haferkamp 8bf2100c01 Merge pull request #2853 from opencloud-eu/dependabot/npm_and_yarn/services/idp/cldr-8.0.0
build(deps-dev): bump cldr from 7.9.0 to 8.0.0 in /services/idp
2026-06-01 09:30:06 +02:00
dependabot[bot] 1f6cbaf39e 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.43.0 to 1.44.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.43.0...v1.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 16:54:34 +00:00
dependabot[bot] fbc08a5ccf build(deps-dev): bump license-checker-rseidelsohn in /services/idp
Bumps [license-checker-rseidelsohn](https://github.com/RSeidelsohn/license-checker-rseidelsohn) from 4.4.2 to 5.0.1.
- [Release notes](https://github.com/RSeidelsohn/license-checker-rseidelsohn/releases)
- [Changelog](https://github.com/RSeidelsohn/license-checker-rseidelsohn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RSeidelsohn/license-checker-rseidelsohn/compare/v4.4.2...v5.0.1)

---
updated-dependencies:
- dependency-name: license-checker-rseidelsohn
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 16:54:21 +00:00
dependabot[bot] c3dc7b370f build(deps-dev): bump cldr from 7.9.0 to 8.0.0 in /services/idp
Bumps [cldr](https://github.com/papandreou/node-cldr) from 7.9.0 to 8.0.0.
- [Changelog](https://github.com/papandreou/node-cldr/blob/master/CHANGELOG.md)
- [Commits](https://github.com/papandreou/node-cldr/compare/v7.9.0...v8.0.0)

---
updated-dependencies:
- dependency-name: cldr
  dependency-version: 8.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 16:54:04 +00:00
oauth 6bd81b7302 🎉 Release 7.0.1 2026-05-28 15:04:47 +00:00
oauth e381df016e Merge branch 'origin/main' into 'next-release/main' 2026-05-28 15:04:46 +00:00
Ralf Haferkamp 31340e30fb Merge pull request #2851 from rhafer/issue/2844
fix: Send SSE events for SpaceCreated/-Disabled/-Deleted
2026-05-28 17:02:03 +02:00
oauth c9fb429b1a 🎉 Release 7.0.1 2026-05-28 13:33:56 +00:00
oauth d561ab1ebc Merge branch 'origin/main' into 'next-release/main' 2026-05-28 13:33:56 +00:00
Ralf Haferkamp 11b6d7793d Merge pull request #2849 from opencloud-eu/dependabot/npm_and_yarn/services/idp/i18next-26.3.0
build(deps): bump i18next from 26.1.0 to 26.3.0 in /services/idp
2026-05-28 15:31:32 +02:00
oauth 9d36da169b 🎉 Release 7.0.1 2026-05-28 13:00:26 +00:00
oauth 6ca9a3e1c4 Merge branch 'origin/main' into 'next-release/main' 2026-05-28 13:00:26 +00:00
Ralf Haferkamp 3e875017a6 Merge pull request #2845 from opencloud-eu/dependabot/npm_and_yarn/services/idp/sass-loader-17.0.0
build(deps-dev): bump sass-loader from 16.0.8 to 17.0.0 in /services/idp
2026-05-28 14:58:00 +02:00
Ralf Haferkamp 69b9031b2d fix: Send SSE events for SpaceCreated/-Disabled/-Deleted
Closes: #2844
2026-05-28 14:34:17 +02:00
oauth 74bf60cbd9 🎉 Release 7.0.1 2026-05-28 08:50:54 +00:00
oauth d9c775d250 Merge branch 'origin/main' into 'next-release/main' 2026-05-28 08:50:53 +00:00
Ralf Haferkamp 5dba8ecc4e Merge pull request #2848 from opencloud-eu/dependabot/go_modules/google.golang.org/grpc-1.81.1
build(deps): bump google.golang.org/grpc from 1.80.0 to 1.81.1
2026-05-28 10:48:21 +02:00
dependabot[bot] 552e5dd289 build(deps-dev): bump sass-loader from 16.0.8 to 17.0.0 in /services/idp
Bumps [sass-loader](https://github.com/webpack/sass-loader) from 16.0.8 to 17.0.0.
- [Release notes](https://github.com/webpack/sass-loader/releases)
- [Changelog](https://github.com/webpack/sass-loader/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/sass-loader/compare/v16.0.8...v17.0.0)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-version: 17.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 08:47:57 +00:00
dependabot[bot] 5ad4ea4413 build(deps): bump i18next from 26.1.0 to 26.3.0 in /services/idp
Bumps [i18next](https://github.com/i18next/i18next) from 26.1.0 to 26.3.0.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.1.0...v26.3.0)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 08:47:53 +00:00
Ralf Haferkamp a023172421 Merge pull request #2847 from opencloud-eu/dependabot/go_modules/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc-0.69.0
build(deps): bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc from 0.68.0 to 0.69.0
2026-05-28 10:47:40 +02:00
oauth aac062c9e2 🎉 Release 7.0.1 2026-05-28 08:47:24 +00:00
oauth d7f287c813 Merge branch 'origin/main' into 'next-release/main' 2026-05-28 08:47:24 +00:00
Ralf Haferkamp 52b05be436 Merge pull request #2846 from opencloud-eu/dependabot/npm_and_yarn/services/idp/typescript-6.0.3
build(deps-dev): bump typescript from 5.9.3 to 6.0.3 in /services/idp
2026-05-28 10:45:06 +02:00
oauth a609940cb8 🎉 Release 7.0.1 2026-05-28 07:36:49 +00:00
oauth 3c9af15d54 Merge branch 'origin/main' into 'next-release/main' 2026-05-28 07:36:49 +00:00
dependabot[bot] a5ead45c87 build(deps): bump google.golang.org/grpc from 1.80.0 to 1.81.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.80.0 to 1.81.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.80.0...v1.81.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 07:36:24 +00:00
dependabot[bot] 96478695cf build(deps): bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
Bumps [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.68.0 to 0.69.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.68.0...zpages/v0.69.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-version: 0.69.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 07:36:14 +00:00
dependabot[bot] 44cef0ae65 build(deps-dev): bump typescript from 5.9.3 to 6.0.3 in /services/idp
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 07:36:14 +00:00
Ralf Haferkamp 69989fea49 Merge pull request #2840 from rhafer/fix-dependabot
chore: fix dependabot config
2026-05-28 09:34:21 +02:00
Ralf Haferkamp b7cddc215d chore: fix dependabot config
It's `cooldown.default-days` not `cooldown.default`.
2026-05-27 16:59:31 +02:00
oauth f86da12a5e 🎉 Release 7.0.1 2026-05-27 14:10:25 +00:00
oauth 0ebbe33d3a Merge branch 'origin/main' into 'next-release/main' 2026-05-27 14:10:25 +00:00
Ralf Haferkamp 49726b8af8 Merge pull request #2834 from aduffeck/fix-space-search
Only try to limit search to spaces if there's a space id to limit to
2026-05-27 16:07:55 +02:00
oauth 6287c9e885 🎉 Release 7.0.1 2026-05-27 13:43:25 +00:00
oauth 217b49a3d1 Merge branch 'origin/main' into 'next-release/main' 2026-05-27 13:43:24 +00:00
Ralf Haferkamp 4cf1331dc9 Merge pull request #2839 from rhafer/issue/1022
fix(init): Only log admin password if it was generated
2026-05-27 15:40:57 +02:00
oauth 370a685482 🎉 Release 7.0.1 2026-05-27 13:34:12 +00:00
oauth 19333cb99a Merge branch 'origin/main' into 'next-release/main' 2026-05-27 13:34:12 +00:00
Ralf Haferkamp bf397732e7 docs(adr): Remove erroneous mention of kanidm
Apparently kanidm does allow to set the client-id.
2026-05-27 15:31:48 +02:00
Ralf Haferkamp da0b9417cd fix(init): Only log admin password if it was generated
This fixes `opencloud init` to only print the admin password if it was
generated by `init`. No longer log is when is was set via env var or command
line arguments.

Additionally add a `--quiet` command line flag to disable all non error
output.

Fixes: #1022
2026-05-27 13:22:29 +02:00
oauth 1162e26b16 🎉 Release 7.0.1 2026-05-27 10:15:34 +00:00
oauth a28998e536 Merge branch 'origin/main' into 'next-release/main' 2026-05-27 10:15:33 +00:00
Ralf Haferkamp 81572a0a24 Merge pull request #2838 from rhafer/bump-js
chore: bump js dependencies in idp
2026-05-27 12:13:17 +02:00
Ralf Haferkamp 5efe80577f Merge pull request #2836 from rhafer/issue/2833
fix: translations for activities and others
2026-05-27 12:12:50 +02:00
v.scharf c445f1fc93 adjust tets failures file 2026-05-27 11:56:52 +02:00
Ralf Haferkamp 70c0f4388b chore: bump js dependencies in idp 2026-05-27 11:46:34 +02:00
Ralf Haferkamp 719bb2123c fix: translations for activities and others
This fixes the server-side translations for "activties" and e.g. the
default space description. We need to bump "leonelquinteros/gotext"
to latest master for that as even the latest release still contains
and issue that cause `go vet` to complain about non-constant
format strings.

Fixes: #2833, #2835
2026-05-27 11:22:26 +02:00
Ralf Haferkamp c98cca36c6 Merge pull request #2823 from aduffeck/fix-build
Fix build by adding the missing protoc-gen-go-grpc dependency
2026-05-27 11:19:21 +02:00
oauth caa182cce3 🎉 Release 7.0.1 2026-05-27 08:24:49 +00:00
oauth 0c0b6d7403 Merge branch 'origin/main' into 'next-release/main' 2026-05-27 08:24:49 +00:00
Ralf Haferkamp bceaf4811f Merge pull request #2830 from opencloud-eu/dependabot/npm_and_yarn/services/idp/postcss-loader-8.2.1
build(deps-dev): bump postcss-loader from 4.3.0 to 8.2.1 in /services/idp
2026-05-27 10:22:41 +02:00
André Duffeck 5b9ec396c4 Dynamically adapt the URL prefix depending on the request 2026-05-27 10:04:01 +02:00
oauth b5e8c1a268 🎉 Release 7.0.1 2026-05-27 07:00:24 +00:00
oauth a69786e933 Merge branch 'origin/main' into 'next-release/main' 2026-05-27 07:00:23 +00:00
Ralf Haferkamp 613288e02a Merge pull request #2814 from opencloud-eu/dependabot/go_modules/github.com/riandyrn/otelchi-0.12.3
build(deps): bump github.com/riandyrn/otelchi from 0.12.2 to 0.12.3
2026-05-27 08:56:31 +02:00
v.scharf 071b09c504 remove tests from expected failures file 2026-05-26 15:15:04 +02:00
André Duffeck 6bb2324233 Only try to limit search to spaces if there's a space id to limit to 2026-05-26 13:42:57 +02:00
oauth e8d5249c93 🎉 Release 7.0.1 2026-05-26 11:39:51 +00:00
oauth ca347ad0e0 Merge branch 'origin/main' into 'next-release/main' 2026-05-26 11:39:51 +00:00
Viktor Scharf cff56490cb fix-2824. run tests without remote.php (#2826) 2026-05-26 13:35:49 +02:00
dependabot[bot] 68c0dceb49 build(deps-dev): bump postcss-loader in /services/idp
Bumps [postcss-loader](https://github.com/webpack/postcss-loader) from 4.3.0 to 8.2.1.
- [Release notes](https://github.com/webpack/postcss-loader/releases)
- [Changelog](https://github.com/webpack/postcss-loader/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/postcss-loader/compare/v4.3.0...v8.2.1)

---
updated-dependencies:
- dependency-name: postcss-loader
  dependency-version: 8.2.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 20:54:43 +00:00
André DuffeckandItsGamerik 6c5e486924 Fix build by adding the missing protoc-gen-go-grpc dependency
Co-authored-by: ItsGamerik
2026-05-22 11:51:03 +02:00
Andre Duffeck 29f0387323 Merge pull request #2813 from aduffeck/parentid-consistency
Verify parent ids during the consistency check
2026-05-22 11:18:53 +02:00
André Duffeck c336e4ae34 Bump reva 2026-05-22 10:37:47 +02:00
André Duffeck fbc868522d Verify parent ids during the consistency check
Fixes #2372
2026-05-22 10:37:20 +02:00
oauth 882d20cc7f 🎉 Release 7.0.1 2026-05-22 08:33:11 +00:00
Andre Duffeck b189fadbff Merge pull request #2789 from aduffeck/posixfs-scan-command
Add a "posixfs scan" command for scanning the posix tree for new files
2026-05-22 10:29:40 +02:00
dependabot[bot] 97d541d956 build(deps): bump github.com/riandyrn/otelchi from 0.12.2 to 0.12.3
Bumps [github.com/riandyrn/otelchi](https://github.com/riandyrn/otelchi) from 0.12.2 to 0.12.3.
- [Release notes](https://github.com/riandyrn/otelchi/releases)
- [Changelog](https://github.com/riandyrn/otelchi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/riandyrn/otelchi/compare/v0.12.2...v0.12.3)

---
updated-dependencies:
- dependency-name: github.com/riandyrn/otelchi
  dependency-version: 0.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 16:18:29 +00:00
Ralf Haferkamp bdff962639 Merge pull request #2781 from opencloud-eu/dependabot/npm_and_yarn/services/idp/workbox-webpack-plugin-7.4.1
build(deps-dev): bump workbox-webpack-plugin from 7.4.0 to 7.4.1 in /services/idp
2026-05-21 15:52:10 +02:00
Ralf Haferkamp 3cfc1609ae Merge pull request #2810 from rhafer/bump-pnpm
chore(idp): bump to pnpm-11 and migrate config
2026-05-21 15:51:51 +02:00
Ralf Haferkamp 39d8979360 chore: dependabot changes
- allow it to open up to 4 PRs in parallel
- change the npm interval to daily
- set cooldown to 1 day to reflect the `minimumReleaseAge` default
  of pnpm
2026-05-21 14:08:02 +02:00
Ralf Haferkamp 075bddd1cb chore(idp): bump to pnpm-11 and migrate config 2026-05-21 12:32:00 +02:00
dependabot[bot] 17c27372fe build(deps-dev): bump workbox-webpack-plugin in /services/idp
Bumps [workbox-webpack-plugin](https://github.com/googlechrome/workbox) from 7.4.0 to 7.4.1.
- [Release notes](https://github.com/googlechrome/workbox/releases)
- [Commits](https://github.com/googlechrome/workbox/compare/v7.4.0...v7.4.1)

---
updated-dependencies:
- dependency-name: workbox-webpack-plugin
  dependency-version: 7.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 10:21:23 +00:00
André Duffeck 9de4e24395 Add a "posixfs scan" command for scanning the posix tree for new files 2026-05-21 11:18:24 +02:00
273 changed files with 35384 additions and 29102 deletions

No files matched your search

+3 -3
View File
@@ -83,11 +83,11 @@ $(PIGEON): $(BINGO_DIR)/pigeon.mod
@echo "(re)installing $(GOBIN)/pigeon-v1.3.0"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=pigeon.mod -o=$(GOBIN)/pigeon-v1.3.0 "github.com/mna/pigeon"
PROTOC_GEN_GO_GRPC := $(GOBIN)/protoc-gen-go-grpc-v1.81.0
PROTOC_GEN_GO_GRPC := $(GOBIN)/protoc-gen-go-grpc-v1.6.2
$(PROTOC_GEN_GO_GRPC): $(BINGO_DIR)/protoc-gen-go-grpc.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)/protoc-gen-go-grpc-v1.81.0"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=protoc-gen-go-grpc.mod -o=$(GOBIN)/protoc-gen-go-grpc-v1.81.0 "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
@echo "(re)installing $(GOBIN)/protoc-gen-go-grpc-v1.6.2"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=protoc-gen-go-grpc.mod -o=$(GOBIN)/protoc-gen-go-grpc-v1.6.2 "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
PROTOC_GEN_GO := $(GOBIN)/protoc-gen-go-v1.28.1
$(PROTOC_GEN_GO): $(BINGO_DIR)/protoc-gen-go.mod
+1 -1
View File
@@ -30,7 +30,7 @@ MUTAGEN="${GOBIN}/mutagen-v0.18.1"
PIGEON="${GOBIN}/pigeon-v1.3.0"
PROTOC_GEN_GO_GRPC="${GOBIN}/protoc-gen-go-grpc-v1.81.0"
PROTOC_GEN_GO_GRPC="${GOBIN}/protoc-gen-go-grpc-v1.6.2"
PROTOC_GEN_GO="${GOBIN}/protoc-gen-go-v1.28.1"
+5 -4
View File
@@ -4,11 +4,12 @@ updates:
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 2
open-pull-requests-limit: 4
- package-ecosystem: "npm"
directory: "/services/idp"
schedule:
interval: "weekly"
open-pull-requests-limit: 2
interval: "daily"
open-pull-requests-limit: 4
cooldown:
default-days: 1
+1
View File
@@ -11,6 +11,7 @@ protoc-deps: $(BINGO)
@cd ../.. && GOPATH="" GOBIN=".bingo" $(BINGO) get -l github.com/owncloud/protoc-gen-microweb
@cd ../.. && GOPATH="" GOBIN=".bingo" $(BINGO) get -l github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
@cd ../.. && GOPATH="" GOBIN=".bingo" $(BINGO) get -l github.com/favadi/protoc-go-inject-tag
@cd ../.. && GOPATH="" GOBIN=".bingo" $(BINGO) get -l google.golang.org/grpc/cmd/protoc-gen-go-grpc
.PHONY: buf-generate
buf-generate: $(SHA1_LOCK_FILE)
+1 -1
View File
@@ -1,4 +1,4 @@
# The test runner source for UI tests
WEB_COMMITID=9de6541fc8bb116fcc78b09da697563faa5e61bd
WEB_COMMITID=2cee98bb27fab223fc4ce2455f5483ce7767ff1a
WEB_BRANCH=main
+14 -14
View File
@@ -109,10 +109,10 @@ config = {
},
"basic2": {
"suites": [
"apiDownloads",
"apiDepthInfinity",
"apiLocks",
"apiActivities",
"apiArchiver",
"apiContract",
"apiCors",
"apiAsyncUpload",
],
"skip": False,
},
@@ -121,7 +121,7 @@ config = {
"apiSettings",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
"emailNeeded": True,
"extraTestEnvironment": {
"EMAIL_HOST": "email",
@@ -144,14 +144,14 @@ config = {
#"collaborativePosix",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
},
"graphUserGroup": {
"suites": [
"apiGraphUserGroup",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
},
"spaces": {
"suites": [
@@ -209,7 +209,7 @@ config = {
"apiNotification",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
"emailNeeded": True,
"extraTestEnvironment": {
"EMAIL_HOST": "email",
@@ -251,7 +251,7 @@ config = {
"apiOcm",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
"federationServer": True,
"emailNeeded": True,
"extraTestEnvironment": {
@@ -287,14 +287,14 @@ config = {
"apiAuthApp",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
},
"cliCommands": {
"suites": [
"cliCommands",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
"antivirusNeeded": True,
"extraServerEnvironment": {
"ANTIVIRUS_SCANNER_TYPE": "clamav",
@@ -309,7 +309,7 @@ config = {
"apiTenancy",
],
"skip": False,
"withRemotePhp": [True],
"withRemotePhp": [False],
"ldapNeeded": True,
"extraTestEnvironment": {
"USE_PREPARED_LDAP_USERS": True,
@@ -1199,7 +1199,7 @@ def localApiTestPipeline(ctx):
"federationServer": False,
"collaborationServiceNeeded": False,
"extraCollaborationEnvironment": {},
"withRemotePhp": [True],
"withRemotePhp": [False],
"enableWatchFs": [False],
"ldapNeeded": False,
}
@@ -1316,7 +1316,7 @@ def localApiTest(suites, storage = "decomposed", extra_environment = {}, with_re
def coreApiTestPipeline(ctx):
defaults = {
"withRemotePhp": [True],
"withRemotePhp": [False],
"enableWatchFs": [False],
"storages": ["posix"],
"numberOfParts": 7,
+38
View File
@@ -1,5 +1,43 @@
# Changelog
## [7.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.1.0) - 2026-06-02
### ❤️ Thanks to all contributors! ❤️
@ScharfViktor, @aduffeck, @dragonchaser, @kulmann, @micbar, @rhafer
### 🐛 Bug Fixes
- Prevent personal space creation for service- and lightweight users [[#2876](https://github.com/opencloud-eu/opencloud/pull/2876)]
- chore: bump reva to 2.46.1 [[#2869](https://github.com/opencloud-eu/opencloud/pull/2869)]
- fix: Send SSE events for SpaceCreated/-Disabled/-Deleted [[#2851](https://github.com/opencloud-eu/opencloud/pull/2851)]
- Only try to limit search to spaces if there's a space id to limit to [[#2834](https://github.com/opencloud-eu/opencloud/pull/2834)]
- fix(init): Only log admin password if it was generated [[#2839](https://github.com/opencloud-eu/opencloud/pull/2839)]
- fix: translations for activities and others [[#2836](https://github.com/opencloud-eu/opencloud/pull/2836)]
- fix-2824. run tests without remote.php [[#2826](https://github.com/opencloud-eu/opencloud/pull/2826)]
### 📈 Enhancement
- chore: bump web to v7.1.0 [[#2870](https://github.com/opencloud-eu/opencloud/pull/2870)]
### 📚 Documentation
- docs(adr): Remove erroneous mention of kanidm [[#2783](https://github.com/opencloud-eu/opencloud/pull/2783)]
### 📦️ Dependencies
- build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.43.0 to 1.44.0 [[#2855](https://github.com/opencloud-eu/opencloud/pull/2855)]
- build(deps-dev): bump license-checker-rseidelsohn from 4.4.2 to 5.0.1 in /services/idp [[#2854](https://github.com/opencloud-eu/opencloud/pull/2854)]
- build(deps-dev): bump cldr from 7.9.0 to 8.0.0 in /services/idp [[#2853](https://github.com/opencloud-eu/opencloud/pull/2853)]
- build(deps): bump i18next from 26.1.0 to 26.3.0 in /services/idp [[#2849](https://github.com/opencloud-eu/opencloud/pull/2849)]
- build(deps-dev): bump sass-loader from 16.0.8 to 17.0.0 in /services/idp [[#2845](https://github.com/opencloud-eu/opencloud/pull/2845)]
- build(deps): bump google.golang.org/grpc from 1.80.0 to 1.81.1 [[#2848](https://github.com/opencloud-eu/opencloud/pull/2848)]
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc from 0.68.0 to 0.69.0 [[#2847](https://github.com/opencloud-eu/opencloud/pull/2847)]
- build(deps-dev): bump typescript from 5.9.3 to 6.0.3 in /services/idp [[#2846](https://github.com/opencloud-eu/opencloud/pull/2846)]
- build(deps-dev): bump postcss-loader from 4.3.0 to 8.2.1 in /services/idp [[#2830](https://github.com/opencloud-eu/opencloud/pull/2830)]
- build(deps): bump github.com/riandyrn/otelchi from 0.12.2 to 0.12.3 [[#2814](https://github.com/opencloud-eu/opencloud/pull/2814)]
- build(deps-dev): bump workbox-webpack-plugin from 7.4.0 to 7.4.1 in /services/idp [[#2781](https://github.com/opencloud-eu/opencloud/pull/2781)]
## [7.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.0.0) - 2026-05-21
### ❤️ Thanks to all contributors! ❤️
@@ -18,7 +18,7 @@ OpenCloud with various existing identity providers. For example:
- Authentik basically creates a different issuer URL for each client. As OpenCloud
can only work with a single issuer URL, all OpenCloud clients need to use the
same client id to work with Authentik.
- Some IDPs (kanidm) are not able to work with user-supplied client ids. They generate
- Some IDPs are not able to work with user-supplied client ids. They generate
client ids automatically and do not allow to specify them manually.
- To make features like automatic role assignment work, clients need to request
specific scopes, depending on which exact IDP is used.
+18 -18
View File
@@ -42,14 +42,14 @@ require (
github.com/google/uuid v1.6.0
github.com/gookit/config/v2 v2.2.7
github.com/gorilla/mux v1.8.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0
github.com/invopop/validation v0.8.0
github.com/jellydator/ttlcache/v2 v2.11.1
github.com/jellydator/ttlcache/v3 v3.4.0
github.com/jinzhu/now v1.1.5
github.com/justinas/alice v1.2.0
github.com/kovidgoyal/imaging v1.8.20
github.com/leonelquinteros/gotext v1.7.2
github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69
github.com/libregraph/idm v0.5.0
github.com/libregraph/lico v0.66.0
github.com/mitchellh/mapstructure v1.5.0
@@ -64,14 +64,14 @@ require (
github.com/open-policy-agent/opa v1.15.2
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.46.0
github.com/opencloud-eu/reva/v2 v2.46.2-0.20260602061404-0e975e5456eb
github.com/opensearch-project/opensearch-go/v4 v4.6.0
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.23.2
github.com/r3labs/sse/v2 v2.10.0
github.com/riandyrn/otelchi v0.12.2
github.com/riandyrn/otelchi v0.12.3
github.com/rogpeppe/go-internal v1.14.1
github.com/rs/cors v1.11.1
github.com/rs/zerolog v1.35.0
@@ -94,24 +94,24 @@ require (
github.com/xhit/go-simple-mail/v2 v2.16.0
go-micro.dev/v4 v4.11.0
go.etcd.io/bbolt v1.4.3
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
go.opentelemetry.io/contrib/zpages v0.68.0
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.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.43.0
go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/trace v1.43.0
golang.org/x/crypto v0.50.0
go.opentelemetry.io/otel/sdk v1.44.0
go.opentelemetry.io/otel/trace v1.44.0
golang.org/x/crypto v0.51.0
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/image v0.40.0
golang.org/x/net v0.53.0
golang.org/x/net v0.55.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0
golang.org/x/term v0.42.0
golang.org/x/term v0.43.0
golang.org/x/text v0.37.0
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9
google.golang.org/grpc v1.80.0
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa
google.golang.org/grpc v1.81.1
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
@@ -382,19 +382,19 @@ require (
go.etcd.io/etcd/client/v3 v3.6.11 // 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.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // 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.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+38 -38
View File
@@ -613,8 +613,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.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
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/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=
@@ -756,8 +756,8 @@ github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvf
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69 h1:ZLo0bXX17iUZpb/Sp7N+1uK4Jb7dl0fm5x5ETfzYSvY=
github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69/go.mod h1:ksG5iXViKefoupjy+0qQjAVoaDnylnQ1ejWl9g14wh8=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
@@ -952,8 +952,8 @@ github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIft
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
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.46.0 h1:wYUHIHOsLP9vad/K19/52RRfoooeHUGY4BSeowNrbpY=
github.com/opencloud-eu/reva/v2 v2.46.0/go.mod h1:fWAzVpZlJQEY/qeIrd9d3U+LpqY9JGsjJ2dc0a1jCEs=
github.com/opencloud-eu/reva/v2 v2.46.2-0.20260602061404-0e975e5456eb h1:7LXIqaeVQqOghfTw44kyLSak+cR0TIBl55M4+Tb08f8=
github.com/opencloud-eu/reva/v2 v2.46.2-0.20260602061404-0e975e5456eb/go.mod h1:V1bwCQXM1pGmswtjdSJVIweA9Vv5EmtUZr/7Cm5086A=
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=
@@ -1065,8 +1065,8 @@ github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKc
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/riandyrn/otelchi v0.12.2 h1:6QhGv0LVw/dwjtPd12mnNrl0oEQF4ZAlmHcnlTYbeAg=
github.com/riandyrn/otelchi v0.12.2/go.mod h1:weZZeUJURvtCcbWsdb7Y6F8KFZGedJlSrgUjq9VirV8=
github.com/riandyrn/otelchi v0.12.3 h1:KW9gA+97d6mExk8vbh0FRwb2biUvpyYlc8YuxP1Oap0=
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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -1300,28 +1300,28 @@ 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.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
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.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/contrib/zpages v0.68.0 h1:H5yrUwxPrbvhzdBxjQD+VXMtPjIBfp8NWNVvQT8E30M=
go.opentelemetry.io/contrib/zpages v0.68.0/go.mod h1:sZGctYYO4UOHItj9bx3F+t/s+u1Fv8CHCJ5s2eR2cjU=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
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.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
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.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -1362,8 +1362,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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
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=
@@ -1453,8 +1453,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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
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=
@@ -1560,8 +1560,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1570,8 +1570,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1711,10 +1711,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-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
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-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/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=
@@ -1730,8 +1730,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.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
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=
+3 -1
View File
@@ -35,9 +35,10 @@ func InitCommand(_ *config.Config) *cobra.Command {
}
forceOverwriteFlag := viper.GetBool("force-overwrite")
diffFlag, _ := cmd.Flags().GetBool("diff")
quietFlag, _ := cmd.Flags().GetBool("quiet")
configPathFlag := viper.GetString("config-path")
adminPasswordFlag := viper.GetString("admin-password")
err := ocinit.CreateConfig(insecure, forceOverwriteFlag, diffFlag, configPathFlag, adminPasswordFlag)
err := ocinit.CreateConfig(insecure, forceOverwriteFlag, diffFlag, configPathFlag, adminPasswordFlag, quietFlag)
if err != nil {
log.Fatalf("Could not create config: %s", err)
}
@@ -49,6 +50,7 @@ func InitCommand(_ *config.Config) *cobra.Command {
_ = viper.BindPFlag("insecure", initCmd.Flags().Lookup("insecure"))
initCmd.Flags().BoolP("diff", "d", false, "Show the difference between the current config and the new one")
initCmd.Flags().BoolP("quiet", "q", false, "Work quietly. Surpresses and non-error message")
initCmd.Flags().BoolP("force-overwrite", "f", false, "Force overwrite existing config file")
_ = viper.BindEnv("force-overwrite", "OC_FORCE_CONFIG_OVERWRITE")
+156 -12
View File
@@ -10,8 +10,18 @@ import (
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
"github.com/opencloud-eu/opencloud/pkg/config"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/config/parser"
storageUsersParser "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config/parser"
"github.com/opencloud-eu/opencloud/services/storage-users/pkg/event"
"github.com/opencloud-eu/opencloud/services/storage-users/pkg/revaconfig"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"
"github.com/pkg/xattr"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/theckman/yacspin"
"github.com/vmihailenco/msgpack/v5"
@@ -28,8 +38,13 @@ const (
var (
spinner *yacspin.Spinner
restartRequired = false
ignorer *ignore.Ignorer
)
type IDCacher interface {
WarmupIDCache(root string, assimilate, onlyDirty bool) error
}
// EntryInfo holds information about a directory entry.
type EntryInfo struct {
Path string
@@ -46,6 +61,7 @@ func PosixfsCommand(cfg *config.Config) *cobra.Command {
}
posixCmd.AddCommand(consistencyCmd(cfg))
posixCmd.AddCommand(scanCmd(cfg))
return posixCmd
}
@@ -54,6 +70,73 @@ func init() {
register.AddCommand(PosixfsCommand)
}
// scanCmd performs a posixfs id cache warmup scan
func scanCmd(ocCfg *config.Config) *cobra.Command {
cmd := &cobra.Command{
Use: "scan",
Short: "Perform a filesystem scan and update the ID and filemetadata cache",
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := parser.ParseConfig(ocCfg, true); err != nil {
return configlog.ReturnError(err)
}
// Parse storage users config
ocCfg.StorageUsers.Commons = ocCfg.Commons
return configlog.ReturnFatal(storageUsersParser.ParseConfig(ocCfg.StorageUsers))
},
RunE: func(cmd *cobra.Command, args []string) error {
cfg := ocCfg.StorageUsers
if cfg.Driver != "posix" {
fmt.Fprintf(os.Stderr, "This command is only available when using the 'posix' driver. Current driver: '%s'\n", cfg.Driver)
os.Exit(1)
}
// 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)
}
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, nil)
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)
}
fmt.Println("Starting posixfs scan...")
err = cacher.WarmupIDCache(cfg.Drivers.Posix.Root, true, false)
if err != nil {
fmt.Fprintf(os.Stderr, "Scan failed: %v\n", err)
return err
}
fmt.Println("Scan completed successfully.")
return nil
},
}
return cmd
}
// consistencyCmd returns a command to check the consistency of the posixfs storage.
func consistencyCmd(cfg *config.Config) *cobra.Command {
consCmd := &cobra.Command{
@@ -74,6 +157,12 @@ func checkPosixfsConsistency(cmd *cobra.Command, cfg *config.Config) error {
rootPath, _ := cmd.Flags().GetString("root")
indexesPath := filepath.Join(rootPath, "indexes")
opt, _ := options.New(map[string]interface{}{
"root": rootPath,
})
log := zerolog.Nop()
ignorer = ignore.NewIgnorer(opt, &log)
_, err := os.Stat(indexesPath)
if err != nil {
if os.IsNotExist(err) {
@@ -130,6 +219,7 @@ func checkSpaces(basePath string) {
}
func checkSpace(spacePath string) {
spinner.Message("")
spinner.Suffix(fmt.Sprintf(" Checking space '%s'", spacePath))
info, err := os.Stat(spacePath)
@@ -149,10 +239,11 @@ func checkSpace(spacePath string) {
}
checkSpaceID(spacePath)
checkNodeIDs(spacePath)
}
func checkSpaceID(spacePath string) {
spinner.Message("checking space ID uniqueness")
spinner.Message(" - checking space ID uniqueness")
entries, uniqueIDs, oldestEntry, err := gatherAttributes(spacePath)
if err != nil {
@@ -161,7 +252,6 @@ func checkSpaceID(spacePath string) {
}
if len(entries) == 0 {
logSuccess("(empty space)")
return
}
@@ -199,8 +289,65 @@ func checkSpaceID(spacePath string) {
}
fixSpaceID(spacePath, obsoleteIDs, targetID, entries)
spinner.Unpause()
} else {
logSuccess("")
}
}
func walkParentIDs(dir string, parentID string) int {
fixes := 0
entries, err := os.ReadDir(dir)
if err != nil {
logFailure("Error reading directory '%s': %v", dir, err)
return 0
}
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
if ignorer.IsIgnored(fullPath) {
continue
}
actualParentID, err := xattr.Get(fullPath, parentIDAttrName)
if err != nil || string(actualParentID) != parentID {
err = xattr.Set(fullPath, parentIDAttrName, []byte(parentID))
if err != nil {
logFailure("Failed to fix parent ID for '%s': %v", fullPath, err)
} else {
spinner.Pause()
fmt.Printf("\n + Fixed parent ID for '%s'\n", fullPath)
spinner.Unpause()
fixes++
restartRequired = true
}
}
if entry.IsDir() {
nodeID, err := xattr.Get(fullPath, idAttrName)
if err != nil || len(nodeID) == 0 {
logFailure("Directory '%s' missing '%s', skipping its children", fullPath, idAttrName)
continue
}
walkParentIDs(fullPath, string(nodeID))
}
}
return fixes
}
func checkNodeIDs(spacePath string) {
spinner.Message(" - checking parent IDs")
rootID, err := xattr.Get(spacePath, idAttrName)
if err != nil || len(rootID) == 0 {
logFailure("Space root '%s' missing '%s' attribute", spacePath, idAttrName)
return
}
fixes := walkParentIDs(spacePath, string(rootID))
if fixes > 0 {
spinner.Pause()
fmt.Printf("\n ✓ Fixed %d incorrect parent IDs in %s\n", fixes, filepath.Base(spacePath))
spinner.Unpause()
}
}
@@ -245,6 +392,9 @@ func gatherAttributes(path string) ([]EntryInfo, map[string]struct{}, EntryInfo,
for _, entry := range dirEntries {
fullPath := filepath.Join(path, entry.Name())
if ignorer.IsIgnored(fullPath) {
continue
}
info, err := os.Stat(fullPath)
if err != nil {
fmt.Printf(" - Warning: could not stat %s: %v\n", entry.Name(), err)
@@ -349,7 +499,7 @@ func updateOwnerIndexFile(basePath string, obsoleteIDs []string) error {
return fmt.Errorf("failed to write updated index file: %w", err)
}
logSuccess("Successfully removed %d item(s) and saved index file.\n", itemsRemoved)
fmt.Printf("Successfully removed %d item(s) and saved index file.\n", itemsRemoved)
return nil
}
@@ -368,13 +518,7 @@ func removeAttributes(path string) error {
}
func logFailure(message string, args ...any) {
spinner.StopFailMessage(fmt.Sprintf(message, args...))
spinner.StopFailMessage(fmt.Sprintf("\n"+message, args...))
spinner.StopFail()
spinner.Start()
}
func logSuccess(message string, args ...any) {
spinner.StopMessage(fmt.Sprintf(message, args...))
spinner.Stop()
spinner.Start()
}
+16 -7
View File
@@ -47,15 +47,22 @@ func backupOpenCloudConfigFile(configPath string) (string, error) {
}
// printBanner prints the generated opencloud config banner.
func printBanner(targetPath, ocAdminServicePassword, targetBackupConfig string) {
func printBanner(targetPath, ocAdminServicePassword string, adminPWgenerated bool, targetBackupConfig string) {
fmt.Printf(
"\n=========================================\n"+
" generated OpenCloud Config\n"+
"=========================================\n"+
" configpath : %s\n"+
" user : admin\n"+
" password : %s\n\n",
targetPath, ocAdminServicePassword)
" configpath : %s\n",
targetPath,
)
if adminPWgenerated {
fmt.Printf(" user : admin\n"+
" password : %s\n",
ocAdminServicePassword,
)
}
fmt.Println()
if targetBackupConfig != "" {
fmt.Printf("\n=========================================\n"+
"An older config file has been backed up to\n %s\n\n",
@@ -64,13 +71,15 @@ func printBanner(targetPath, ocAdminServicePassword, targetBackupConfig string)
}
// writeConfig writes the config to the target path and prints a banner
func writeConfig(configPath, ocAdminServicePassword, targetBackupConfig string, yamlOutput []byte) error {
func writeConfig(configPath, ocAdminServicePassword, targetBackupConfig string, yamlOutput []byte, adminPWgenerated, quiet bool) error {
targetPath := path.Join(configPath, configFilename)
err := os.WriteFile(targetPath, yamlOutput, 0600)
if err != nil {
return err
}
printBanner(targetPath, ocAdminServicePassword, targetBackupConfig)
if !quiet {
printBanner(targetPath, ocAdminServicePassword, adminPWgenerated, targetBackupConfig)
}
return nil
}
+4 -2
View File
@@ -22,7 +22,7 @@ var (
)
// CreateConfig creates a config file with random passwords at configPath
func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword string) error {
func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword string, quiet bool) error {
if diff && forceOverwrite {
return fmt.Errorf("diff and force-overwrite flags are mutually exclusive")
}
@@ -69,6 +69,7 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword
idmServicePassword, idpServicePassword, ocAdminServicePassword, revaServicePassword string
tokenManagerJwtSecret, collaborationWOPISecret, machineAuthAPIKey, systemUserAPIKey string
revaTransferSecret, thumbnailsTransferSecret, serviceAccountSecret, urlSigningSecret string
adminPasswdwordGenerated bool
)
if diff {
@@ -123,6 +124,7 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword
if err != nil {
return fmt.Errorf("could not generate random password for opencloud admin: %s", err)
}
adminPasswdwordGenerated = true
}
revaServicePassword, err = generators.GenerateRandomPassword(passwordLength)
@@ -311,5 +313,5 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword
if diff {
return writePatch(configPath, yamlOutput)
}
return writeConfig(configPath, ocAdminServicePassword, targetBackupConfig, yamlOutput)
return writeConfig(configPath, ocAdminServicePassword, targetBackupConfig, yamlOutput, adminPasswdwordGenerated, quiet)
}
+1 -1
View File
@@ -54,7 +54,7 @@ func NewTranslatorFromCommonConfig(defaultLocale string, domain string, path str
// Translate translates a string to the locale
func (t Translator) Translate(str, locale string) string {
return t.Locale(locale).Get("%s", str)
return t.Locale(locale).Get(str)
}
// Locale returns the gotext.Locale, use `.Get` method to translate strings
+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.0.0+dev"
LatestTag = "7.1.0+dev"
// Date indicates the build date.
// This has been removed, it looks like you can only replace static strings with recent go versions
+3
View File
@@ -34,6 +34,9 @@ var _registeredEvents = []events.Unmarshaller{
events.FileLocked{},
events.FileUnlocked{},
events.FileTouched{},
events.SpaceCreated{},
events.SpaceDeleted{},
events.SpaceDisabled{},
events.SpaceShared{},
events.SpaceShareUpdated{},
events.SpaceUnshared{},
+23
View File
@@ -131,6 +131,11 @@ func (cl *ClientlogService) processEvent(event events.Event) {
users, data, err = processShareEvent(ctx, ref, gwc, event.InitiatorID, uid, gid)
}
spaceEv := func(typ string, id *provider.StorageSpaceId, uids []string) {
evType = typ
users, data, err = processSpaceEvent(ctx, id, gwc, event.InitiatorID, uids)
}
switch e := event.Event.(type) {
default:
err = errors.New("unhandled event")
@@ -160,6 +165,16 @@ func (cl *ClientlogService) processEvent(event events.Event) {
fileEv("file-unlocked", e.Ref)
case events.FileTouched:
fileEv("file-touched", e.Ref)
case events.SpaceCreated:
spaceEv("space-created", e.ID, []string{e.Executant.GetOpaqueId()})
case events.SpaceDisabled:
spaceEv("space-disabled", e.ID, []string{e.Executant.GetOpaqueId()})
case events.SpaceDeleted:
uids := []string{}
for k := range e.FinalMembers {
uids = append(uids, k)
}
spaceEv("space-deleted", e.ID, uids)
case events.SpaceShared:
r, _ := storagespace.ParseReference(e.ID.GetOpaqueId())
shareEv("space-member-added", &r, e.GranteeUserID, e.GranteeGroupID)
@@ -239,6 +254,14 @@ func processShareEvent(ctx context.Context, ref *provider.Reference, gwc gateway
return addShareeData(ctx, gwc, data, users, shareeID, shareeGroupID)
}
func processSpaceEvent(ctx context.Context, id *provider.StorageSpaceId, gwc gateway.GatewayAPIClient, initiatorid string, uids []string) ([]string, FileEvent, error) {
data := FileEvent{
SpaceID: id.GetOpaqueId(),
InitiatorID: initiatorid,
}
return uids, data, nil
}
// custom logic for item trashed event
func processItemTrashedEvent(ctx context.Context, ref *provider.Reference, gwc gateway.GatewayAPIClient, initiatorid string, itemID *provider.ResourceId) ([]string, FileEvent, error) {
data := FileEvent{
-2
View File
@@ -1,2 +0,0 @@
auto-install-peers=true
public-hoist-pattern[]=*
+8 -15
View File
@@ -36,7 +36,7 @@
"@types/react-redux": "^7.1.34",
"@types/redux-logger": "^3.0.13",
"axios": "^1.16.0",
"i18next": "^26.1.0",
"i18next": "^26.3.0",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-resources-to-backend": "^1.2.1",
"query-string": "^9.3.1",
@@ -56,7 +56,7 @@
"babel-plugin-named-asset-import": "^0.3.8",
"babel-preset-react-app": "^10.1.0",
"case-sensitive-paths-webpack-plugin": "2.4.0",
"cldr": "^7.9.0",
"cldr": "^8.0.0",
"css-loader": "^7.1.4",
"css-minimizer-webpack-plugin": "^8.0.0",
"dotenv": "17.4.2",
@@ -64,30 +64,23 @@
"html-webpack-plugin": "^5.6.7",
"i18next-conv": "^15.1.2",
"i18next-parser": "^9.4.0",
"license-checker-rseidelsohn": "4.4.2",
"license-checker-rseidelsohn": "5.0.1",
"mini-css-extract-plugin": "2.9.2",
"pnp-webpack-plugin": "1.7.0",
"postcss-flexbugs-fixes": "5.0.2",
"postcss-loader": "4.3.0",
"postcss-loader": "8.2.1",
"postcss-normalize": "13.0.1",
"postcss-preset-env": "11.2.0",
"react-dev-utils": "^12.0.1",
"resolve": "^1.22.12",
"resolve-url-loader": "^5.0.0",
"sass-loader": "^16.0.8",
"sass-loader": "^17.0.0",
"source-map-explorer": "^2.5.3",
"typescript": "^5.9.3",
"typescript": "^6.0.3",
"url-loader": "4.1.1",
"webpack": "5.105.2",
"webpack-manifest-plugin": "5.0.0",
"workbox-webpack-plugin": "7.4.0"
"workbox-webpack-plugin": "7.4.1"
},
"pnpm": {
"overrides": {
"fast-uri": ">=3.1.2",
"serialize-javascript@<7.0.3": ">=7.0.3",
"@xmldom/xmldom": ">=0.8.13"
}
},
"packageManager": "pnpm@9.15.4"
"packageManager": "pnpm@11.1.3"
}
+1904 -1397
View File
File diff suppressed because it is too large. Load diff
+14
View File
@@ -0,0 +1,14 @@
autoInstallPeers: true
publicHoistPattern:
- "*"
overrides:
fast-uri: ">=3.1.2"
postcss: ">=8.5.10"
"serialize-javascript@<7.0.3": ">=7.0.3"
"@babel/plugin-transform-modules-systemjs": ">=7.29.4"
"@xmldom/xmldom": ">=0.8.13"
allowBuilds:
esbuild: true
+1 -1
View File
@@ -1,6 +1,6 @@
SHELL := bash
NAME := web
WEB_ASSETS_VERSION = v7.0.1
WEB_ASSETS_VERSION = v7.1.0
WEB_ASSETS_BRANCH = main
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
+17 -10
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
"path"
"regexp"
"slices"
"strconv"
"strings"
@@ -36,6 +37,8 @@ const (
// TODO elementNameFilterFiles = "filter-files"
)
var spacesSearchRegex = regexp.MustCompile(`^/(?:remote\.php/)?dav/spaces/([^/]+)`)
// Search is the endpoint for retrieving search results for REPORT requests
func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
logger := g.log.SubloggerWithRequestID(r.Context())
@@ -76,9 +79,9 @@ func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
PageSize: int32(rep.SearchFiles.Search.Limit),
}
// Limit search to the according space when searching /dav/spaces/
if strings.HasPrefix(r.URL.Path, "/dav/spaces") {
space := strings.TrimPrefix(r.URL.Path, "/dav/spaces/")
// Limit search to the according space when searching /dav/spaces/<spaceid>
if matches := spacesSearchRegex.FindStringSubmatch(r.URL.Path); matches != nil {
space := matches[1]
rid, err := storagespace.ParseID(space)
if err != nil {
logger.Debug().Err(err).Msg("error parsing the space id for filtering")
@@ -92,6 +95,10 @@ func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
}
}
}
davPrefix := ""
if strings.HasPrefix(r.URL.Path, "/remote.php") {
davPrefix = "/remote.php"
}
rsp, err := g.searchClient.Search(ctx, req)
if err != nil {
@@ -105,12 +112,12 @@ func (g Webdav) Search(w http.ResponseWriter, r *http.Request) {
logger.Error().Err(err).Msg("could not get search results")
return
}
g.sendSearchResponse(rsp, w, r, user)
g.sendSearchResponse(davPrefix, rsp, w, r, user)
}
func (g Webdav) sendSearchResponse(rsp *searchsvc.SearchResponse, w http.ResponseWriter, r *http.Request, user *userv1beta1.User) {
func (g Webdav) sendSearchResponse(davPrefix string, rsp *searchsvc.SearchResponse, w http.ResponseWriter, r *http.Request, user *userv1beta1.User) {
logger := g.log.SubloggerWithRequestID(r.Context())
responsesXML, err := multistatusResponse(r.Context(), g.config.OpenCloudPublicURL, rsp.Matches, user)
responsesXML, err := multistatusResponse(r.Context(), davPrefix, g.config.OpenCloudPublicURL, rsp.Matches, user)
if err != nil {
logger.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
@@ -128,10 +135,10 @@ func (g Webdav) sendSearchResponse(rsp *searchsvc.SearchResponse, w http.Respons
}
// multistatusResponse converts a list of matches into a multistatus response string
func multistatusResponse(ctx context.Context, publicURL string, matches []*searchmsg.Match, user *userv1beta1.User) ([]byte, error) {
func multistatusResponse(ctx context.Context, davPrefix, publicURL string, matches []*searchmsg.Match, user *userv1beta1.User) ([]byte, error) {
responses := make([]*propfind.ResponseXML, 0, len(matches))
for i := range matches {
res, err := matchToPropResponse(ctx, publicURL, matches[i], user)
res, err := matchToPropResponse(ctx, davPrefix, publicURL, matches[i], user)
if err != nil {
return nil, err
}
@@ -147,7 +154,7 @@ func multistatusResponse(ctx context.Context, publicURL string, matches []*searc
return msg, nil
}
func matchToPropResponse(ctx context.Context, publicURL string, match *searchmsg.Match, user *userv1beta1.User) (*propfind.ResponseXML, error) {
func matchToPropResponse(ctx context.Context, davPrefix, publicURL string, match *searchmsg.Match, user *userv1beta1.User) (*propfind.ResponseXML, error) {
// unfortunately, search uses own versions of ResourceId and Ref. So we need to assert them here
var (
ref string
@@ -180,7 +187,7 @@ func matchToPropResponse(ctx context.Context, publicURL string, match *searchmsg
return nil, err
}
response := propfind.ResponseXML{
Href: net.EncodePath(path.Join("/remote.php/dav/spaces/", ref)),
Href: net.EncodePath(path.Join(davPrefix, "/dav/spaces/", ref)),
Propstat: []propfind.PropstatXML{},
}
@@ -0,0 +1,35 @@
package svc
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestSearch(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Search Suite")
}
var _ = Describe("SpacesSearchRegex", func() {
DescribeTable("path matching",
func(path, expectedSpace string, shouldMatch bool) {
matches := spacesSearchRegex.FindStringSubmatch(path)
if shouldMatch {
Expect(matches).ToNot(BeNil(), "Expected path %q to match", path)
Expect(matches[1]).To(Equal(expectedSpace), "Expected space to be %q", expectedSpace)
} else {
Expect(matches).To(BeNil(), "Expected path %q not to match", path)
}
},
Entry("standard dav spaces path", "/dav/spaces/12345", "12345", true),
Entry("remote.php dav spaces path", "/remote.php/dav/spaces/12345", "12345", true),
Entry("standard dav spaces path with subpaths", "/dav/spaces/12345/some/folder", "12345", true),
Entry("remote.php dav spaces path with subpaths", "/remote.php/dav/spaces/12345/some/folder", "12345", true),
Entry("standard dav spaces path without space", "/dav/spaces/", "", false),
Entry("remote.php dav spaces path without space", "/remote.php/dav/spaces/", "", false),
Entry("prefix match only", "/dav/spaces", "", false),
Entry("unrelated path", "/dav/files/123", "", false),
)
})
@@ -190,10 +190,6 @@
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
#### [Skip tests for different languages](https://github.com/opencloud-eu/opencloud/issues/183)
- [apiActivities/activities.feature:2598](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiActivities/activities.feature#L2598)
#### [Missing properties in REPORT response](https://github.com/owncloud/ocis/issues/9780), [d:getetag property has empty value in REPORT response](https://github.com/owncloud/ocis/issues/9783)
- [apiSearch1/search.feature:437](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L437)
@@ -190,10 +190,6 @@
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
#### [Skip tests for different languages](https://github.com/opencloud-eu/opencloud/issues/183)
- [apiActivities/activities.feature:2598](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiActivities/activities.feature#L2598)
#### [Missing properties in REPORT response](https://github.com/owncloud/ocis/issues/9780), [d:getetag property has empty value in REPORT response](https://github.com/owncloud/ocis/issues/9783)
- [apiSearch1/search.feature:437](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L437)
@@ -1,298 +1,9 @@
## Scenarios that are expected to fail when remote.php is not used
#### [REPORT request without remote.php returns empty result (only with dav/spaces path)](https://github.com/owncloud/ocis/issues/10329)
- [apiContract/sharesReport.feature:43](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L43)
- [apiContract/sharesReport.feature:84](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L84)
- [apiContract/sharesReport.feature:150](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L150)
- [apiContract/sharesReport.feature:180](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L180)
- [apiContract/spacesReport.feature:16](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L16)
- [apiContract/spacesReport.feature:35](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L35)
- [apiContract/spacesReport.feature:55](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L55)
- [apiContract/spacesReport.feature:74](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L74)
- [apiContract/spacesSharesReport.feature:46](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L46)
- [apiContract/spacesSharesReport.feature:77](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L77)
- [apiSearch1/dateSearch.feature:19](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L19)
- [apiSearch1/dateSearch.feature:39](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L39)
- [apiSearch1/dateSearch.feature:40](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L40)
- [apiSearch1/dateSearch.feature:41](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L41)
- [apiSearch1/dateSearch.feature:42](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L42)
- [apiSearch1/dateSearch.feature:43](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L43)
- [apiSearch1/dateSearch.feature:44](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L44)
- [apiSearch1/dateSearch.feature:45](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L45)
- [apiSearch1/dateSearch.feature:46](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L46)
- [apiSearch1/dateSearch.feature:47](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L47)
- [apiSearch1/dateSearch.feature:48](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L48)
- [apiSearch1/dateSearch.feature:50](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L50)
- [apiSearch1/dateSearch.feature:53](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L53)
- [apiSearch1/search.feature:113](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L113)
- [apiSearch1/search.feature:176](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L176)
- [apiSearch1/search.feature:280](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L280)
- [apiSearch1/search.feature:304](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L304)
- [apiSearch1/search.feature:317](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L317)
- [apiSearch1/search.feature:318](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L318)
- [apiSearch1/search.feature:319](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L319)
- [apiSearch1/search.feature:320](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L320)
- [apiSearch1/search.feature:321](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L321)
- [apiSearch1/search.feature:324](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L324)
- [apiSearch1/search.feature:356](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L356)
- [apiSearch1/search.feature:369](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L369)
- [apiSearch1/search.feature:396](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L396)
- [apiSearch1/search.feature:410](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch1/search.feature#L410)
- [apiSearch2/mediaTypeSearch.feature:31](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L31)
- [apiSearch2/mediaTypeSearch.feature:32](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L32)
- [apiSearch2/mediaTypeSearch.feature:33](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L33)
- [apiSearch2/mediaTypeSearch.feature:34](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L34)
- [apiSearch2/mediaTypeSearch.feature:35](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L35)
- [apiSearch2/mediaTypeSearch.feature:36](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L36)
- [apiSearch2/mediaTypeSearch.feature:37](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L37)
- [apiSearch2/mediaTypeSearch.feature:38](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L38)
- [apiSearch2/mediaTypeSearch.feature:39](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L39)
- [apiSearch2/mediaTypeSearch.feature:60](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L60)
- [apiSearch2/mediaTypeSearch.feature:61](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L61)
- [apiSearch2/mediaTypeSearch.feature:62](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L62)
- [apiSearch2/mediaTypeSearch.feature:63](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L63)
- [apiSearch2/mediaTypeSearch.feature:64](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L64)
- [apiSearch2/mediaTypeSearch.feature:65](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L65)
- [apiSearch2/mediaTypeSearch.feature:66](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L66)
- [apiSearch2/mediaTypeSearch.feature:67](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L67)
- [apiSearch2/mediaTypeSearch.feature:68](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L68)
- [apiSearch2/mediaTypeSearch.feature:90](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L90)
- [apiSearch2/mediaTypeSearch.feature:91](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L91)
- [apiSearch2/mediaTypeSearch.feature:92](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L92)
- [apiSearch2/mediaTypeSearch.feature:93](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L93)
- [apiSearch2/mediaTypeSearch.feature:94](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L94)
- [apiSearch2/mediaTypeSearch.feature:95](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L95)
- [apiSearch2/mediaTypeSearch.feature:96](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L96)
- [apiSearch2/mediaTypeSearch.feature:97](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L97)
- [apiSearch2/mediaTypeSearch.feature:98](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L98)
- [apiSearch2/mediaTypeSearch.feature:126](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L126)
- [apiSearch2/mediaTypeSearch.feature:127](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L127)
- [apiSearch2/mediaTypeSearch.feature:128](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L128)
- [apiSearch2/mediaTypeSearch.feature:129](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L129)
- [apiSearch2/mediaTypeSearch.feature:130](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L130)
- [apiSearch2/mediaTypeSearch.feature:131](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L131)
- [apiSearch2/mediaTypeSearch.feature:132](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L132)
- [apiSearch2/mediaTypeSearch.feature:133](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L133)
- [apiSearch2/mediaTypeSearch.feature:134](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L134)
- [apiSearch2/mediaTypeSearch.feature:161](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L161)
- [apiSearch2/mediaTypeSearch.feature:162](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L162)
- [apiSearch2/mediaTypeSearch.feature:163](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L163)
- [apiSearch2/mediaTypeSearch.feature:164](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L164)
- [apiSearch2/mediaTypeSearch.feature:165](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L165)
- [apiSearch2/mediaTypeSearch.feature:166](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L166)
- [apiSearch2/mediaTypeSearch.feature:167](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L167)
- [apiSearch2/mediaTypeSearch.feature:168](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L168)
- [apiSearch2/mediaTypeSearch.feature:169](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L169)
- [apiSearch2/mediaTypeSearch.feature:172](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L172)
- [apiSearch2/tagSearch.feature:36](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L36)
- [apiSearch2/tagSearch.feature:64](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L64)
- [apiSearch2/tagSearch.feature:85](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L85)
- [apiSearch2/tagSearch.feature:108](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L108)
- [apiSearch2/tagSearch.feature:138](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L138)
- [apiSearch2/tagSearch.feature:172](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L172)
- [apiSearch2/tagSearch.feature:207](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L207)
- [apiSearch2/tagSearch.feature:226](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L226)
- [apiSearch2/tagSearch.feature:254](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L254)
- [apiSearch2/tagSearch.feature:255](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L255)
- [apiSearch2/tagSearch.feature:256](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L256)
- [apiSearch2/tagSearch.feature:257](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L257)
- [apiSearch2/tagSearch.feature:258](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L258)
- [apiSearch2/tagSearch.feature:259](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L259)
- [apiSearch2/tagSearch.feature:260](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L260)
- [apiSearch2/tagSearch.feature:261](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L261)
- [apiSearch2/tagSearch.feature:262](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L262)
- [apiSearch2/tagSearch.feature:263](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L263)
- [apiSearch2/tagSearch.feature:265](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L265)
- [apiSearchContent/contentSearch.feature:27](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L27)
- [apiSearchContent/contentSearch.feature:51](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L51)
- [apiSearchContent/contentSearch.feature:84](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L84)
- [apiSearchContent/contentSearch.feature:112](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L112)
- [apiSearchContent/contentSearch.feature:131](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L131)
- [apiSearchContent/contentSearch.feature:147](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L147)
- [apiSearchContent/contentSearch.feature:163](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L163)
- [apiSearchContent/contentSearch.feature:186](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L186)
- [apiSearchContent/contentSearch.feature:215](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L215)
- [apiSearchContent/contentSearch.feature:233](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L233)
- [apiSearchContent/contentSearch.feature:234](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L234)
- [apiSearchContent/contentSearch.feature:235](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L235)
- [apiSearchContent/contentSearch.feature:236](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L236)
- [apiSearchContent/contentSearch.feature:237](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L237)
- [apiSearchContent/contentSearch.feature:238](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L238)
- [apiSearchContent/contentSearch.feature:267](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L267)
- [cliCommands/searchReIndex.feature:15](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/cliCommands/searchReIndex.feature#L15)
#### [Downloading public files without remote.php returns 401 unauthorized error](https://github.com/owncloud/ocis/issues/9724)
- [apiSpaces/editPublicLinkOfSpace.feature:48](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature#L48)
- [apiSpaces/editPublicLinkOfSpace.feature:49](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature#L49)
- [apiSpacesShares/publicLinkDownload.feature:16](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature#L16)
- [apiSpacesShares/publicLinkDownload.feature:32](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature#L32)
- [apiSpacesShares/shareSpacesViaLink.feature:42](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L42)
- [apiSpacesShares/shareSpacesViaLink.feature:43](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L43)
- [apiSpacesShares/shareSpacesViaLink.feature:44](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L44)
- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:146](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L146)
- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:147](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L147)
- [apiSharingNgLinkSharePermission/createLinkShare.feature:473](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/createLinkShare.feature#L473)
- [apiSharingNgLinkSharePermission/createLinkShare.feature:1208](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/createLinkShare.feature#L1208)
- [apiSharingNgLinkSharePermission/updateLinkShare.feature:204](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/updateLinkShare.feature#L204)
- [apiSharingNgLinkSharePermission/updateLinkShare.feature:282](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/updateLinkShare.feature#L282)
- [apiSharingNgLinkShareRoot/updateLinkShare.feature:10](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNgLinkShareRoot/updateLinkShare.feature#L10)
- [apiSharingNgLinkShareRoot/updateLinkShare.feature:42](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSharingNgLinkShareRoot/updateLinkShare.feature#L42)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:220](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L220)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:221](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L221)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:59](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L59)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:60](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L60)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:88](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L88)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:89](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L89)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:122](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L122)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:123](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L123)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:209](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L209)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:210](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L210)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:229](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L229)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:230](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L230)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:257](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L257)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:258](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L258)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:273](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L273)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:274](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L274)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:277](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L277)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:291](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L291)
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:111](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L111)
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:112](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L112)
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:249](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L249)
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:250](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L250)
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:276](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L276)
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:277](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L277)
#### [Most (if not all) requests to public link share without remote.php returns 401 Unauthorized error](https://github.com/owncloud/ocis/issues/10331)
- [apiSpaces/publicLink.feature:18](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L18)
- [apiSpaces/publicLink.feature:23](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L23)
- [apiSpaces/publicLink.feature:28](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L28)
- [apiSpaces/publicLink.feature:34](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L34)
- [apiSpaces/publicLink.feature:40](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L40)
- [apiSpaces/uploadSpaces.feature:95](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/uploadSpaces.feature#L95)
- [apiSpaces/uploadSpaces.feature:112](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/uploadSpaces.feature#L112)
- [apiSpaces/uploadSpaces.feature:129](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpaces/uploadSpaces.feature#L129)
- [apiSpacesShares/shareSpacesViaLink.feature:58](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L58)
- [apiDepthInfinity/propfind.feature:74](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiDepthInfinity/propfind.feature#L74)
- [apiDepthInfinity/propfind.feature:124](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiDepthInfinity/propfind.feature#L124)
- [apiLocks/lockFiles.feature:490](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L490)
- [apiLocks/lockFiles.feature:487](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L487)
- [apiLocks/lockFiles.feature:488](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L488)
- [apiLocks/lockFiles.feature:489](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L489)
- [apiLocks/lockFiles.feature:513](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L513)
- [apiLocks/lockFiles.feature:510](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L510)
- [apiLocks/lockFiles.feature:511](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L511)
- [apiLocks/lockFiles.feature:512](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L512)
- [apiLocks/unlockFiles.feature:320](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L320)
- [apiLocks/unlockFiles.feature:321](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L321)
- [apiLocks/unlockFiles.feature:322](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L322)
- [apiLocks/unlockFiles.feature:323](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L323)
- [apiAntivirus/antivirus.feature:114](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L114)
- [apiAntivirus/antivirus.feature:115](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L115)
- [apiAntivirus/antivirus.feature:116](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L116)
- [apiAntivirus/antivirus.feature:117](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L117)
- [apiAntivirus/antivirus.feature:118](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L118)
- [apiAntivirus/antivirus.feature:119](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L119)
- [apiAntivirus/antivirus.feature:140](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L140)
- [apiAntivirus/antivirus.feature:141](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L141)
- [apiAntivirus/antivirus.feature:142](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L142)
- [apiAntivirus/antivirus.feature:143](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L143)
- [apiAntivirus/antivirus.feature:144](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L144)
- [apiAntivirus/antivirus.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L145)
- [apiAntivirus/antivirus.feature:357](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L357)
- [apiAntivirus/antivirus.feature:358](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L358)
- [apiAntivirus/antivirus.feature:359](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L359)
- [apiCollaboration/wopi.feature:956](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L956)
- [apiCollaboration/wopi.feature:957](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L957)
- [apiCollaboration/wopi.feature:958](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L958)
- [apiCollaboration/wopi.feature:961](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L961)
- [apiCollaboration/wopi.feature:1047](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1047)
- [apiCollaboration/wopi.feature:1048](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1048)
- [apiCollaboration/wopi.feature:1049](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1049)
- [apiCollaboration/wopi.feature:1052](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1052)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:27](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L27)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:28](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L28)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:29](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L29)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:30](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L30)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:33](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L33)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:46](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L46)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:70](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L70)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:95](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L95)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:120](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L120)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:132](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L132)
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:145](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L145)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:189](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L189)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:190](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L190)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:304](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L304)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:328](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L328)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:329](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L329)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:344](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L344)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:345](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L345)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:348](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L348)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:363](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L363)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:377](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L377)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:13](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L13)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:28](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L28)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:44](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L44)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:60](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L60)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:75](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L75)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:92](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L92)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:105](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L105)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:137](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L137)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:138](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L138)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:139](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L139)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:140](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L140)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:158](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L158)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:159](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L159)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:160](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L160)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:161](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L161)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:180](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L180)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:181](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L181)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:182](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L182)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:183](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L183)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:186](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L186)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:212](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L212)
- [coreApiSharePublicLink2/copyFromPublicLink.feature:225](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L225)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:28](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L28)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:29](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L29)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:30](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L30)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:33](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L33)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:44](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L44)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:58](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L58)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:70](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L70)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:82](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L82)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:94](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L94)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:106](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L106)
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:118](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L118)
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:238](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L238)
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:239](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L239)
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:240](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L240)
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:262](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L262)
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:263](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L263)
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:264](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L264)
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:308](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L308)
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:309](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L309)
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:310](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L310)
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:333](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L333)
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:334](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L334)
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:335](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L335)
- [coreApiWebdavEtagPropagation2/copyFileFolder.feature:135](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature#L135)
- [coreApiWebdavEtagPropagation2/copyFileFolder.feature:136](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature#L136)
- [coreApiWebdavEtagPropagation2/copyFileFolder.feature:137](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature#L137)
- [coreApiWebdavEtagPropagation2/createFolder.feature:111](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature#L111)
- [coreApiWebdavEtagPropagation2/upload.feature:188](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature#L188)
- [coreApiWebdavEtagPropagation2/upload.feature:189](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature#L189)
- [coreApiWebdavEtagPropagation2/upload.feature:190](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature#L190)
- [coreApiWebdavOperations/listFiles.feature:112](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L112)
- [coreApiWebdavOperations/listFiles.feature:140](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L140)
- [coreApiWebdavOperations/propfind.feature:40](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L40)
- [coreApiWebdavOperations/propfind.feature:55](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L55)
- [coreApiWebdavOperations/propfind.feature:69](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L69)
- [coreApiWebdavUpload/uploadFile.feature:376](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavUpload/uploadFile.feature#L376)
- [apiActivities/shareActivities.feature:1956](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiActivities/shareActivities.feature#L1956)
- [apiActivities/shareActivities.feature:2095](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiActivities/shareActivities.feature#L2095)
#### [Cannot create new TUS upload resource using /webdav without remote.php - returns html instead](https://github.com/owncloud/ocis/issues/10346)
@@ -339,8 +50,8 @@
- [coreApiWebdavUploadTUS/uploadToShare.feature:69](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L69)
- [coreApiWebdavUploadTUS/uploadToShare.feature:89](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L89)
#### [PROPFIND to /webdav root (old dav path) without remote.php returns html instead of xml](https://github.com/owncloud/ocis/issues/10334)
#### [PROPFIND to /webdav root (old dav path) without remote.php returns html instead of xml](https://github.com/owncloud/ocis/issues/10334)
- [coreApiAuth/webDavAuth.feature:15](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiAuth/webDavAuth.feature#L15)
- [coreApiAuth/webDavAuth.feature:25](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiAuth/webDavAuth.feature#L25)
- [coreApiShareManagementToShares/moveReceivedShare.feature:49](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature#L49)
@@ -350,11 +61,6 @@
- [coreApiWebdavOperations/propfind.feature:26](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L26)
- [coreApiWebdavOperations/propfind.feature:36](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L36)
#### [Public cannot download file preview of unprotected (without password) link share without remote.php](https://github.com/owncloud/ocis/issues/10341)
- [coreApiWebdavPreviews/linkSharePreviews.feature:25](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/linkSharePreviews.feature#L25)
- [coreApiWebdavPreviews/linkSharePreviews.feature:54](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavPreviews/linkSharePreviews.feature#L54)
#### [Trying to create .. resource with /webdav root (old dav path) without remote.php returns html](https://github.com/owncloud/ocis/issues/10339)
- [coreApiWebdavCopyCreate/createFileFolder.feature:176](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/createFileFolder.feature#L176)
@@ -1,6 +1,6 @@
load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
package(default_visibility = ["//visibility:public"])
+17 -1
View File
@@ -71,6 +71,7 @@ type ServeMux struct {
streamErrorHandler StreamErrorHandlerFunc
routingErrorHandler RoutingErrorHandlerFunc
disablePathLengthFallback bool
disableHTTPMethodOverride bool
unescapingMode UnescapingMode
writeContentLength bool
disableChunkedEncoding bool
@@ -271,6 +272,19 @@ func WithDisablePathLengthFallback() ServeMuxOption {
}
}
// WithDisableHTTPMethodOverride returns a ServeMuxOption that disables the
// X-HTTP-Method-Override header handling.
//
// When this option is used, the mux will no longer allow POST requests with
// the X-HTTP-Method-Override header to override the HTTP method. The path
// length fallback (POST with application/x-www-form-urlencoded falling back
// to a matching GET handler) is not affected by this option.
func WithDisableHTTPMethodOverride() ServeMuxOption {
return func(serveMux *ServeMux) {
serveMux.disableHTTPMethodOverride = true
}
}
// WithWriteContentLength returns a ServeMuxOption to enable writing content length on non-streaming responses
func WithWriteContentLength() ServeMuxOption {
return func(serveMux *ServeMux) {
@@ -405,7 +419,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path = r.URL.RawPath
}
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) {
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && !s.disableHTTPMethodOverride && s.isPathLengthFallback(r) {
if err := r.ParseForm(); err != nil {
_, outboundMarshaler := MarshalerForRequest(s, r)
sterr := status.Error(codes.InvalidArgument, err.Error())
@@ -467,6 +481,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
HTTPStatus: http.StatusBadRequest,
Err: mse,
})
return
}
continue
}
@@ -509,6 +524,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
HTTPStatus: http.StatusBadRequest,
Err: mse,
})
return
}
continue
}
+49
View File
@@ -0,0 +1,49 @@
# GEMINI.md - gotext Project Context & Guidelines
This document serves as the primary source of truth for the `gotext` project architecture, conventions, and workflows. Adherence to these guidelines is mandatory for all development tasks.
## Project Overview
`gotext` is a GNU Gettext implementation for Go (Golang). it provides a thread-safe, hierarchical translation system that supports PO/MO files, plural forms, and context-aware translations.
## Core Architecture
The project follows a hierarchical structure for managing translations:
1. **Global Package API (`gotext.go`)**: Provides convenient, global functions (e.g., `Get`, `GetD`, `GetN`) that rely on a shared `globalConfig`.
2. **Locales (`locale.go`)**: Represent a specific language (e.g., "en_US", "es_ES"). A `Locale` container manages multiple `Domains`.
3. **Domains (`domain.go`)**: The core translation engine for a specific set of messages. It handles:
- Message retrieval (with support for plural forms and `msgctxt`).
- Header parsing (including plural-form rules).
- Thread-safety via `sync.RWMutex`.
4. **Translators (`translator.go`)**: An interface for translation sources.
- **PO (`po.go`)**: Parsers for human-readable Gettext Portable Object files.
- **MO (`mo.go`)**: Parsers for binary Gettext Machine Object files.
5. **Plural Forms (`plurals/`)**: A dedicated package for compiling and evaluating GNU Gettext plural form expressions.
## CLI Tools
- **xgotext (`cli/xgotext/`)**: A tool to scan Go source files, extract translatable strings (matching `Get`, `GetD`, etc.), and generate/update PO files.
## Development Workflows
### Branching & PRs
- **Feature Branches**: All development MUST happen in dedicated feature branches (e.g., `feature/description-of-change`).
- **Target Branch**: All Pull Requests MUST be made against the `master` branch.
- **Review Requirement**: No direct commits to `master` are allowed. All changes must be reviewed via a PR.
- **GitHub Integration**: If the `gh` CLI tool is installed and authenticated, it should be used for managing Pull Requests, Issues, and Reviews. Otherwise, these tasks must be performed manually through the GitHub web interface.
### Coding Standards
- **Idiomatic Go**: Follow standard Go conventions (`gofmt`, `go vet`).
- **Thread Safety**: Ensure all shared state (like in `Domain`) is protected by appropriate synchronization primitives.
- **Testing**:
- Every new feature or bug fix must include corresponding tests.
- Use the `fixtures/` directory for sample PO/MO files.
- Run `go test ./...` to ensure no regressions.
## Key Files & Symbols
- `gotext.go`: `Configure`, `Get`, `GetD`, `GetN`.
- `locale.go`: `Locale`, `NewLocale`, `AddDomain`.
- `domain.go`: `Domain`, `Get`, `GetN`, `parseHeaders`.
- `translator.go`: `Translator` interface.
- `plurals/compiler.go`: `Compile` function for plural expressions.
## Instructions for Gemini CLI
- Always work in a feature branch.
- Before submitting, verify changes with existing and new tests.
- Propose a PR to `master` after confirmation.
+94 -210
View File
@@ -9,258 +9,142 @@
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
`gotext` is a native Go implementation of the GNU Gettext utilities. It provides a thread-safe, flexible, and powerful way to handle internationalization (i18n) and localization (l10n) in your Go applications.
# Features
---
- Implements GNU gettext support in native Go.
- Complete support for [PO files](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) including:
- Support for multiline strings and headers.
- Support for variables inside translation strings using Go's [fmt syntax](https://golang.org/pkg/fmt/).
- Support for [pluralization rules](https://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html).
- Support for [message contexts](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html).
- Support for MO files.
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
- It works with UTF-8 encoding as it's the default for Go language.
- Unit tests available.
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
- Ready to use inside Go templates.
- Objects are serializable to []byte to store them in cache.
- Support for Go Modules.
**📖 [Read the Full Documentation](https://leonelquinteros.github.io/gotext/)**
---
# License
### Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [CLI Tool (xgotext)](#cli-tool-xgotext)
- [Advanced Usage](#advanced-usage)
- [Locale Object](#using-locale-object)
- [Plural Forms](#use-plural-forms-of-translations)
- [Dynamic Variables](#using-dynamic-variables-on-translations)
- [Directory Structure](#locales-directories-structure)
- [Contributing](#contributing)
- [License](#license)
[MIT license](LICENSE)
---
## Features
- Native Go implementation of Gettext (no external dependencies).
- Full support for **PO and MO files**.
- **Pluralization rules** support via GNU Gettext plural-form expressions.
- **Message context** support (`msgctxt`).
- Thread-safe for concurrent use.
- Works with UTF-8 by default.
- Integrated CLI tool (`xgotext`) for string extraction.
- Serializable objects for caching.
- Seamless integration with Go's `text/template` and `html/template`.
# Documentation
---
Refer to package documentation at (https://pkg.go.dev/github.com/leonelquinteros/gotext)
# Installation
```
## Installation
```bash
go get github.com/leonelquinteros/gotext
```
- There are no requirements or dependencies to use this package.
- No need to install GNU gettext utilities (unless specific needs of CLI tools).
- No need for environment variables. Some naming conventions are applied but not needed.
---
# Usage examples
## Using package for single language/domain settings
For quick/simple translations you can use the package level functions directly.
## Getting Started
For a quick start, use the package-level API:
```go
package main
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Configure package
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
// Configure package: locales path, language, and domain
gotext.Configure("/path/to/locales", "en_US", "default")
// Translate text from default domain
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
// Simple translation
fmt.Println(gotext.Get("Hello, world!"))
// Translate text from a different domain without reconfigure
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
// Translation with variables
fmt.Println(gotext.Get("Hello, %s!", "Gopher"))
}
```
## Using dynamic variables on translations
For more details, see the [Getting Started Guide](docs/GETTING_STARTED.md).
All translation strings support dynamic variables to be inserted without translate.
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
---
## CLI Tool (xgotext)
`gotext` includes a command-line tool to extract translatable strings from your Go source code.
**Install xgotext:**
```bash
go install github.com/leonelquinteros/gotext/cli/xgotext@latest
```
**Extract strings:**
```bash
xgotext -p . -o locales/en_US/default.po
```
See the [xgotext Documentation](docs/xgotext.md) for full usage details.
---
## Advanced Usage
### Using Locale object
For managing multiple languages or domains independently:
```go
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Configure package
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
// Set variables
name := "John"
// Translate text with variables
fmt.Println(gotext.Get("Hi, my name is %s", name))
}
l := gotext.NewLocale("/path/to/locales", "es_UY")
l.AddDomain("default")
fmt.Println(l.Get("Translate this"))
```
## Using Locale object
When having multiple languages/domains/libraries at the same time, you can create Locale objects for each variation
so you can handle each settings on their own.
### Use plural forms of translations
`gotext` handles complex pluralization rules defined in PO headers:
```go
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Create Locale with library path and language code
l := gotext.NewLocale("/path/to/locales/root/dir", "es_UY")
// Load domain '/path/to/locales/root/dir/es_UY/default.po'
l.AddDomain("default")
// Translate text from default domain
fmt.Println(l.Get("Translate this"))
// Load different domain
l.AddDomain("translations")
// Translate text from domain
fmt.Println(l.GetD("translations", "Translate this"))
}
// GetN(singular, plural, quantity, args...)
fmt.Println(gotext.GetN("I have one apple.", "I have %d apples.", 5, 5))
```
See the [Plural Forms Guide](docs/PLURALS.md) for more examples.
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
If you set the Locale object as "Loc" in the template, then the template code would look like:
```
{{ .Loc.Get "Translate this" }}
```
## Using the Po object to handle .po files and PO-formatted strings
For when you need to work with PO files and strings,
you can directly use the Po object to parse it and access the translations in there in the same way.
### Using dynamic variables on translations
Supports standard `fmt` package syntax:
```go
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Set PO content
str := `
msgid "Translate this"
msgstr "Translated text"
msgid "Another string"
msgstr ""
msgid "One with var: %s"
msgstr "This one sets the var: %s"
`
// Create Po object
po := gotext.NewPo()
po.Parse(str)
fmt.Println(po.Get("Translate this"))
}
name := "John"
fmt.Println(gotext.Get("Hi, my name is %s", name))
```
---
## Use plural forms of translations
PO format supports defining one or more plural forms for the same translation.
Relying on the PO file headers, a Plural-Forms formula can be set on the translation file
as defined in (https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html)
```go
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
func main() {
// Set PO content
str := `
msgid ""
msgstr ""
# Header below
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Translate this"
msgstr "Translated text"
msgid "Another string"
msgstr ""
msgid "One with var: %s"
msgid_plural "Several with vars: %s"
msgstr[0] "This one is the singular: %s"
msgstr[1] "This one is the plural: %s"
`
// Create Po object
po := new(gotext.Po)
po.Parse(str)
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
// "This one is the plural: Variable"
}
```
# Locales directories structure
The package will assume a directories structure starting with a base path that will be provided to the package configuration
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
all package functions will be able to resolve this generalization and provide translations for the more general library.
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
A library directory structure can look like:
## Locales directories structure
The package expects a standard Gettext directory structure:
```
/path/to/locales
/path/to/locales/en_US
/path/to/locales/en_US/LC_MESSAGES
/path/to/locales/en_US/LC_MESSAGES/default.po
/path/to/locales/en_US/LC_MESSAGES/extras.po
/path/to/locales/en_UK
/path/to/locales/en_UK/LC_MESSAGES
/path/to/locales/en_UK/LC_MESSAGES/default.po
/path/to/locales/en_UK/LC_MESSAGES/extras.po
/path/to/locales/en_AU
/path/to/locales/en_AU/LC_MESSAGES
/path/to/locales/en_AU/LC_MESSAGES/default.po
/path/to/locales/en_AU/LC_MESSAGES/extras.po
/path/to/locales/es
/path/to/locales/es/default.po
/path/to/locales/es/extras.po
/path/to/locales/es_ES
/path/to/locales/es_ES/default.po
/path/to/locales/es_ES/extras.po
/path/to/locales/fr
/path/to/locales/fr/default.po
/path/to/locales/fr/extras.po
/en_US
/LC_MESSAGES
default.po
/es_ES
default.po
```
It supports automatic language simplification (e.g., falling back from `en_UK` to `en`).
And so on...
---
## Contributing
We welcome contributions of all kinds!
- [Contributing Guidelines](CONTRIBUTING.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)
# Contribute
- Please, contribute.
- Use the package on your projects.
- Report issues on Github.
- Send pull requests for bugfixes and improvements.
- Send proposals on Github issues.
---
## License
`gotext` is released under the [MIT License](LICENSE).
+1 -1
View File
@@ -55,7 +55,7 @@ func init() {
domain: "default",
languages: []string{FallbackLocale},
library: "/usr/local/share/locale",
locales: nil,
locales: make([]*Locale, 0),
}
// Register Translator types for gob encoding
+6 -1
View File
@@ -31,12 +31,17 @@ func SimplifiedLocale(lang string) string {
// FormatString applies text formatting only when needed to parse variables.
func FormatString(str string, vars ...interface{}) string {
if len(vars) > 0 {
return fmt.Sprintf(str, vars...)
return FormatStringWithArgs(str, vars)
}
return str
}
// FormatStringWithArgs applies text formatting as a proxy for fmt.Sprintf.
func FormatStringWithArgs(str string, vars []interface{}) string {
return fmt.Sprintf(str, vars...)
}
// Appendf applies text formatting only when needed to parse variables.
func Appendf(b []byte, str string, vars ...interface{}) []byte {
if len(vars) > 0 {
+4 -4
View File
@@ -357,7 +357,7 @@ func (l *Locale) GetTranslations() map[string]*Translation {
// IsTranslated reports whether a string is translated
func (l *Locale) IsTranslated(str string) bool {
return l.IsTranslatedND(l.GetDomain(), str, 0)
return l.IsTranslatedND(l.GetDomain(), str, 1)
}
// IsTranslatedN reports whether a plural string is translated
@@ -367,7 +367,7 @@ func (l *Locale) IsTranslatedN(str string, n int) bool {
// IsTranslatedD reports whether a domain string is translated
func (l *Locale) IsTranslatedD(dom, str string) bool {
return l.IsTranslatedND(dom, str, 0)
return l.IsTranslatedND(dom, str, 1)
}
// IsTranslatedND reports whether a plural domain string is translated
@@ -387,7 +387,7 @@ func (l *Locale) IsTranslatedND(dom, str string, n int) bool {
// IsTranslatedC reports whether a context string is translated
func (l *Locale) IsTranslatedC(str, ctx string) bool {
return l.IsTranslatedNDC(l.GetDomain(), str, 0, ctx)
return l.IsTranslatedNDC(l.GetDomain(), str, 1, ctx)
}
// IsTranslatedNC reports whether a plural context string is translated
@@ -397,7 +397,7 @@ func (l *Locale) IsTranslatedNC(str string, n int, ctx string) bool {
// IsTranslatedDC reports whether a domain context string is translated
func (l *Locale) IsTranslatedDC(dom, str, ctx string) bool {
return l.IsTranslatedNDC(dom, str, 0, ctx)
return l.IsTranslatedNDC(dom, str, 1, ctx)
}
// IsTranslatedNDC reports whether a plural domain context string is translated
+39
View File
@@ -0,0 +1,39 @@
site_name: gotext
site_description: GNU gettext utilities for Go
site_author: Leonel Quinteros
repo_url: https://github.com/leonelquinteros/gotext
edit_uri: edit/master/docs/
theme:
name: material
palette:
primary: indigo
accent: indigo
features:
- navigation.tabs
- navigation.sections
- navigation.top
- search.suggest
- search.highlight
nav:
- Home: index.md
- Getting Started: GETTING_STARTED.md
- User Guides:
- Plural Forms: PLURALS.md
- xgotext CLI: xgotext.md
- Character Encoding: ENCODING.md
- Best Practices: BEST_PRACTICES.md
- API Reference: https://pkg.go.dev/github.com/leonelquinteros/gotext
- Contributing:
- Guidelines: CONTRIBUTING.md
- Code of Conduct: CODE_OF_CONDUCT.md
- License: LICENSE.md
markdown_extensions:
- admonition
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
+43 -18
View File
@@ -22,7 +22,7 @@ type match struct {
closePos int
}
var pat = regexp.MustCompile(`(\?|:|\|\||&&|==|!=|>=|>|<=|<|%|\d+|n)`)
var pat = regexp.MustCompile(`(\?|:|\|\||&&|==|!=|>=|>|<=|<|%|\d+|n|\(|\))`)
type testToken interface {
compile(tokens []string) (test test, err error)
@@ -161,29 +161,28 @@ func compileEquality(tokens []string, sep string, builder cmpTestBuilder) (test
if err != nil {
return test, err
}
if len(split.Left) == 1 && split.Left[0] == "n" {
if len(split.Right) != 1 {
return test, errors.New("test can only compare n to integers")
}
i, err := parseUint32(split.Right[0])
if contains(split.Left, "n") && !contains(split.Left, "%") && len(split.Left) == 1 {
i, err := parseUint32(strings.Join(split.Right, ""))
if err != nil {
// Try to compile it as a full expression if it's not a simple integer
// but for now we follow the existing pattern of expecting integers here
// unless it's a mod operation.
return test, err
}
return builder(i, false), nil
} else if len(split.Right) == 1 && split.Right[0] == "n" {
if len(split.Left) != 1 {
return test, errors.New("test can only compare n to integers")
}
i, err := parseUint32(split.Left[0])
} else if contains(split.Right, "n") && !contains(split.Right, "%") && len(split.Right) == 1 {
i, err := parseUint32(strings.Join(split.Left, ""))
if err != nil {
return test, err
}
return builder(i, true), nil
} else if contains(split.Left, "n") && contains(split.Left, "%") {
return subPipe(split.Left, split.Right, builder, false)
} else if contains(split.Right, "n") && contains(split.Right, "%") {
return subPipe(split.Right, split.Left, builder, true)
}
return test, errors.New("equality test must have 'n' as one of the two tests")
return test, errors.New("equality test must have 'n' as one of the two tests")
}
var eqToken eqStruct
@@ -353,7 +352,7 @@ func split(s string) <-chan string {
// Tokenizes a string into a list of strings, tokens grouped by parenthesis are
// not split! If the string starts with ( and ends in ), those are stripped.
func tokenize(s string) []string {
func tokenize(s string) ([]string, error) {
/*
TODO: Properly detect if the string starts with a ( and ends with a )
and that those two form a matching pair.
@@ -361,7 +360,7 @@ func tokenize(s string) []string {
Eg: (foo) -> true; (foo)(bar) -> false;
*/
if len(s) == 0 {
return []string{}
return []string{}, nil
}
if s[0] == '(' && s[len(s)-1] == ')' {
s = s[1 : len(s)-1]
@@ -372,15 +371,22 @@ func tokenize(s string) []string {
if chunk[0] == '(' && chunk[len(chunk)-1] == ')' {
ret = append(ret, chunk)
} else {
for _, token := range pat.FindAllStringSubmatch(chunk, -1) {
// Verify all characters in chunk are matched by our pattern
matches := pat.FindAllStringSubmatch(chunk, -1)
matchedLen := 0
for _, token := range matches {
ret = append(ret, token[0])
matchedLen += len(token[0])
}
if matchedLen != len(chunk) {
return nil, fmt.Errorf("invalid character in expression chunk: %s", chunk)
}
}
} else {
fmt.Printf("Empty chunk in string '%s'\n", s)
}
}
return ret
return ret, nil
}
// Compile a string containing a plural form expression to a Expression object.
@@ -406,7 +412,10 @@ func contains(haystack []string, needle string) bool {
// Compiles an expression (ternary or constant)
func compileExpression(s string) (expr Expression, err error) {
tokens := tokenize(s)
tokens, err := tokenize(s)
if err != nil {
return nil, err
}
if contains(tokens, "?") {
return ternaryToken.compile(tokens)
}
@@ -415,12 +424,28 @@ func compileExpression(s string) (expr Expression, err error) {
// Compiles a test (comparison)
func compileTest(s string) (test test, err error) {
tokens := tokenize(s)
tokens, err := tokenize(s)
if err != nil {
return nil, err
}
for _, tokenDef := range precedence {
if contains(tokens, tokenDef.op) {
return tokenDef.token.compile(tokens)
}
}
if contains(tokens, "%") {
m, err := compileMod(tokens)
if err != nil {
return nil, err
}
return pipe{
modifier: m,
action: equal{value: 0}, // default to testing for 0
}, nil
}
if len(tokens) == 1 && tokens[0] == "n" {
return equal{value: 1}, nil
}
return test, errors.New("cannot compile")
}
@@ -28,12 +28,14 @@ import (
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
"github.com/pkg/errors"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
@@ -123,20 +125,22 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token) // TODO(jfd): hardcoded metadata key. use PerRPCCredentials?
// create home directory
createHomeRes, err := s.CreateHome(ctx, &storageprovider.CreateHomeRequest{})
if err != nil {
log.Err(err).Msg("error calling CreateHome")
return &gateway.AuthenticateResponse{
Status: status.NewInternal(ctx, "error creating user home"),
}, nil
}
if res.User.GetId().GetType() != userpb.UserType_USER_TYPE_SERVICE && res.User.GetId().GetType() != userpb.UserType_USER_TYPE_LIGHTWEIGHT {
createHomeRes, err := s.CreateHome(ctx, &storageprovider.CreateHomeRequest{})
if err != nil {
log.Err(err).Msg("error calling CreateHome")
return &gateway.AuthenticateResponse{
Status: status.NewInternal(ctx, "error creating user home"),
}, nil
}
if createHomeRes.Status.Code != rpc.Code_CODE_OK && createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS {
err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway")
log.Err(err).Msg("error calling Createhome")
return &gateway.AuthenticateResponse{
Status: status.NewInternal(ctx, "error creating user home"),
}, nil
if createHomeRes.Status.Code != rpc.Code_CODE_OK && createHomeRes.Status.Code != rpc.Code_CODE_ALREADY_EXISTS {
err := status.NewErrorFromCode(createHomeRes.Status.Code, "gateway")
log.Err(err).Msg("error calling Createhome")
return &gateway.AuthenticateResponse{
Status: status.NewInternal(ctx, "error creating user home"),
}, nil
}
}
gwRes := &gateway.AuthenticateResponse{
@@ -28,20 +28,17 @@ import (
"strings"
"time"
"github.com/BurntSushi/toml"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
linkv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/conversions"
"github.com/rs/zerolog/log"
"google.golang.org/grpc/codes"
"github.com/golang-jwt/jwt/v5"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/conversions"
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/publicshare"
@@ -51,7 +48,12 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/share"
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/BurntSushi/toml"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
)
@@ -114,6 +116,19 @@ func (s *svc) CreateHome(ctx context.Context, req *provider.CreateHomeRequest) (
}, nil
}
if u.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE {
return &provider.CreateHomeResponse{
Status: status.NewInvalid(ctx, "gateway: refusing to create home for service user"),
}, nil
}
if u.GetId().GetType() == userpb.UserType_USER_TYPE_LIGHTWEIGHT {
return &provider.CreateHomeResponse{
Status: status.NewInvalid(ctx, "gateway: refusing to create home for lightweight user"),
}, nil
}
quotaStr := utils.ReadPlainFromOpaque(req.Opaque, "quota")
var quota *provider.Quota
if quotaStr != "" {
@@ -1045,7 +1045,16 @@ func (s *Service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRe
}
func (s *Service) ListGrants(ctx context.Context, req *provider.ListGrantsRequest) (*provider.ListGrantsResponse, error) {
grants, err := s.Storage.ListGrants(ctx, req.Ref)
fs := s.Storage
if req.Opaque != nil && req.Opaque.Map != nil {
if _, ok := req.Opaque.Map["with_disabled_spaces"]; ok {
if dsfs, ok := fs.(storage.SpaceDisabledListableFS); ok {
fs = dsfs.WithDisabledSpaces()
}
}
}
grants, err := fs.ListGrants(ctx, req.Ref)
if err != nil {
var st *rpc.Status
switch err.(type) {
@@ -157,7 +157,7 @@ func (s *svc) Close() error {
}
func (s *svc) Unprotected() []string {
return []string{"/status.php", "/status", "/remote.php/dav/public-files/", "/apps/files/", "/index.php/f/", "/index.php/s/", "/remote.php/dav/ocm/", "/dav/ocm/"}
return []string{"/status.php", "/status", "/remote.php/dav/public-files/", "/dav/public-files/", "/apps/files/", "/index.php/f/", "/index.php/s/", "/remote.php/dav/ocm/", "/dav/ocm/"}
}
func (s *svc) Handler() http.Handler {
@@ -79,7 +79,7 @@ func (m *ImportSpaceMembersMigration) Name() string {
}
func (m *ImportSpaceMembersMigration) Version() int {
return 1
return 2
}
func (m *ImportSpaceMembersMigration) Migrate() error {
@@ -162,7 +162,10 @@ func (m *ImportSpaceMembersMigration) migrateSpace(ctx context.Context, space *p
}
ref := &provider.Reference{ResourceId: space.GetRoot()}
grantsRes, err := spClient.ListGrants(ctx, &provider.ListGrantsRequest{Ref: ref})
grantsRes, err := spClient.ListGrants(ctx, &provider.ListGrantsRequest{
Ref: ref,
Opaque: utils.AppendPlainToOpaque(nil, "with_disabled_spaces", "true"),
})
if err != nil {
return 0, err
}
@@ -0,0 +1,84 @@
package ignore
import (
"path/filepath"
"strings"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/templates"
"github.com/rs/zerolog"
)
// Ignorer handles checking if paths should be ignored in posix operations
type Ignorer struct {
options *options.Options
log *zerolog.Logger
personalSpacesRoot string
projectSpacesRoot string
}
// NewIgnorer creates a new Ignorer given the posix options and logger
func NewIgnorer(options *options.Options, log *zerolog.Logger) *Ignorer {
return &Ignorer{
options: options,
log: log,
personalSpacesRoot: filepath.Clean(filepath.Join(options.Root, templates.Base(options.PersonalSpacePathTemplate))),
projectSpacesRoot: filepath.Clean(filepath.Join(options.Root, templates.Base(options.GeneralSpacePathTemplate))),
}
}
// IsIgnored checking if paths should be ignored in posix operations
func (i *Ignorer) IsIgnored(path string) bool {
return IsLockFile(path) || IsTrash(path) || i.IsUpload(path) || i.IsInternal(path) || i.IsRootPath(path) || i.IsSpaceRoot(path)
}
func (i *Ignorer) IsUpload(path string) bool {
return strings.HasPrefix(path, i.options.UploadDirectory)
}
func (i *Ignorer) IsIndex(path string) bool {
return strings.HasPrefix(path, filepath.Join(i.options.Root, "indexes"))
}
func (i *Ignorer) IsTemporary(path string) bool {
if filepath.IsAbs(path) {
tmpDirPattern := filepath.Join(i.options.Root, "*", "*", blobstore.TMPDir)
isTempDir, err := filepath.Match(tmpDirPattern, path)
if err != nil {
i.log.Error().Err(err).Str("pattern", tmpDirPattern).Str("path", path).Msg("error matching temporary path")
return false
}
isTempParentDir, err := filepath.Match(tmpDirPattern, filepath.Dir(path))
if err != nil {
i.log.Error().Err(err).Str("pattern", tmpDirPattern).Str("path", filepath.Dir(path)).Msg("error matching temporary path")
return false
}
return isTempDir || isTempParentDir
}
return path == blobstore.TMPDir || filepath.Dir(path) == blobstore.TMPDir
}
func (i *Ignorer) IsRootPath(path string) bool {
return path == i.options.Root ||
path == i.personalSpacesRoot ||
path == i.projectSpacesRoot
}
func (i *Ignorer) IsSpaceRoot(path string) bool {
parent := filepath.Dir(path)
return parent == i.personalSpacesRoot || parent == i.projectSpacesRoot
}
func (i *Ignorer) IsInternal(path string) bool {
return i.IsIndex(path) || strings.Contains(path, lookup.MetadataDir) || i.IsTemporary(path)
}
func IsLockFile(path string) bool {
return strings.HasSuffix(path, ".flock") || strings.HasSuffix(path, ".mlock")
}
func IsTrash(path string) bool {
return strings.HasSuffix(path, ".trashinfo") || strings.HasSuffix(path, ".trashitem") || strings.Contains(path, ".Trash")
}
@@ -81,6 +81,8 @@ type Lookup struct {
userMapper usermapper.Mapper
tm node.TimeManager
log *zerolog.Logger
listDisabledSpaces bool
}
// New returns a new Lookup instance
@@ -186,6 +188,13 @@ func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference)
return nil, fmt.Errorf("invalid reference %+v. resource_id must be set", ref)
}
// WithDisabledSpaces returns a lookup that can list disabled spaces
func (lu *Lookup) WithDisabledSpaces() node.PathLookup {
l := *lu
l.listDisabledSpaces = true
return &l
}
// NodeFromID returns the internal path for the id
func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) {
ctx, span := tracer.Start(ctx, "NodeFromID")
@@ -197,7 +206,7 @@ func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *n
// The Resource references the root of a space
return lu.NodeFromSpaceID(ctx, id.SpaceId)
}
return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, "", false, nil, false)
return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, "", lu.listDisabledSpaces, nil, false)
}
// Pathify segments the beginning of a string into depth segments of width length
@@ -218,7 +227,7 @@ func Pathify(id string, depth, width int) string {
// NodeFromSpaceID converts a resource id into a Node
func (lu *Lookup) NodeFromSpaceID(ctx context.Context, spaceID string) (n *node.Node, err error) {
node, err := node.ReadNode(ctx, lu, spaceID, spaceID, "", false, nil, false)
node, err := node.ReadNode(ctx, lu, spaceID, spaceID, "", lu.listDisabledSpaces, nil, false)
if err != nil {
return nil, err
}
+6
View File
@@ -225,6 +225,12 @@ func New(o *options.Options, stream events.Stream, cache, historyCache *idcache.
return fs, nil
}
func (fs *posixFS) WithDisabledSpaces() storage.FS {
f := *fs
f.FS = fs.FS.(storage.SpaceDisabledListableFS).WithDisabledSpaces()
return &f
}
// WarmupIDCache allows triggering a posix fs scan and id cache warmup manually.
func (fs *posixFS) WarmupIDCache(root string, assimilate, onlyDirty bool) error {
return fs.tree.WarmupIDCache(root, assimilate, onlyDirty)
@@ -41,6 +41,7 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/watcher"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
@@ -368,7 +369,7 @@ func (t *Tree) findSpaceId(path string) (string, error) {
spaceCandidate := path
for strings.HasPrefix(spaceCandidate, t.options.Root) {
// jail at root
if t.isRootPath(spaceCandidate) {
if t.Ignorer.IsRootPath(spaceCandidate) {
return "", ErrRootReached
}
@@ -709,7 +710,7 @@ assimilate:
// The Space's name attribute might not match the directory name. Use the name as
// it was set before. Also the space root doesn't have a 'type' attribute
// currently so only set it for normal directories.
if t.isSpaceRoot(path) {
if t.Ignorer.IsSpaceRoot(path) {
if previousAttribs != nil && previousAttribs[prefixes.NameAttr] != nil {
attributes[prefixes.NameAttr] = previousAttribs[prefixes.NameAttr]
}
@@ -866,17 +867,17 @@ func (t *Tree) WarmupIDCache(root string, assimilate, onlyDirty bool) error {
}
// skip irrelevant files
if t.isInternal(path) ||
isLockFile(path) ||
isTrash(path) ||
t.isUpload(path) ||
t.isIndex(path) {
if t.Ignorer.IsInternal(path) ||
ignore.IsLockFile(path) ||
ignore.IsTrash(path) ||
t.Ignorer.IsUpload(path) ||
t.Ignorer.IsIndex(path) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
if t.isRootPath(path) {
if t.Ignorer.IsRootPath(path) {
return nil // ignore the root paths
}
@@ -982,7 +983,7 @@ func (t *Tree) WarmupIDCache(root string, assimilate, onlyDirty bool) error {
})
for dir, size := range sizes {
if t.isRootPath(dir) {
if t.Ignorer.IsRootPath(dir) {
continue
}
spaceID, id, err := t.lookup.IDsForPath(context.Background(), dir)
@@ -84,7 +84,7 @@ func (w *CephFSWatcher) Watch(topic string) {
continue
}
if w.tree.isIgnored(ev.Path) {
if w.tree.Ignorer.IsIgnored(ev.Path) {
continue
}
@@ -83,7 +83,7 @@ start:
w.log.Error().Err(err).Str("line", line).Msg("error unmarshalling line")
continue
}
if w.tree.isIgnored(ev.Path) {
if w.tree.Ignorer.IsIgnored(ev.Path) {
continue
}
go func() {
@@ -66,7 +66,7 @@ func (w *GpfsWatchFolderWatcher) Watch(topic string) {
continue
}
if w.tree.isIgnored(lwev.Path) {
if w.tree.Ignorer.IsIgnored(lwev.Path) {
continue
}
+7 -63
View File
@@ -41,8 +41,8 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/idcache"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/trashbin"
@@ -54,7 +54,6 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/permissions"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree/propagator"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/usermapper"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/templates"
"github.com/opencloud-eu/reva/v2/pkg/utils"
)
@@ -93,9 +92,8 @@ type Tree struct {
idResolver IDResolver // points at the lookup but can be overridden for testing
assimilateFunc func(item scanItem) error // function to call to assimilate a node, can be overridden for testing
options *options.Options
personalSpacesRoot string
projectSpacesRoot string
options *options.Options
Ignorer *ignore.Ignorer
userMapper usermapper.Mapper
idCache *idcache.IDCache
@@ -128,10 +126,9 @@ func New(lu node.PathLookup, bs node.Blobstore, um usermapper.Mapper, trashbin *
scanDebouncer: NewScanDebouncer(o.ScanDebounceDelay, func(item scanItem) {
scanQueue <- item
}),
es: es,
log: log,
personalSpacesRoot: filepath.Clean(filepath.Join(o.Root, templates.Base(o.PersonalSpacePathTemplate))),
projectSpacesRoot: filepath.Clean(filepath.Join(o.Root, templates.Base(o.GeneralSpacePathTemplate))),
es: es,
log: log,
Ignorer: ignore.NewIgnorer(o, log),
}
t.idResolver = t.lookup
t.assimilateFunc = t.assimilate
@@ -499,7 +496,7 @@ func (t *Tree) ListFolder(ctx context.Context, n *node.Node) ([]*node.Node, erro
g.Go(func() error {
defer close(work)
for _, name := range names {
if t.isInternal(name) || isLockFile(name) || isTrash(name) {
if t.Ignorer.IsInternal(name) || ignore.IsLockFile(name) || ignore.IsTrash(name) {
continue
}
@@ -776,56 +773,3 @@ func (t *Tree) createDirNode(ctx context.Context, n *node.Node) (err error) {
}
return n.SetXattrsWithContext(ctx, attributes, false)
}
func (t *Tree) isIgnored(path string) bool {
return isLockFile(path) || isTrash(path) || t.isUpload(path) || t.isInternal(path) || t.isRootPath(path) || t.isSpaceRoot(path)
}
func (t *Tree) isUpload(path string) bool {
return strings.HasPrefix(path, t.options.UploadDirectory)
}
func (t *Tree) isIndex(path string) bool {
return strings.HasPrefix(path, filepath.Join(t.options.Root, "indexes"))
}
func (t *Tree) isTemporary(path string) bool {
if filepath.IsAbs(path) {
tmpDirPattern := filepath.Join(t.options.Root, "*", "*", blobstore.TMPDir)
isTempDir, err := filepath.Match(tmpDirPattern, path)
if err != nil {
t.log.Error().Err(err).Str("pattern", tmpDirPattern).Str("path", path).Msg("error matching temporary path")
return false
}
isTempParentDir, err := filepath.Match(tmpDirPattern, filepath.Dir(path))
if err != nil {
t.log.Error().Err(err).Str("pattern", tmpDirPattern).Str("path", filepath.Dir(path)).Msg("error matching temporary path")
return false
}
return isTempDir || isTempParentDir
}
return path == blobstore.TMPDir || filepath.Dir(path) == blobstore.TMPDir
}
func (t *Tree) isRootPath(path string) bool {
return path == t.options.Root ||
path == t.personalSpacesRoot ||
path == t.projectSpacesRoot
}
func (t *Tree) isSpaceRoot(path string) bool {
parent := filepath.Dir(path)
return parent == t.personalSpacesRoot || parent == t.projectSpacesRoot
}
func (t *Tree) isInternal(path string) bool {
return t.isIndex(path) || strings.Contains(path, lookup.MetadataDir) || t.isTemporary(path)
}
func isLockFile(path string) bool {
return strings.HasSuffix(path, ".flock") || strings.HasSuffix(path, ".mlock")
}
func isTrash(path string) bool {
return strings.HasSuffix(path, ".trashinfo") || strings.HasSuffix(path, ".trashitem") || strings.Contains(path, ".Trash")
}
@@ -12,6 +12,7 @@ import (
"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/watcher"
)
@@ -212,8 +213,8 @@ func isSubpath(root, p string) bool {
// isIgnored checks if the path is ignored by its tree.
func isPathIgnored(tree *Tree, path string) bool {
isLockFile := isLockFile(path)
isTrash := isTrash(path)
isLockFile := ignore.IsLockFile(path)
isTrash := ignore.IsTrash(path)
isUpload := tree.isUpload(path)
isInternal := tree.isInternal(path)
@@ -90,7 +90,7 @@ func (iw *InotifyWatcher) Watch(path string) {
for {
select {
case event := <-events:
if iw.tree.isIgnored(event.Filename) {
if iw.tree.Ignorer.IsIgnored(event.Filename) {
continue
}
for _, e := range event.Events {
@@ -568,6 +568,13 @@ func (fs *Decomposedfs) Postprocessing(ch <-chan events.Event) {
}
}
// WithDisabledSpaces returns a filesystem that can list disabled spaces
func (fs *Decomposedfs) WithDisabledSpaces() storage.FS {
f := *fs
f.lu = f.lu.WithDisabledSpaces()
return &f
}
// Shutdown shuts down the storage
func (fs *Decomposedfs) Shutdown(ctx context.Context) error {
return nil
@@ -57,6 +57,8 @@ type Lookup struct {
metadataBackend metadata.Backend
tm node.TimeManager
listDisabledSpaces bool
}
// New returns a new Lookup instance
@@ -140,6 +142,13 @@ func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference)
return nil, fmt.Errorf("invalid reference %+v. resource_id must be set", ref)
}
// WithDisabledSpaces returns a lookup that can list disabled spaces
func (lu *Lookup) WithDisabledSpaces() node.PathLookup {
l := *lu
l.listDisabledSpaces = true
return &l
}
// NodeFromID returns the internal path for the id
func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) {
ctx, span := tracer.Start(ctx, "NodeFromID")
@@ -151,7 +160,7 @@ func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *n
// The Resource references the root of a space
return lu.NodeFromSpaceID(ctx, id.SpaceId)
}
return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, "", false, nil, false)
return node.ReadNode(ctx, lu, id.SpaceId, id.OpaqueId, "", lu.listDisabledSpaces, nil, false)
}
// Pathify segments the beginning of a string into depth segments of width length
@@ -172,7 +181,7 @@ func Pathify(id string, depth, width int) string {
// NodeFromSpaceID converts a resource id into a Node
func (lu *Lookup) NodeFromSpaceID(ctx context.Context, spaceID string) (n *node.Node, err error) {
node, err := node.ReadNode(ctx, lu, spaceID, spaceID, "", false, nil, false)
node, err := node.ReadNode(ctx, lu, spaceID, spaceID, "", lu.listDisabledSpaces, nil, false)
if err != nil {
return nil, err
}
@@ -148,6 +148,7 @@ type Tree interface {
// PathLookup defines the interface for the lookup component
type PathLookup interface {
WithDisabledSpaces() PathLookup
NodeFromSpaceID(ctx context.Context, spaceID string) (n *Node, err error)
NodeFromResource(ctx context.Context, ref *provider.Reference) (*Node, error)
NodeFromID(ctx context.Context, id *provider.ResourceId) (n *Node, err error)
+6
View File
@@ -152,6 +152,12 @@ type FS interface {
GetHome(ctx context.Context) (string, error)
}
// SpaceDisabledListableFS is the interface to implement for storage drivers that
// can list resources in disabled spaces.
type SpaceDisabledListableFS interface {
WithDisabledSpaces() FS
}
// UnscopeFunc is a function that unscopes a user
type UnscopeFunc func()
@@ -51,6 +51,13 @@ func NewFS(next storage.FS, hooks ...Hook) *FS {
}
}
// WithDisabledSpaces returns a filesystem that can list disabled spaces
func (fs *FS) WithDisabledSpaces() storage.FS {
f := *fs
f.next = fs.next.(storage.SpaceDisabledListableFS).WithDisabledSpaces()
return &f
}
// ListUploadSessions returns the upload sessions matching the given filter
func (f *FS) ListUploadSessions(ctx context.Context, filter storage.UploadSessionFilter) ([]storage.UploadSession, error) {
return f.next.(storage.UploadSessionLister).ListUploadSessions(ctx, filter)
+12 -1
View File
@@ -8,6 +8,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
## [0.12.3] - 2026-05-03
### Added
- Add OpenTelemetry semantic-convention compliant HTTP server metric middleware for `http.server.request.duration`, `http.server.active_requests`, `http.server.request.body.size`, and `http.server.response.body.size`.
### Deprecated
- Deprecate legacy metric middleware for `request_duration_millis`, `requests_inflight`, and `response_size_bytes`. These remain available for backward compatibility.
## [0.12.2] - 2025-09-02
### Fixed
@@ -269,7 +279,8 @@ It contains instrumentation for trace and depends on:
[#2]: https://github.com/riandyrn/otelchi/pull/2
[#1]: https://github.com/riandyrn/otelchi/pull/1
[Unreleased]: https://github.com/riandyrn/otelchi/compare/v0.12.2...HEAD
[Unreleased]: https://github.com/riandyrn/otelchi/compare/v0.12.3...HEAD
[0.12.3]: https://github.com/riandyrn/otelchi/releases/tag/v0.12.3
[0.12.2]: https://github.com/riandyrn/otelchi/releases/tag/v0.12.2
[0.12.1]: https://github.com/riandyrn/otelchi/releases/tag/v0.12.1
[0.12.0]: https://github.com/riandyrn/otelchi/releases/tag/v0.12.0
+11
View File
@@ -22,6 +22,17 @@ $ go get github.com/riandyrn/otelchi
See [examples](./examples) for details.
## Metrics
The `metric` package provides OpenTelemetry semantic-convention compliant HTTP server metric middleware:
- `http.server.request.duration`
- `http.server.active_requests`
- `http.server.request.body.size`
- `http.server.response.body.size`
Legacy metric middleware for `request_duration_millis`, `requests_inflight`, and `response_size_bytes` is still available but deprecated.
## Why Port This?
I was planning to make this project as part of the Open Telemetry Go instrumentation project. However, based on [this comment](https://github.com/open-telemetry/opentelemetry-go-contrib/pull/986#issuecomment-941280855) they no longer accept new instrumentation. This is why I maintain this project here.
+1 -1
View File
@@ -2,5 +2,5 @@ package version
// Version is the current release version of otelchi in use.
func Version() string {
return "0.12.2"
return "0.12.3"
}
@@ -47,7 +47,6 @@ type config struct {
TracerProvider trace.TracerProvider
MeterProvider metric.MeterProvider
SpanKind trace.SpanKind
SpanStartOptions []trace.SpanStartOption
SpanAttributes []attribute.KeyValue
MetricAttributes []attribute.KeyValue
MetricAttributesFn func(ctx context.Context) []attribute.KeyValue
@@ -204,16 +203,6 @@ func WithMessageEvents(events ...Event) Option {
})
}
// WithSpanOptions configures an additional set of
// trace.SpanOptions, which are applied to each new span.
//
// Deprecated: It is only used by the deprecated interceptor, and is unused by [NewClientHandler] and [NewServerHandler].
func WithSpanOptions(opts ...trace.SpanStartOption) Option {
return optionFunc(func(c *config) {
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
})
}
// WithSpanKind returns an Option to set the span kind for spans created by
// the handler.
//
@@ -11,7 +11,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
grpc_codes "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -9,7 +9,7 @@ import (
"go.opentelemetry.io/otel/attribute"
oldsemconv "go.opentelemetry.io/otel/semconv/v1.37.0" //nolint:depguard // Use of v1.37.0 is required for backward compatibility stability opt-in.
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
)
// ParseFullMethod returns a span name following the OpenTelemetry semantic
@@ -13,8 +13,8 @@ import (
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
oldrpcconv "go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv" //nolint:depguard // Use of v1.37.0 is required for backward compatibility stability opt-in.
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/rpcconv"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
"go.opentelemetry.io/otel/semconv/v1.41.0/rpcconv"
"go.opentelemetry.io/otel/trace"
grpc_codes "google.golang.org/grpc/codes"
@@ -367,27 +367,39 @@ func (*config) handleRPC(
span.End()
}
var metricAttrs []attribute.KeyValue
if gctx != nil {
// Don't use gctx.metricAttrSet here, because it requires passing
// multiple RecordOptions, which would call metric.mergeSets and
// allocate a new set for each Record call.
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
}
metricAttrs = append(metricAttrs, rpcStatusAttr)
// Allocate vararg slice once.
recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))}
// Use floating point division here for higher precision (instead of Millisecond method).
// Measure right before calling Record() to capture as much elapsed time as possible.
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Second)
var durationEnabled bool
var oldDurationEnabled bool
if duration != nil {
duration.Record(ctx, elapsedTime, recordOpts...)
durationEnabled = duration.Enabled(ctx)
}
if oldDuration != nil {
oldDuration.Record(ctx, elapsedTime*1000.0, recordOpts...)
oldDurationEnabled = oldDuration.Enabled(ctx)
}
if durationEnabled || oldDurationEnabled {
var metricAttrs []attribute.KeyValue
if gctx != nil {
// Don't use gctx.metricAttrSet here, because it requires passing
// multiple RecordOptions, which would call metric.mergeSets and
// allocate a new set for each Record call.
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
}
metricAttrs = append(metricAttrs, rpcStatusAttr)
// Allocate vararg slice once.
recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))}
// Use floating point division here for higher precision (instead of Millisecond method).
// Measure right before calling Record() to capture as much elapsed time as possible.
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Second)
if durationEnabled {
duration.Record(ctx, elapsedTime, recordOpts...)
}
if oldDurationEnabled {
oldDuration.Record(ctx, elapsedTime*1000.0, recordOpts...)
}
}
default:
@@ -4,4 +4,4 @@
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
// Version is the current release version of the gRPC instrumentation.
const Version = "0.68.0"
const Version = "0.69.0"
+11 -7
View File
@@ -96,9 +96,9 @@ linters:
- "!**/exporters/zipkin/**"
deny:
- pkg: go.opentelemetry.io/otel/semconv
desc: "Use go.opentelemetry.io/otel/semconv/v1.40.0 instead. If a newer semconv version has been released, update the depguard rule."
desc: "Use go.opentelemetry.io/otel/semconv/v1.41.0 instead. If a newer semconv version has been released, update the depguard rule."
allow:
- go.opentelemetry.io/otel/semconv/v1.40.0
- go.opentelemetry.io/otel/semconv/v1.41.0
gocritic:
disabled-checks:
- appendAssign
@@ -134,13 +134,16 @@ linters:
strconcat: true
revive:
confidence: 0.01
enable-all-rules: false
enable-default-rules: true
max-open-files: 2048
rules:
- name: blank-imports
- name: bool-literal-in-expr
- name: constant-logical-expr
- name: context-as-argument
arguments:
- allowTypesBefore: '*testing.T'
- allow-types-before: '*testing.T'
disabled: true
- name: context-keys-type
- name: deep-exit
@@ -152,7 +155,7 @@ linters:
- name: duplicated-imports
- name: early-return
arguments:
- preserveScope
- preserve-scope
- name: empty-block
- name: empty-lines
- name: error-naming
@@ -161,7 +164,7 @@ linters:
- name: errorf
- name: exported
arguments:
- sayRepetitiveInsteadOfStutters
- say-repetitive-instead-of-stutters
- name: flag-parameter
- name: identical-branches
- name: if-return
@@ -169,11 +172,12 @@ linters:
- name: increment-decrement
- name: indent-error-flow
arguments:
- preserveScope
- preserve-scope
- name: package-comments
- name: range
- name: range-val-in-closure
- name: range-val-address
- name: receiver-naming
- name: redefines-builtin-id
- name: string-format
arguments:
@@ -183,7 +187,7 @@ linters:
- name: struct-tag
- name: superfluous-else
arguments:
- preserveScope
- preserve-scope
- name: time-equal
- name: unconditional-recursion
- name: unexported-return
+109
View File
@@ -0,0 +1,109 @@
# Agent Guide for opentelemetry-go
This file contains active, task-oriented instructions for autonomous and semi-autonomous coding agents working in this repository.
Before starting any task, read `.github/copilot-instructions.md`, `CONTRIBUTING.md`, and this file.
Treat `.github/copilot-instructions.md` as global passive guidance for every task, including docs-only and review-only work.
## Core expectations
- Preserve OpenTelemetry specification compliance, API stability, and idiomatic Go.
- Prefer minimal, surgical changes over broad refactors or speculative cleanup.
- Read the package you are editing and match its existing naming, option types, error handling, comments, tests, and concurrency patterns.
- Keep public APIs backward compatible unless the task explicitly requires a breaking change.
- Keep telemetry resilient and loosely coupled. Do not introduce behavior that can unexpectedly interfere with host applications.
- Inspect boundaries carefully: input validation, resource limits, cancellation, shutdown, error propagation, concurrency, and memory growth.
- Prefer fail-safe behavior and explicit invariants over implicit assumptions.
- Keep dependencies minimal and justified.
- Preserve host-application safety: telemetry should not panic, block indefinitely, or amplify attacker-controlled input.
- Be conservative on hot paths. Avoid unnecessary allocations, reflection, interface churn, blocking, global state, and high-cardinality telemetry.
- Write comments only for intent, invariants, and non-obvious constraints. Do not add comments that restate the code.
## Default workflow
For new features and behavior changes, use this order unless the task explicitly says otherwise:
1. Read the relevant package, its tests, and any package docs or `README.md`.
2. Add or update a failing unit test that captures the required behavior or regression.
3. Implement the smallest change that makes the test pass.
4. Refactor only after the behavior is locked in, and only if the refactor keeps the diff focused.
5. If the changed code is on a hot path or performance-sensitive, inspect existing benchmarks and run them. Add a benchmark if coverage is missing.
6. Update documentation artifacts as needed while the context is fresh. Follow the documentation and changelog conventions below for the specific updates required.
7. Run `make precommit` each time before considering the work complete.
For docs-only, test-only, or review-only tasks, still start with the required repository guidance above, then skip the workflow steps that do not apply while keeping the same discipline around scope, verification, and repository conventions.
## Verification
- Use `make` as the canonical repository verification command. The default target is `precommit`.
- `make precommit` is the expected final verification step for linting, generation, README checks, module checks, and tests.
- During iteration, targeted commands are fine for fast feedback, but do not stop there if the task changes code.
- If you touch performance-sensitive code, run focused benchmarks and compare the results using `benchstat` in addition to `make`.
## Documentation and changelog
- Non-internal, non-test packages should have Go doc comments, usually in `doc.go`.
- Non-internal, non-test, non-documentation packages should also have a `README.md` with at least a title and a `pkg.go.dev` badge.
- Prefer examples over long code snippets in GoDoc when practical.
- Keep docs aligned with actual behavior. Do not leave stale comments, stale examples, or stale package documentation behind.
- For user-visible changes, update `CHANGELOG.md` under the appropriate `Added`, `Changed`, `Deprecated`, `Fixed`, or `Removed` section within `## [Unreleased]`.
## Repository habits
- Prefer focused diffs. Avoid drive-by cleanup.
- Follow existing option patterns and exported API conventions instead of inventing new abstractions.
- Generated files are checked in. If your change affects generation, keep generated output up to date.
- Prefer fast local search tools such as `rg` when exploring the repository.
- When changing behavior, make the invariants explicit in tests.
## Personas
### Feature Agent
Use this persona for new behavior, new API surface, or spec-driven feature work.
- Start with a failing unit test.
- Confirm the expected behavior against the spec, existing package behavior, and public API compatibility.
- Implement the smallest viable change.
- Update GoDoc, examples, `README.md`, and `CHANGELOG.md` when the change is user-visible.
- If the feature touches a hot path, check benchmarks and add one if the coverage is missing.
### Refactoring Agent
Use this persona when improving structure without intentionally changing behavior.
- Treat behavior preservation as the default contract.
- Add or tighten tests before moving code if current behavior is not already pinned down.
- Avoid broad rewrites, clever abstractions, or package-wide cleanup unless explicitly requested.
- If a refactor touches a hot path, benchmark before and after.
- Keep API shape, semantics, concurrency guarantees, and failure modes unchanged unless the task says otherwise.
### Test Agent
Use this persona when adding missing coverage, reproducing bugs, or hardening regressions.
- Reproduce the bug or missing behavior with the smallest failing test you can.
- Prefer testing public behavior and externally visible invariants.
- Add targeted regression tests before changing production code.
- Only change production code when it is required to make the tested behavior correct or testable.
- Keep tests deterministic, readable, and aligned with package patterns.
### Performance Agent
Use this persona for hot-path work, allocation reduction, or throughput and latency improvements.
- Benchmark first to establish a baseline.
- Prefer changes that reduce allocations, copying, interface churn, and unnecessary synchronization.
- Do not trade away correctness, spec compliance, or API stability for micro-optimizations.
- Add or update benchmarks when performance-sensitive coverage is missing.
- If you materially change a hot path, capture before-and-after results, preferably with `benchstat`.
### Review Agent
Use this persona when asked to review code, patches, or pull requests.
- Lead with findings, not summaries.
- Order findings by severity and include precise file and line references when available.
- Focus on correctness, spec compliance, API compatibility, concurrency safety, resilience, performance regressions, missing tests, missing benchmarks, documentation gaps, and changelog gaps.
- Call out when a diff is broader than necessary.
- If you find no issues, say that explicitly and note any residual risks or verification gaps.
+96 -1
View File
@@ -11,6 +11,100 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
<!-- Released section -->
<!-- Don't change this section unless doing release -->
## [1.44.0/0.66.0/0.20.0/0.0.17] 2026-05-27
### Added
- Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948)
- Apply attribute value limit to the `KindBytes` attribute type in `go.opentelemetry.io/otel/sdk/log`. (#7990)
- Apply attribute value limit to the `BYTESLICE` attribute type in `go.opentelemetry.io/otel/sdk/trace`. (#7990)
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/trace`. (#8153)
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8153)
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8153)
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8153)
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8153)
- Add `String` method for `Value` type in `go.opentelemetry.io/otel/attribute`. (#8142)
- Add `Slice` and `SliceValue` functions for new `SLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#8166)
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8216)
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8216)
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8216)
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8216)
- Apply `AttributeValueLengthLimit` to `attribute.SLICE` type attribute values in `go.opentelemetry.io/otel/sdk/trace`, recursively truncating contained string values. (#8217)
- Add `Error` field on `Record` type in `go.opentelemetry.io/otel/log/logtest`. (#8148)
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`. (#8157)
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#8157)
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#8157)
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8157)
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`. (#8157)
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8157)
- Add `Settable` to `go.opentelemetry.io/otel/metric/x` to allow reusing attribute options. (#8178)
- Add experimental support for splitting metric data across multiple batches in `go.opentelemetry.io/otel/sdk/metric`.
Set `OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE=<max_size>` to enable for all periodic readers.
See `go.opentelemetry.io/otel/sdk/metric/internal/x` for feature documentation. (#8071)
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x` for feature documentation. (#8192)
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/x` for feature documentation. (#8194)
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`.
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
See `go.opentelemetry.io/otel/stdout/stdoutlog/internal/x` for feature documentation. (#8263)
- Add `WithDefaultAttributes` to `go.opentelemetry.io/otel/metric/x` to support setting default attributes on instruments. (#8135)
- Add `go.opentelemetry.io/otel/semconv/v1.41.0` package.
The package contains semantic conventions from the `v1.41.0` version of the OpenTelemetry Semantic Conventions.
See the [migration documentation](./semconv/v1.41.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.40.0`. (#8324)
- Add Observable variants of instruments to `go.opentelemetry.io/otel/semconv/v1.41.0` package. (#8350)
- Generate explicit histogram bucket boundaries from weaver configuration for HTTP and RPC duration instruments in `go.opentelemetry.io/otel/semconv/v1.41.0`. (#8002)
### Changed
- ⚠️ **Breaking Change:** `go.opentelemetry.io/otel/sdk/metric` now applies a default cardinality limit of 2000 to comply with the Metrics SDK specification recommendation.
New attribute sets are dropped when the cardinality limit is reached. The measurement of these sets are aggregated into a special attribute set containing `attribute.Bool("otel.metric.overflow", true)`.
This can break users who relied on the previous unlimited default.
Set `WithCardinalityLimit(0)` or the deprecated `OTEL_GO_X_CARDINALITY_LIMIT=0` environment variable to preserve unlimited cardinality.
Note that support for `OTEL_GO_X_CARDINALITY_LIMIT` may be removed in a future release. (#8247)
- `ErrorType` in `go.opentelemetry.io/otel/semconv` now unwraps errors created with `fmt.Errorf` when deriving the `error.type` attribute. (#8133)
- `go.opentelemetry.io/otel/sdk/log` now unwraps error chains created with `fmt.Errorf` when deriving the `error.type` attribute from errors on log records. (#8133)
- `Set.MarshalLog` method in `go.opentelemetry.io/otel/attribute` now uses `Value.String` formatting following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8169)
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir and short-circuit `Offer` calls to the exemplar reservoir when `exemplar.AlwaysOffFilter` is configured. (#8211) (#8267)
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir for asynchronous instruments when `exemplar.TraceBasedFilter` is configured. (#8286)
### Deprecated
- Deprecate `Value.Emit` method in `go.opentelemetry.io/otel/attribute`.
Use `Value.String` instead. (#8176)
### Fixed
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`.
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`.
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`.
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`.
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8135)
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8152)
- `go.opentelemetry.io/otel/exporters/prometheus` now uses `Value.String` formatting for label values following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8170)
- Propagate errors from the exporter when calling `Shutdown` on `BatchSpanProcessor` in `go.opentelemetry.io/otel/sdk/trace`. (#8197)
- Fix stale status code reporting on self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8226)
- Fix a concurrent `Collect` data race and potential panic in `go.opentelemetry.io/otel/exporters/prometheus` when `WithResourceAsConstantLabels` option is used. (#8227)
- Fix race condition in `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` by reverting #7447. (#8249)
- Fix `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` to safely handle zero size.
A capacity check in the constructor initializes the reservoir safely and skips initialization for zero-cap; early returns in `Offer()` and `Collect()` ensure no-op behavior. (#8295)
- Fix counting of spans and logs in self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`, and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8254)
- Drop conflicting scope attributes named `name`, `version`, or `schema_url` from metric labels in `go.opentelemetry.io/otel/exporters/prometheus`, preserving the dedicated `otel_scope_name`, `otel_scope_version`, and `otel_scope_schema_url` labels. (#8264)
- Close schema files opened by `ParseFile` in `go.opentelemetry.io/otel/schema/v1.0` and `go.opentelemetry.io/otel/schema/v1.1`. ([GHSA-995v-fvrw-c78m](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-995v-fvrw-c78m))
- Enforce the 8192-byte baggage size limit during extraction/parsing, changing behavior when the limit is exceeded in `go.opentelemetry.io/otel/baggage` and `go.opentelemetry.io/otel/propagation`. (#8222)
- Fix `go.opentelemetry.io/otel/semconv/v1.41.0` to include `Attr*` helper methods for required attributes on observable instruments. (#8361)
- Limit baggage extraction error reporting in `go.opentelemetry.io/otel/propagation` to prevent malformed or oversized baggage headers from flooding logs. ([GHSA-5wrp-cwcj-q835](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-5wrp-cwcj-q835))
## [1.43.0/0.65.0/0.19.0] 2026-04-02
### Added
@@ -3619,7 +3713,8 @@ It contains api and sdk for trace and meter.
- CircleCI build CI manifest files.
- CODEOWNERS file to track owners of this project.
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.43.0...HEAD
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...HEAD
[1.44.0/0.66.0/0.20.0/0.0.17]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.44.0
[1.43.0/0.65.0/0.19.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.43.0
[1.42.0/0.64.0/0.18.0/0.0.16]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.42.0
[1.41.0/0.63.0/0.17.0/0.0.15]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.41.0
+3
View File
@@ -0,0 +1,3 @@
# Instructions for Claude Code
@AGENTS.md
+89 -12
View File
@@ -11,6 +11,12 @@ for a summary description of past meetings. To request edit access,
join the meeting or get in touch on
[Slack](https://cloud-native.slack.com/archives/C01NPAXACKT).
The meeting is open for all to join. We invite everyone to join our
meeting, regardless of your experience level. Whether you're a
seasoned OpenTelemetry developer, just starting your journey, or
simply curious about the work we do, you're more than welcome to
participate!
## Development
You can view and edit the source code by cloning this repository:
@@ -746,8 +752,8 @@ Encapsulate setup in constructor functions, ensuring clear ownership and scope:
import (
"errors"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
)
type SDKComponent struct {
@@ -808,11 +814,11 @@ func (c *Component) initObservability() {
#### Performance
When observability is disabled there should be little to no overhead.
When observability is disabled or the instrument is not `Enabled`, there should be little to no overhead.
```go
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
if e.inst != nil {
if e.inst != nil && e.inst.Enabled(ctx) {
attrs := expensiveOperation()
e.inst.recordSpanInflight(ctx, int64(len(spans)), attrs...)
}
@@ -829,7 +835,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
}
func (i *instrumentation) recordSpanInflight(ctx context.Context, count int64, attrs ...attribute.KeyValue) {
if i == nil || i.inflight == nil {
if i == nil || i.inflight == nil || !i.inflight.Enabled(ctx) {
return
}
i.inflight.Add(ctx, count, metric.WithAttributes(attrs...))
@@ -865,8 +871,12 @@ var (
)
func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...attribute.KeyValue) {
if !i.counter.Enabled(ctx) {
return
}
attrs := attrPool.Get().(*[]attribute.KeyValue)
defer func() {
clear(*attrs) // Clear references to strings/etc to let GC collect them.
*attrs = (*attrs)[:0] // Reset.
attrPool.Put(attrs)
}()
@@ -877,6 +887,7 @@ func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...
addOpt := addOptPool.Get().(*[]metric.AddOption)
defer func() {
clear(*addOpt)
*addOpt = (*addOpt)[:0]
addOptPool.Put(addOpt)
}()
@@ -1007,16 +1018,20 @@ Ensure observability measurements receive the correct context, especially for tr
```go
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
// Use the provided context for observability measurements
e.inst.recordSpanExportStarted(ctx, len(spans))
if e.inst.Enabled(ctx) {
e.inst.recordSpanExportStarted(ctx, len(spans))
}
err := e.doExport(ctx, spans)
if err != nil {
e.inst.recordSpanExportFailed(ctx, len(spans), err)
} else {
e.inst.recordSpanExportSucceeded(ctx, len(spans))
if e.inst.Enabled(ctx) {
if err != nil {
e.inst.recordSpanExportFailed(ctx, len(spans), err)
} else {
e.inst.recordSpanExportSucceeded(ctx, len(spans))
}
}
return err
}
```
@@ -1039,7 +1054,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
All observability metrics should follow the [OpenTelemetry Semantic Conventions for SDK metrics](https://github.com/open-telemetry/semantic-conventions/blob/1cf2476ae5e518225a766990a28a6d5602bd5a30/docs/otel/sdk-metrics.md).
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.40.0/otelconv/metric.go).
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.41.0/otelconv/metric.go).
##### Component Identification
@@ -1109,6 +1124,68 @@ func TestObservability(t *testing.T) {
Test order should not affect results.
Ensure that any global state (e.g. component ID counters) is reset between tests.
### Experimental Features
To support the development of new features in the specification, we use the following patterns to implement in-development features without adding new public artifacts in stable modules.
#### Experimental behavior with no API artifacts
Features that change behavior without changing the API (e.g., exemplar collection, auto-generation of identifiers) are implemented behind a feature gate.
The implementation resides in an `/internal/x` package and is activated through environment variables with the `OTEL_GO_X_` prefix (e.g., `OTEL_GO_X_OBSERVABILITY`).
The feature must be documented in a `README.md` file in the `/internal/x` package.
#### Experimental methods on SDK-only interfaces
Features that require new methods on SDK interfaces are defined as a new interface in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
The SDK must not depend on the experimental module.
#### Experimental structs, functions, or interfaces
Features that don't need any changes to the existing stable package are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
#### Experimental signals and components
New telemetry signals (e.g., Logs before stabilization) and components (e.g. bridges) are hosted in new, unstable modules (e.g., `go.opentelemetry.io/otel/log` before 1.0.0).
The package should have the final name it will use once stabilized (i.e. not `/x`), and is released at a v0.x.y version to indicate it is not stable.
Most new components are hosted in [opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib).
#### Experimental options for API or SDK functions
Experimental Options functions are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
The return type of the Option function must embed the option's type (e.g. `metric.InstrumentOption`), and have an `Experimental()` method to prevent the API from panicking when the option is used.
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
The SDK must not depend on the experimental module.
For example:
```go
type myOption struct {
// Embed the stable option type.
metric.InstrumentOption
value string
}
// Experimental prevents the API from panicking when the option is used.
func (o myOption) Experimental() {}
// The SDK can use type assertions to use this function.
func (o myOption) Value() string { return o.value }
func WithMyOption(value string) metric.InstrumentOption {
return myOption{value: value}
}
```
#### Not Supported
The following kinds of experimental features are **not currently supported** on stable interfaces:
- Experimental methods on API interfaces
- Experimental fields for API or SDK exported structs
In some cases forks or long-lived branches may be used for prototyping these features.
## Approvers and Maintainers
### Maintainers
+9 -1
View File
@@ -191,8 +191,16 @@ benchmark: $(OTEL_GO_MOD_DIRS:%=benchmark/%)
benchmark/%:
cd $* && $(GO) test -run='^$$' -bench=. $(ARGS) ./...
# sdk/metric is split into two shards to work around CodSpeed limitations.
# See https://github.com/CodSpeedHQ/codspeed-go/issues/56
BENCHMARK_SHARDS := $(filter-out ./sdk/metric,$(OTEL_GO_MOD_DIRS)) ./sdk/metric/root ./sdk/metric/internal
benchmark/./sdk/metric/root:
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) . ./exemplar/...
benchmark/./sdk/metric/internal:
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) ./internal/...
print-sharded-benchmarks:
@echo $(OTEL_GO_MOD_DIRS) | jq -cR 'split(" ")'
@echo $(BENCHMARK_SHARDS) | jq -cR 'split(" ")'
.PHONY: golangci-lint golangci-lint-fix
golangci-lint-fix: ARGS=--fix
+3 -1
View File
@@ -105,7 +105,9 @@ func (d *defaultAttrEncoder) Encode(iter Iterator) string {
if keyValue.Value.Type() == STRING {
copyAndEscape(buf, keyValue.Value.AsString())
} else {
_, _ = buf.WriteString(keyValue.Value.Emit())
_, _ = buf.WriteString(
keyValue.Value.Emit(),
) //nolint:staticcheck // Preserve the existing default encoder output.
}
}
return buf.String()
+47 -11
View File
@@ -27,6 +27,8 @@ const (
int64SliceID uint64 = 3762322556277578591 // "_[]int64" (little endian)
float64SliceID uint64 = 7308324551835016539 // "[]double" (little endian)
stringSliceID uint64 = 7453010373645655387 // "[]string" (little endian)
byteSliceID uint64 = 6874028470941080415 // "_[]byte_" (little endian)
sliceID uint64 = 7883494272577650031 // "__slice_" (little endian)
emptyID uint64 = 7305809155345288421 // "__empty_" (little endian)
)
@@ -42,53 +44,87 @@ func hashKVs(kvs []KeyValue) uint64 {
// hashKV returns the xxHash64 hash of kv with h as the base.
func hashKV(h xxhash.Hash, kv KeyValue) xxhash.Hash {
h = h.String(string(kv.Key))
return hashValue(h, kv.Value)
}
switch kv.Value.Type() {
func hashValue(h xxhash.Hash, v Value) xxhash.Hash {
switch v.Type() {
case BOOL:
h = h.Uint64(boolID)
h = h.Uint64(kv.Value.numeric)
h = h.Uint64(v.numeric)
case INT64:
h = h.Uint64(int64ID)
h = h.Uint64(kv.Value.numeric)
h = h.Uint64(v.numeric)
case FLOAT64:
h = h.Uint64(float64ID)
// Assumes numeric stored with math.Float64bits.
h = h.Uint64(kv.Value.numeric)
h = h.Uint64(v.numeric)
case STRING:
h = h.Uint64(stringID)
h = h.String(kv.Value.stringly)
h = h.String(v.stringly)
case BOOLSLICE:
h = h.Uint64(boolSliceID)
rv := reflect.ValueOf(kv.Value.slice)
rv := reflect.ValueOf(v.slice)
for i := 0; i < rv.Len(); i++ {
h = h.Bool(rv.Index(i).Bool())
}
case INT64SLICE:
h = h.Uint64(int64SliceID)
rv := reflect.ValueOf(kv.Value.slice)
rv := reflect.ValueOf(v.slice)
for i := 0; i < rv.Len(); i++ {
h = h.Int64(rv.Index(i).Int())
}
case FLOAT64SLICE:
h = h.Uint64(float64SliceID)
rv := reflect.ValueOf(kv.Value.slice)
rv := reflect.ValueOf(v.slice)
for i := 0; i < rv.Len(); i++ {
h = h.Float64(rv.Index(i).Float())
}
case STRINGSLICE:
h = h.Uint64(stringSliceID)
rv := reflect.ValueOf(kv.Value.slice)
rv := reflect.ValueOf(v.slice)
for i := 0; i < rv.Len(); i++ {
h = h.String(rv.Index(i).String())
}
case BYTESLICE:
h = h.Uint64(byteSliceID)
h = h.String(v.stringly)
case SLICE:
h = h.Uint64(sliceID)
switch vals := v.slice.(type) {
case [0]Value:
// No values to hash, but the type identifier is still hashed above.
case [1]Value:
h = hashValueSlice(h, vals[:])
case [2]Value:
h = hashValueSlice(h, vals[:])
case [3]Value:
h = hashValueSlice(h, vals[:])
case [4]Value:
h = hashValueSlice(h, vals[:])
case [5]Value:
h = hashValueSlice(h, vals[:])
default:
rv := reflect.ValueOf(v.slice)
for i := 0; i < rv.Len(); i++ {
h = hashValue(h, rv.Index(i).Interface().(Value))
}
}
case EMPTY:
h = h.Uint64(emptyID)
default:
// Logging is an alternative, but using the internal logger here
// causes an import cycle so it is not done.
v := kv.Value.AsInterface()
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", v)
val := v.AsInterface()
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", val)
panic(msg)
}
return h
}
func hashValueSlice(h xxhash.Hash, vals []Value) xxhash.Hash {
for _, v := range vals {
h = hashValue(h, v)
}
return h
}
+22
View File
@@ -117,6 +117,28 @@ func (k Key) StringSlice(v []string) KeyValue {
}
}
// ByteSlice creates a KeyValue instance with a BYTESLICE Value.
//
// If creating both a key and value at the same time, use the provided
// convenience function instead -- ByteSlice(name, value).
func (k Key) ByteSlice(v []byte) KeyValue {
return KeyValue{
Key: k,
Value: ByteSliceValue(v),
}
}
// Slice creates a KeyValue instance with a SLICE Value.
//
// If creating both a key and value at the same time, use the provided
// convenience function instead -- Slice(name, values...).
func (k Key) Slice(v ...Value) KeyValue {
return KeyValue{
Key: k,
Value: SliceValue(v...),
}
}
// Defined reports whether the key is not empty.
func (k Key) Defined() bool {
return len(k) != 0
+10
View File
@@ -68,6 +68,16 @@ func StringSlice(k string, v []string) KeyValue {
return Key(k).StringSlice(v)
}
// ByteSlice creates a KeyValue with a BYTESLICE Value type.
func ByteSlice(k string, v []byte) KeyValue {
return Key(k).ByteSlice(v)
}
// Slice creates a KeyValue with a SLICE Value type.
func Slice(k string, v ...Value) KeyValue {
return Key(k).Slice(v...)
}
// Stringer creates a new key-value pair with a passed name and a string
// value generated by the passed Stringer interface.
func Stringer(k string, v fmt.Stringer) KeyValue {
+2 -2
View File
@@ -401,7 +401,7 @@ func computeDataFixed(kvs []KeyValue) any {
func computeDataReflect(kvs []KeyValue) any {
at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem()
for i, keyValue := range kvs {
*(at.Index(i).Addr().Interface().(*KeyValue)) = keyValue
*at.Index(i).Addr().Interface().(*KeyValue) = keyValue
}
return at.Interface()
}
@@ -415,7 +415,7 @@ func (l *Set) MarshalJSON() ([]byte, error) {
func (l Set) MarshalLog() any {
kvs := make(map[string]string)
for _, kv := range l.ToSlice() {
kvs[string(kv.Key)] = kv.Value.Emit()
kvs[string(kv.Key)] = kv.Value.String()
}
return kvs
}
+4 -2
View File
@@ -17,11 +17,13 @@ func _() {
_ = x[INT64SLICE-6]
_ = x[FLOAT64SLICE-7]
_ = x[STRINGSLICE-8]
_ = x[BYTESLICE-9]
_ = x[SLICE-10]
}
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE"
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICEBYTESLICESLICE"
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69}
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69, 78, 83}
func (i Type) String() string {
idx := int(i) - 0
+742
View File
@@ -4,9 +4,14 @@
package attribute // import "go.opentelemetry.io/otel/attribute"
import (
"encoding/base64"
"encoding/json"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"unicode/utf8"
attribute "go.opentelemetry.io/otel/attribute/internal"
)
@@ -45,6 +50,10 @@ const (
FLOAT64SLICE
// STRINGSLICE is a slice of strings Type Value.
STRINGSLICE
// BYTESLICE is a slice of bytes Type Value.
BYTESLICE
// SLICE is a slice of Value Type values.
SLICE
// INVALID is used for a Value with no value set.
//
// Deprecated: Use EMPTY instead as an empty value is a valid value.
@@ -134,6 +143,19 @@ func StringSliceValue(v []string) Value {
return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
}
// ByteSliceValue creates a BYTESLICE Value.
func ByteSliceValue(v []byte) Value {
return Value{
vtype: BYTESLICE,
stringly: string(v),
}
}
// SliceValue creates a SLICE Value.
func SliceValue(v ...Value) Value {
return Value{vtype: SLICE, slice: sliceValue(v)}
}
// Type returns a type of the Value.
func (v Value) Type() Type {
return v.vtype
@@ -215,6 +237,59 @@ func (v Value) asStringSlice() []string {
return attribute.AsSlice[string](v.slice)
}
// AsSlice returns the []Value value. Make sure that the Value's type is
// SLICE.
func (v Value) AsSlice() []Value {
if v.vtype != SLICE {
return nil
}
return v.asSlice()
}
func (v Value) asSlice() []Value {
switch vals := v.slice.(type) {
case [0]Value:
return []Value{}
case [1]Value:
return []Value{vals[0]}
case [2]Value:
return []Value{vals[0], vals[1]}
case [3]Value:
return []Value{vals[0], vals[1], vals[2]}
case [4]Value:
return []Value{vals[0], vals[1], vals[2], vals[3]}
case [5]Value:
return []Value{vals[0], vals[1], vals[2], vals[3], vals[4]}
default:
return asValueSliceReflect(v.slice)
}
}
func asValueSliceReflect(v any) []Value {
rv := reflect.ValueOf(v)
if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[Value]() {
return nil
}
cpy := make([]Value, rv.Len())
if len(cpy) > 0 {
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
}
return cpy
}
// AsByteSlice returns the bytes value. Make sure that the Value's type
// is BYTESLICE.
func (v Value) AsByteSlice() []byte {
if v.vtype != BYTESLICE {
return nil
}
return v.asByteSlice()
}
func (v Value) asByteSlice() []byte {
return []byte(v.stringly)
}
type unknownValueType struct{}
// AsInterface returns Value's data as any.
@@ -236,13 +311,60 @@ func (v Value) AsInterface() any {
return v.stringly
case STRINGSLICE:
return v.asStringSlice()
case BYTESLICE:
return v.asByteSlice()
case SLICE:
return v.asSlice()
case EMPTY:
return nil
}
return unknownValueType{}
}
// String returns a string representation of Value using the
// [OpenTelemetry AnyValue representation for non-OTLP protocols] rules.
//
// Strings are returned as-is without JSON quoting, booleans and integers use
// JSON literals, floating-point values use JSON numbers except that NaN and
// ±Inf are rendered as NaN, Infinity, and -Infinity, byte slices are
// base64-encoded, empty values are the empty string, and slices are encoded as
// JSON arrays. String, byte, and special floating-point values inside arrays
// are encoded as JSON strings, and empty values inside arrays are encoded as
// null.
//
// [OpenTelemetry AnyValue representation for non-OTLP protocols]: https://opentelemetry.io/docs/specs/otel/common/#anyvalue-representation-for-non-otlp-protocols
func (v Value) String() string {
switch v.Type() {
case BOOL:
return strconv.FormatBool(v.AsBool())
case BOOLSLICE:
return formatBoolSliceValue(v.slice)
case INT64:
return strconv.FormatInt(v.AsInt64(), 10)
case INT64SLICE:
return formatInt64SliceValue(v.slice)
case FLOAT64:
return formatFloat64(v.AsFloat64())
case FLOAT64SLICE:
return formatFloat64SliceValue(v.slice)
case STRING:
return v.stringly
case STRINGSLICE:
return formatStringSliceValue(v.slice)
case BYTESLICE:
return formatByteSlice(v.stringly)
case SLICE:
return formatValueSliceValue(v.slice)
case EMPTY:
return ""
default:
return "unknown"
}
}
// Emit returns a string representation of Value's data.
//
// Deprecated: Use [Value.String] instead.
func (v Value) Emit() string {
switch v.Type() {
case BOOLSLICE:
@@ -273,6 +395,10 @@ func (v Value) Emit() string {
return string(j)
case STRING:
return v.stringly
case BYTESLICE:
return formatByteSlice(v.stringly)
case SLICE:
return formatValueSliceValue(v.slice)
case EMPTY:
return ""
default:
@@ -280,6 +406,622 @@ func (v Value) Emit() string {
}
}
const (
jsonArrayBracketsLen = len("[]")
boolArrayElemMaxLen = len("false")
int64ArrayElemMaxLen = len("-9223372036854775808")
float64ArrayElemMaxLen = len("-1.7976931348623157e+308")
commaLen = len(",")
)
func sliceValue(v []Value) any {
switch len(v) {
case 0:
return [0]Value{}
case 1:
return [1]Value{v[0]}
case 2:
return [2]Value{v[0], v[1]}
case 3:
return [3]Value{v[0], v[1], v[2]}
case 4:
return [4]Value{v[0], v[1], v[2], v[3]}
case 5:
return [5]Value{v[0], v[1], v[2], v[3], v[4]}
default:
return sliceValueReflect(v)
}
}
func sliceValueReflect(v []Value) any {
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[Value]())).Elem()
reflect.Copy(cp, reflect.ValueOf(v))
return cp.Interface()
}
func formatBoolSliceValue(v any) string {
switch vals := v.(type) {
case [0]bool:
return "[]"
case [1]bool:
return formatBoolSlice(vals[:])
case [2]bool:
return formatBoolSlice(vals[:])
case [3]bool:
return formatBoolSlice(vals[:])
default:
return formatBoolSliceReflect(v)
}
}
func formatBoolSlice(vals []bool) string {
var b strings.Builder
appendBoolSlice(&b, vals)
return b.String()
}
func formatBoolSliceReflect(v any) string {
var b strings.Builder
appendBoolSliceReflect(&b, reflect.ValueOf(v))
return b.String()
}
func appendBoolSliceValue(dst *strings.Builder, v any) {
switch vals := v.(type) {
case [0]bool:
_, _ = dst.WriteString("[]")
case [1]bool:
appendBoolSlice(dst, vals[:])
case [2]bool:
appendBoolSlice(dst, vals[:])
case [3]bool:
appendBoolSlice(dst, vals[:])
default:
appendBoolSliceReflect(dst, reflect.ValueOf(v))
}
}
func appendBoolSlice(dst *strings.Builder, vals []bool) {
dst.Grow(jsonArrayBracketsLen + len(vals)*(boolArrayElemMaxLen+commaLen))
_ = dst.WriteByte('[')
for i, val := range vals {
if i > 0 {
_ = dst.WriteByte(',')
}
if val {
_, _ = dst.WriteString("true")
} else {
_, _ = dst.WriteString("false")
}
}
_ = dst.WriteByte(']')
}
func appendBoolSliceReflect(dst *strings.Builder, rv reflect.Value) {
dst.Grow(jsonArrayBracketsLen + rv.Len()*(boolArrayElemMaxLen+commaLen))
_ = dst.WriteByte('[')
for i := 0; i < rv.Len(); i++ {
if i > 0 {
_ = dst.WriteByte(',')
}
if rv.Index(i).Bool() {
_, _ = dst.WriteString("true")
} else {
_, _ = dst.WriteString("false")
}
}
_ = dst.WriteByte(']')
}
func formatInt64SliceValue(v any) string {
switch vals := v.(type) {
case [0]int64:
return "[]"
case [1]int64:
return formatInt64Slice(vals[:])
case [2]int64:
return formatInt64Slice(vals[:])
case [3]int64:
return formatInt64Slice(vals[:])
default:
return formatInt64SliceReflect(v)
}
}
func formatInt64Slice(vals []int64) string {
var b strings.Builder
appendInt64Slice(&b, vals)
return b.String()
}
func formatInt64SliceReflect(v any) string {
var b strings.Builder
appendInt64SliceReflect(&b, reflect.ValueOf(v))
return b.String()
}
func appendInt64SliceValue(dst *strings.Builder, v any) {
switch vals := v.(type) {
case [0]int64:
_, _ = dst.WriteString("[]")
case [1]int64:
appendInt64Slice(dst, vals[:])
case [2]int64:
appendInt64Slice(dst, vals[:])
case [3]int64:
appendInt64Slice(dst, vals[:])
default:
appendInt64SliceReflect(dst, reflect.ValueOf(v))
}
}
func appendInt64Slice(dst *strings.Builder, vals []int64) {
dst.Grow(jsonArrayBracketsLen + len(vals)*(int64ArrayElemMaxLen+commaLen))
_ = dst.WriteByte('[')
var buf [int64ArrayElemMaxLen]byte
for i, val := range vals {
if i > 0 {
_ = dst.WriteByte(',')
}
out := strconv.AppendInt(buf[:0], val, 10)
_, _ = dst.Write(out)
}
_ = dst.WriteByte(']')
}
func appendInt64SliceReflect(dst *strings.Builder, rv reflect.Value) {
dst.Grow(jsonArrayBracketsLen + rv.Len()*(int64ArrayElemMaxLen+commaLen))
_ = dst.WriteByte('[')
var scratch [int64ArrayElemMaxLen]byte
for i := 0; i < rv.Len(); i++ {
if i > 0 {
_ = dst.WriteByte(',')
}
out := strconv.AppendInt(scratch[:0], rv.Index(i).Int(), 10)
_, _ = dst.Write(out)
}
_ = dst.WriteByte(']')
}
func formatFloat64(v float64) string {
switch {
case math.IsNaN(v):
return "NaN"
case math.IsInf(v, 1):
return "Infinity"
case math.IsInf(v, -1):
return "-Infinity"
default:
return strconv.FormatFloat(v, 'g', -1, 64)
}
}
func formatFloat64SliceValue(v any) string {
switch vals := v.(type) {
case [0]float64:
return "[]"
case [1]float64:
return formatFloat64Slice(vals[:])
case [2]float64:
return formatFloat64Slice(vals[:])
case [3]float64:
return formatFloat64Slice(vals[:])
default:
return formatFloat64SliceReflect(v)
}
}
func formatFloat64Slice(vals []float64) string {
var b strings.Builder
appendFloat64Slice(&b, vals)
return b.String()
}
func formatFloat64SliceReflect(v any) string {
var b strings.Builder
appendFloat64SliceReflect(&b, reflect.ValueOf(v))
return b.String()
}
func appendFloat64SliceValue(dst *strings.Builder, v any) {
switch vals := v.(type) {
case [0]float64:
_, _ = dst.WriteString("[]")
case [1]float64:
appendFloat64Slice(dst, vals[:])
case [2]float64:
appendFloat64Slice(dst, vals[:])
case [3]float64:
appendFloat64Slice(dst, vals[:])
default:
appendFloat64SliceReflect(dst, reflect.ValueOf(v))
}
}
func appendFloat64Slice(dst *strings.Builder, vals []float64) {
dst.Grow(jsonArrayBracketsLen + len(vals)*(float64ArrayElemMaxLen+commaLen))
_ = dst.WriteByte('[')
var buf [float64ArrayElemMaxLen]byte
for i, val := range vals {
if i > 0 {
_ = dst.WriteByte(',')
}
switch {
case math.IsNaN(val):
_, _ = dst.WriteString(`"NaN"`)
case math.IsInf(val, 1):
_, _ = dst.WriteString(`"Infinity"`)
case math.IsInf(val, -1):
_, _ = dst.WriteString(`"-Infinity"`)
default:
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
_, _ = dst.Write(out)
}
}
_ = dst.WriteByte(']')
}
func appendFloat64SliceReflect(dst *strings.Builder, rv reflect.Value) {
dst.Grow(jsonArrayBracketsLen + rv.Len()*(float64ArrayElemMaxLen+commaLen))
_ = dst.WriteByte('[')
var scratch [float64ArrayElemMaxLen]byte
for i := 0; i < rv.Len(); i++ {
if i > 0 {
_ = dst.WriteByte(',')
}
val := rv.Index(i).Float()
switch {
case math.IsNaN(val):
_, _ = dst.WriteString(`"NaN"`)
case math.IsInf(val, 1):
_, _ = dst.WriteString(`"Infinity"`)
case math.IsInf(val, -1):
_, _ = dst.WriteString(`"-Infinity"`)
default:
out := strconv.AppendFloat(scratch[:0], val, 'g', -1, 64)
_, _ = dst.Write(out)
}
}
_ = dst.WriteByte(']')
}
func formatStringSliceValue(v any) string {
switch vals := v.(type) {
case [0]string:
return "[]"
case [1]string:
return formatStringSlice(vals[:])
case [2]string:
return formatStringSlice(vals[:])
case [3]string:
return formatStringSlice(vals[:])
default:
return formatStringSliceReflect(v)
}
}
func formatStringSlice(vals []string) string {
var b strings.Builder
appendStringSlice(&b, vals)
return b.String()
}
func formatStringSliceReflect(v any) string {
var b strings.Builder
appendStringSliceReflect(&b, reflect.ValueOf(v))
return b.String()
}
func appendStringSliceValue(dst *strings.Builder, v any) {
switch vals := v.(type) {
case [0]string:
_, _ = dst.WriteString("[]")
case [1]string:
appendStringSlice(dst, vals[:])
case [2]string:
appendStringSlice(dst, vals[:])
case [3]string:
appendStringSlice(dst, vals[:])
default:
appendStringSliceReflect(dst, reflect.ValueOf(v))
}
}
func appendStringSlice(dst *strings.Builder, vals []string) {
size := jsonArrayBracketsLen
for _, val := range vals {
size += len(val) + commaLen + 2 // Account for JSON string quotes and comma.
}
dst.Grow(size)
_ = dst.WriteByte('[')
for i, val := range vals {
if i > 0 {
_ = dst.WriteByte(',')
}
appendJSONString(dst, val)
}
_ = dst.WriteByte(']')
}
func appendStringSliceReflect(dst *strings.Builder, rv reflect.Value) {
size := jsonArrayBracketsLen
for i := 0; i < rv.Len(); i++ {
size += len(rv.Index(i).String()) + commaLen + 2 // Account for JSON string quotes and comma.
}
dst.Grow(size)
_ = dst.WriteByte('[')
for i := 0; i < rv.Len(); i++ {
if i > 0 {
_ = dst.WriteByte(',')
}
appendJSONString(dst, rv.Index(i).String())
}
_ = dst.WriteByte(']')
}
func formatByteSlice(v string) string {
var b strings.Builder
appendBase64(&b, v)
return b.String()
}
func formatValueSliceValue(v any) string {
switch vals := v.(type) {
case [0]Value:
return "[]"
case [1]Value:
return formatValueSlice(vals[:])
case [2]Value:
return formatValueSlice(vals[:])
case [3]Value:
return formatValueSlice(vals[:])
case [4]Value:
return formatValueSlice(vals[:])
case [5]Value:
return formatValueSlice(vals[:])
default:
return formatValueSliceReflect(v)
}
}
func formatValueSlice(vals []Value) string {
var b strings.Builder
appendValueSlice(&b, vals)
return b.String()
}
func formatValueSliceReflect(v any) string {
var b strings.Builder
appendValueSliceReflect(&b, reflect.ValueOf(v))
return b.String()
}
func appendValueSliceValue(dst *strings.Builder, v any) {
switch vals := v.(type) {
case [0]Value:
_, _ = dst.WriteString("[]")
case [1]Value:
appendValueSlice(dst, vals[:])
case [2]Value:
appendValueSlice(dst, vals[:])
case [3]Value:
appendValueSlice(dst, vals[:])
case [4]Value:
appendValueSlice(dst, vals[:])
case [5]Value:
appendValueSlice(dst, vals[:])
default:
appendValueSliceReflect(dst, reflect.ValueOf(v))
}
}
func appendValueSlice(dst *strings.Builder, vals []Value) {
// Estimate 10 bytes per value for small values and commas.
dst.Grow(jsonArrayBracketsLen + len(vals)*commaLen + len(vals)*10)
_ = dst.WriteByte('[')
for i, val := range vals {
if i > 0 {
_ = dst.WriteByte(',')
}
appendJSONValue(dst, val)
}
_ = dst.WriteByte(']')
}
func appendValueSliceReflect(dst *strings.Builder, rv reflect.Value) {
// Estimate 10 bytes per value for small values and commas.
dst.Grow(jsonArrayBracketsLen + rv.Len()*commaLen + rv.Len()*10)
_ = dst.WriteByte('[')
for i := 0; i < rv.Len(); i++ {
if i > 0 {
_ = dst.WriteByte(',')
}
appendJSONValue(dst, rv.Index(i).Interface().(Value))
}
_ = dst.WriteByte(']')
}
func appendJSONValue(dst *strings.Builder, v Value) {
switch v.Type() {
case BOOL:
if v.AsBool() {
_, _ = dst.WriteString("true")
} else {
_, _ = dst.WriteString("false")
}
case BOOLSLICE:
appendBoolSliceValue(dst, v.slice)
case INT64:
var buf [int64ArrayElemMaxLen]byte
out := strconv.AppendInt(buf[:0], v.AsInt64(), 10)
_, _ = dst.Write(out)
case INT64SLICE:
appendInt64SliceValue(dst, v.slice)
case FLOAT64:
val := v.AsFloat64()
switch {
case math.IsNaN(val):
appendJSONString(dst, "NaN")
case math.IsInf(val, 1):
appendJSONString(dst, "Infinity")
case math.IsInf(val, -1):
appendJSONString(dst, "-Infinity")
default:
var buf [float64ArrayElemMaxLen]byte
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
_, _ = dst.Write(out)
}
case FLOAT64SLICE:
appendFloat64SliceValue(dst, v.slice)
case STRING:
appendJSONString(dst, v.stringly)
case STRINGSLICE:
appendStringSliceValue(dst, v.slice)
case BYTESLICE:
_ = dst.WriteByte('"')
appendBase64(dst, v.stringly)
_ = dst.WriteByte('"')
case SLICE:
appendValueSliceValue(dst, v.slice)
case EMPTY:
_, _ = dst.WriteString("null")
default:
appendJSONString(dst, "unknown")
}
}
// appendJSONString appends s to dst as a JSON string literal.
//
// This is adapted from the Go standard library's encoding/json
// [appendString implementation]. It keeps the same escaping behavior we need
// here, but writes directly into a strings.Builder and intentionally does not
// apply HTML escaping because the OpenTelemetry non-OTLP AnyValue representation
// only requires JSON array string encoding. We inline this instead of using
// encoding/json so slice formatting avoids allocations and reflection.
//
// [appendString implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/json/encode.go#L998-L1064
func appendJSONString(dst *strings.Builder, s string) {
const hex = "0123456789abcdef" // For escaping bytes to hex.
_ = dst.WriteByte('"')
start := 0
for i := 0; i < len(s); {
if c := s[i]; c < utf8.RuneSelf {
if c >= 0x20 && c != '\\' && c != '"' {
i++
continue
}
if start < i {
_, _ = dst.WriteString(s[start:i])
}
switch c {
case '\\', '"':
_ = dst.WriteByte('\\')
_ = dst.WriteByte(c)
case '\b':
_, _ = dst.WriteString(`\b`)
case '\f':
_, _ = dst.WriteString(`\f`)
case '\n':
_, _ = dst.WriteString(`\n`)
case '\r':
_, _ = dst.WriteString(`\r`)
case '\t':
_, _ = dst.WriteString(`\t`)
default:
_, _ = dst.WriteString(`\u00`)
_ = dst.WriteByte(hex[c>>4])
_ = dst.WriteByte(hex[c&0x0f])
}
i++
start = i
continue
}
r, size := utf8.DecodeRuneInString(s[i:])
if r == utf8.RuneError && size == 1 {
if start < i {
_, _ = dst.WriteString(s[start:i])
}
// Match encoding/json by replacing invalid UTF-8 with U+FFFD.
_, _ = dst.WriteString(`\ufffd`)
i++
start = i
continue
}
if r == '\u2028' || r == '\u2029' {
if start < i {
_, _ = dst.WriteString(s[start:i])
}
// Escape JSONP-sensitive separators unconditionally, like encoding/json.
_, _ = dst.WriteString(`\u202`)
_ = dst.WriteByte(hex[r&0x0f])
i += size
start = i
continue
}
i += size
}
if start < len(s) {
_, _ = dst.WriteString(s[start:])
}
_ = dst.WriteByte('"')
}
// This is adapted from the Go standard library's encoding/base64
// [Encoding.Encode implementation]. It keeps the same encoding behavior we need
// here, but writes directly into a strings.Builder. We inline this instead of using
// encoding/base64 to avoid allocations.
//
// [Encoding.Encode implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/base64/base64.go#L139-L189
func appendBase64(dst *strings.Builder, s string) {
const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
dst.Grow(base64.StdEncoding.EncodedLen(len(s)))
i := 0
for ; i+2 < len(s); i += 3 {
n := uint32(s[i])<<16 | uint32(s[i+1])<<8 | uint32(s[i+2])
_ = dst.WriteByte(encode[n>>18&0x3f])
_ = dst.WriteByte(encode[n>>12&0x3f])
_ = dst.WriteByte(encode[n>>6&0x3f])
_ = dst.WriteByte(encode[n&0x3f])
}
switch len(s) - i {
case 1:
n := uint32(s[i]) << 16
_ = dst.WriteByte(encode[n>>18&0x3f])
_ = dst.WriteByte(encode[n>>12&0x3f])
_ = dst.WriteByte('=')
_ = dst.WriteByte('=')
case 2:
n := uint32(s[i])<<16 | uint32(s[i+1])<<8
_ = dst.WriteByte(encode[n>>18&0x3f])
_ = dst.WriteByte(encode[n>>12&0x3f])
_ = dst.WriteByte(encode[n>>6&0x3f])
_ = dst.WriteByte('=')
}
}
// MarshalJSON returns the JSON encoding of the Value.
func (v Value) MarshalJSON() ([]byte, error) {
var jsonVal struct {
+26 -4
View File
@@ -14,6 +14,10 @@ import (
)
const (
maxParseErrors = 5
// W3C Baggage specification limits.
// https://www.w3.org/TR/baggage/#limits
maxMembers = 64
maxBytesPerBaggageString = 8192
@@ -493,9 +497,15 @@ func New(members ...Member) (Baggage, error) {
// from the W3C Baggage specification which allows duplicate list-members, but
// conforms to the OpenTelemetry Baggage specification.
//
// If the baggage-string exceeds the maximum allowed members (64) or bytes
// (8192), members are dropped until the limits are satisfied and an error is
// returned along with the partial result.
// If the raw baggage-string exceeds the maximum allowed bytes (8192), an
// empty Baggage and an error are returned.
//
// Otherwise, members are parsed left-to-right and accumulated until one of
// the following conditions is reached, at which point parsing stops and an
// error is returned alongside the partial result:
// - accepting the next member would cause the encoded baggage to exceed
// 8192 bytes, or
// - the baggage already contains 64 distinct keys.
//
// Invalid members are skipped and the error is returned along with the
// partial result containing the valid members.
@@ -504,9 +514,14 @@ func Parse(bStr string) (Baggage, error) {
return Baggage{}, nil
}
if n := len(bStr); n > maxBytesPerBaggageString {
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
}
b := make(baggage.List)
sizes := make(map[string]int) // Track per-key byte sizes
var totalBytes int
var parseErrors int
var truncateErr error
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
// Check member count limit.
@@ -517,7 +532,10 @@ func Parse(bStr string) (Baggage, error) {
m, err := parseMember(memberStr)
if err != nil {
truncateErr = errors.Join(truncateErr, err)
parseErrors++
if parseErrors <= maxParseErrors {
truncateErr = errors.Join(truncateErr, err)
}
continue // skip invalid member, keep processing
}
@@ -553,6 +571,10 @@ func Parse(bStr string) (Baggage, error) {
totalBytes = newTotalBytes
}
if dropped := parseErrors - maxParseErrors; dropped > 0 {
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more invalid member(s)", dropped))
}
if len(b) == 0 {
return Baggage{}, truncateErr
}
+1 -1
View File
@@ -1,4 +1,4 @@
# This is a renovate-friendly source of Docker images.
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
FROM otel/weaver:v0.22.1@sha256:33ae522ae4b71c1c562563c1d81f46aa0f79f088a0873199143a1f11ac30e5c9 AS weaver
FROM otel/weaver:v0.23.0@sha256:7984ecb55b859eb3034ae9d836c4eeda137e2bdd0873b7ba2bb6c3d24d6ff457 AS weaver
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
@@ -87,6 +87,16 @@ func Value(v attribute.Value) *commonpb.AnyValue {
av.Value = &commonpb.AnyValue_StringValue{
StringValue: v.AsString(),
}
case attribute.BYTESLICE:
av.Value = &commonpb.AnyValue_BytesValue{
BytesValue: v.AsByteSlice(),
}
case attribute.SLICE:
av.Value = &commonpb.AnyValue_ArrayValue{
ArrayValue: &commonpb.ArrayValue{
Values: values(v.AsSlice()),
},
}
case attribute.STRINGSLICE:
av.Value = &commonpb.AnyValue_ArrayValue{
ArrayValue: &commonpb.ArrayValue{
@@ -149,3 +159,11 @@ func stringSliceValues(vals []string) []*commonpb.AnyValue {
}
return converted
}
func values(vals []attribute.Value) []*commonpb.AnyValue {
converted := make([]*commonpb.AnyValue, len(vals))
for i, v := range vals {
converted[i] = Value(v)
}
return converted
}
@@ -6,6 +6,7 @@ package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptra
import (
"context"
"errors"
"fmt"
"sync"
"time"
@@ -16,6 +17,7 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
@@ -26,11 +28,12 @@ import (
)
type client struct {
endpoint string
dialOpts []grpc.DialOption
metadata metadata.MD
exportTimeout time.Duration
requestFunc retry.RequestFunc
endpoint string
dialOpts []grpc.DialOption
metadata metadata.MD
exportTimeout time.Duration
maxRequestSize int
requestFunc retry.RequestFunc
// stopCtx is used as a parent context for all exports. Therefore, when it
// is canceled with the stopFunc all exports are canceled.
@@ -65,14 +68,15 @@ func newClient(opts ...Option) *client {
ctx, cancel := context.WithCancel(context.Background()) //nolint:gosec // cancel called in client shutdown.
c := &client{
endpoint: cfg.Traces.Endpoint,
exportTimeout: cfg.Traces.Timeout,
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
dialOpts: cfg.DialOptions,
stopCtx: ctx,
stopFunc: cancel,
conn: cfg.GRPCConn,
instID: counter.NextExporterID(),
endpoint: cfg.Traces.Endpoint,
exportTimeout: cfg.Traces.Timeout,
maxRequestSize: cfg.Traces.MaxRequestSize,
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
dialOpts: cfg.DialOptions,
stopCtx: ctx,
stopFunc: cancel,
conn: cfg.GRPCConn,
instID: counter.NextExporterID(),
}
if len(cfg.Traces.Headers) > 0 {
@@ -205,16 +209,28 @@ func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
ctx, cancel := c.exportContext(ctx)
defer cancel()
var code codes.Code
pbRequest := &coltracepb.ExportTraceServiceRequest{
ResourceSpans: protoSpans,
}
code := codes.Unknown
if c.inst != nil {
op := c.inst.ExportSpans(ctx, len(protoSpans))
var spanCount int
for _, rs := range protoSpans {
for _, ss := range rs.ScopeSpans {
spanCount += len(ss.Spans)
}
}
op := c.inst.ExportSpans(ctx, spanCount)
defer func() { op.End(uploadErr, code) }()
}
if maxSize := c.maxRequestSize; maxSize > 0 && proto.Size(pbRequest) > maxSize {
return fmt.Errorf("request message too large: exceeded %d bytes", maxSize)
}
return c.requestFunc(ctx, func(iCtx context.Context) error {
resp, err := c.tsc.Export(iCtx, &coltracepb.ExportTraceServiceRequest{
ResourceSpans: protoSpans,
})
resp, err := c.tsc.Export(iCtx, pbRequest)
if resp != nil && resp.PartialSuccess != nil {
msg := resp.PartialSuccess.GetErrorMessage()
n := resp.PartialSuccess.GetRejectedSpans()
@@ -18,8 +18,8 @@ import (
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
)
const (
@@ -72,6 +72,7 @@ var (
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
func put[T any](p *sync.Pool, s *[]T) {
clear(*s) // erase elements to allow GC to collect what they refer to.
*s = (*s)[:0] // Reset.
p.Put(s)
}
@@ -339,7 +340,10 @@ var errPartialPool = &sync.Pool{
// the provided non-nil err.
func rejected(n int64, err error) int64 {
ps := errPartialPool.Get().(*internal.PartialSuccess)
defer errPartialPool.Put(ps)
defer func() {
*ps = internal.PartialSuccess{} // erase fields to allow GC to collect them.
errPartialPool.Put(ps)
}()
// Check for partial success.
if errors.As(err, ps) {
// Bound RejectedItems to [0, n]. This should not be needed,
@@ -31,6 +31,9 @@ const (
// DefaultTracesPath is a default URL path for endpoint that
// receives spans.
DefaultTracesPath string = "/v1/traces"
// DefaultMaxRequestSize is the default maximum size of a serialized export
// request, before compression.
DefaultMaxRequestSize int = 64 * 1024 * 1024
// DefaultTimeout is a default max waiting time for the backend to process
// each span batch.
DefaultTimeout time.Duration = 10 * time.Second
@@ -42,13 +45,14 @@ type (
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
SignalConfig struct {
Endpoint string
Insecure bool
TLSCfg *tls.Config
Headers map[string]string
Compression Compression
Timeout time.Duration
URLPath string
Endpoint string
Insecure bool
TLSCfg *tls.Config
Headers map[string]string
Compression Compression
MaxRequestSize int
Timeout time.Duration
URLPath string
// gRPC configurations
GRPCCredentials credentials.TransportCredentials
@@ -77,10 +81,11 @@ type (
func NewHTTPConfig(opts ...HTTPOption) Config {
cfg := Config{
Traces: SignalConfig{
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
URLPath: DefaultTracesPath,
Compression: NoCompression,
Timeout: DefaultTimeout,
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
URLPath: DefaultTracesPath,
Compression: NoCompression,
MaxRequestSize: DefaultMaxRequestSize,
Timeout: DefaultTimeout,
},
RetryConfig: retry.DefaultConfig,
}
@@ -111,10 +116,11 @@ func NewGRPCConfig(opts ...GRPCOption) Config {
userAgent := "OTel OTLP Exporter Go/" + otlptrace.Version()
cfg := Config{
Traces: SignalConfig{
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
URLPath: DefaultTracesPath,
Compression: NoCompression,
Timeout: DefaultTimeout,
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
URLPath: DefaultTracesPath,
Compression: NoCompression,
MaxRequestSize: DefaultMaxRequestSize,
Timeout: DefaultTimeout,
},
RetryConfig: retry.DefaultConfig,
DialOptions: []grpc.DialOption{grpc.WithUserAgent(userAgent)},
@@ -345,6 +351,13 @@ func WithTimeout(duration time.Duration) GenericOption {
})
}
func WithMaxRequestSize(size int) GenericOption {
return newGenericOption(func(cfg Config) Config {
cfg.Traces.MaxRequestSize = size
return cfg
})
}
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
return newGenericOption(func(cfg Config) Config {
cfg.Traces.Proxy = pf
@@ -5,4 +5,4 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
// Version is the current release version of the OpenTelemetry OTLP gRPC trace
// exporter in use.
const Version = "1.43.0"
const Version = "1.44.0"
@@ -192,6 +192,16 @@ func WithTimeout(duration time.Duration) Option {
return wrappedOption{otlpconfig.WithTimeout(duration)}
}
// WithMaxRequestSize sets the maximum size, in bytes, of a serialized export
// request, before compression, that the exporter will send.
//
// If size is less than or equal to zero, no request-size limit is applied.
// Disabling the limit is not recommended because it can lead to excessive
// resource consumption or abuse.
func WithMaxRequestSize(size int) Option {
return wrappedOption{otlpconfig.WithMaxRequestSize(size)}
}
// WithRetry sets the retry policy for transient retryable errors that may be
// returned by the target endpoint when exporting a batch of spans.
//
+1 -1
View File
@@ -5,5 +5,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
func Version() string {
return "1.43.0"
return "1.44.0"
}
+9
View File
@@ -51,6 +51,9 @@ type Float64ObservableCounterConfig struct {
func NewFloat64ObservableCounterConfig(opts ...Float64ObservableCounterOption) Float64ObservableCounterConfig {
var config Float64ObservableCounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyFloat64ObservableCounter(config)
}
return config
@@ -111,6 +114,9 @@ func NewFloat64ObservableUpDownCounterConfig(
) Float64ObservableUpDownCounterConfig {
var config Float64ObservableUpDownCounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyFloat64ObservableUpDownCounter(config)
}
return config
@@ -168,6 +174,9 @@ type Float64ObservableGaugeConfig struct {
func NewFloat64ObservableGaugeConfig(opts ...Float64ObservableGaugeOption) Float64ObservableGaugeConfig {
var config Float64ObservableGaugeConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyFloat64ObservableGauge(config)
}
return config
+9
View File
@@ -50,6 +50,9 @@ type Int64ObservableCounterConfig struct {
func NewInt64ObservableCounterConfig(opts ...Int64ObservableCounterOption) Int64ObservableCounterConfig {
var config Int64ObservableCounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyInt64ObservableCounter(config)
}
return config
@@ -110,6 +113,9 @@ func NewInt64ObservableUpDownCounterConfig(
) Int64ObservableUpDownCounterConfig {
var config Int64ObservableUpDownCounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyInt64ObservableUpDownCounter(config)
}
return config
@@ -167,6 +173,9 @@ type Int64ObservableGaugeConfig struct {
func NewInt64ObservableGaugeConfig(opts ...Int64ObservableGaugeOption) Int64ObservableGaugeConfig {
var config Int64ObservableGaugeConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyInt64ObservableGauge(config)
}
return config
+7
View File
@@ -42,11 +42,18 @@ type MeterOption interface {
applyMeter(MeterConfig) MeterConfig
}
type experimentalOption interface {
Experimental()
}
// NewMeterConfig creates a new MeterConfig and applies
// all the given options.
func NewMeterConfig(opts ...MeterOption) MeterConfig {
var config MeterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyMeter(config)
}
return config
+39 -12
View File
@@ -24,10 +24,10 @@ all instruments fall into two overlapping logical categories: asynchronous or
synchronous, and int64 or float64.
All synchronous instruments ([Int64Counter], [Int64UpDownCounter],
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
[Float64Histogram]) are used to measure the operation and performance of source
code during the source code execution. These instruments only make measurements
when the source code they instrument is run.
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
[Float64Histogram], and [Float64Gauge]) are used to measure the operation and
performance of source code during the source code execution. These instruments
only make measurements when the source code they instrument is run.
All asynchronous instruments ([Int64ObservableCounter],
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
@@ -50,9 +50,11 @@ incrementally increase in value. UpDownCounters ([Int64UpDownCounter],
values that can increase and decrease. When more information needs to be
conveyed about all the synchronous measurements made during a collection cycle,
a Histogram ([Int64Histogram] and [Float64Histogram]) should be used. Finally,
when just the most recent measurement needs to be conveyed about an
asynchronous measurement, a Gauge ([Int64ObservableGauge] and
[Float64ObservableGauge]) should be used.
when just the most recent measurement needs to be conveyed, a Gauge
([Int64Gauge], [Float64Gauge], [Int64ObservableGauge], and
[Float64ObservableGauge]) should be used: the synchronous variants record an
instantaneous value at a specific point in code, while the observable variants
sample the value via a callback once per collection cycle.
See the [OpenTelemetry documentation] for more information about instruments
and their intended use.
@@ -80,11 +82,11 @@ Measurements are made by recording values and information about the values with
an instrument. How these measurements are recorded depends on the instrument.
Measurements for synchronous instruments ([Int64Counter], [Int64UpDownCounter],
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
[Float64Histogram]) are recorded using the instrument methods directly. All
counter instruments have an Add method that is used to measure an increment
value, and all histogram instruments have a Record method to measure a data
point.
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
[Float64Histogram], and [Float64Gauge]) are recorded using the instrument
methods directly. All counter instruments have an Add method that is used to
measure an increment value, and all histogram and synchronous gauge
instruments have a Record method to measure a data point.
Asynchronous instruments ([Int64ObservableCounter],
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
@@ -107,6 +109,31 @@ respectively):
If the criteria are not met, use the RegisterCallback method of the [Meter] that
created the instrument to register a [Callback].
# Avoiding Expensive Computations
All synchronous instruments provide an Enabled method that reports whether the
instrument will process measurements for the given context. When no SDK is
registered or the instrument is otherwise disabled, Enabled returns false. This
can be used to avoid expensive measurement work when a measurement will not be
recorded:
if counter.Enabled(ctx) {
counter.Add(ctx, 1, metric.WithAttributes(expensiveAttributes()...))
}
This is especially valuable when computing attributes is expensive.
[WithAttributes] performs non-trivial work on every call to build an
[attribute.Set] from the provided attributes, and that work is wasted if the
measurement is not recorded.
For performance sensitive code where the same attribute set is used repeatedly,
prefer [WithAttributeSet]. It accepts a pre-built [attribute.Set], letting you
pay the construction cost once and reuse it across many measurements:
attrs := attribute.NewSet(attribute.String("key", "val"))
// ... later, on each call:
counter.Add(ctx, 1, metric.WithAttributeSet(attrs))
# API Implementations
This package does not conform to the standard Go versioning policy, all of its
+33 -6
View File
@@ -3,7 +3,9 @@
package metric // import "go.opentelemetry.io/otel/metric"
import "go.opentelemetry.io/otel/attribute"
import (
"go.opentelemetry.io/otel/attribute"
)
// Observable is used as a grouping mechanism for all instruments that are
// updated within a Callback.
@@ -228,6 +230,9 @@ type AddConfig struct {
func NewAddConfig(opts []AddOption) AddConfig {
config := AddConfig{attrs: *attribute.EmptySet()}
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyAdd(config)
}
return config
@@ -253,6 +258,9 @@ type RecordConfig struct {
func NewRecordConfig(opts []RecordOption) RecordConfig {
config := RecordConfig{attrs: *attribute.EmptySet()}
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyRecord(config)
}
return config
@@ -278,6 +286,9 @@ type ObserveConfig struct {
func NewObserveConfig(opts []ObserveOption) ObserveConfig {
config := ObserveConfig{attrs: *attribute.EmptySet()}
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyObserve(config)
}
return config
@@ -299,6 +310,10 @@ type attrOpt struct {
set attribute.Set
}
func (o *attrOpt) Set(set attribute.Set) {
o.set = set
}
// mergeSets returns the union of keys between a and b. Any duplicate keys will
// use the value associated with b.
func mergeSets(a, b attribute.Set) attribute.Set {
@@ -311,7 +326,7 @@ func mergeSets(a, b attribute.Set) attribute.Set {
return attribute.NewSet(merged...)
}
func (o attrOpt) applyAdd(c AddConfig) AddConfig {
func (o *attrOpt) applyAdd(c AddConfig) AddConfig {
switch {
case o.set.Len() == 0:
case c.attrs.Len() == 0:
@@ -322,7 +337,7 @@ func (o attrOpt) applyAdd(c AddConfig) AddConfig {
return c
}
func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
func (o *attrOpt) applyRecord(c RecordConfig) RecordConfig {
switch {
case o.set.Len() == 0:
case c.attrs.Len() == 0:
@@ -333,7 +348,7 @@ func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
return c
}
func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
func (o *attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
switch {
case o.set.Len() == 0:
case c.attrs.Len() == 0:
@@ -350,8 +365,14 @@ func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
// If multiple WithAttributeSet or WithAttributes options are passed the
// attributes will be merged together in the order they are passed. Attributes
// with duplicate keys will use the last value passed.
//
// Experimental: The returned option may implement
// [go.opentelemetry.io/otel/metric/x.Settable][attribute.Set], which can be
// used to replace the option's attribute set and reuse the option without
// additional allocations. This behavior is experimental and may be changed or
// removed in a future release without notice.
func WithAttributeSet(attributes attribute.Set) MeasurementOption {
return attrOpt{set: attributes}
return &attrOpt{set: attributes}
}
// WithAttributes converts attributes into an attribute Set and sets the Set to
@@ -369,8 +390,14 @@ func WithAttributeSet(attributes attribute.Set) MeasurementOption {
//
// See [WithAttributeSet] for information about how multiple WithAttributes are
// merged.
//
// Experimental: The returned option may implement
// [go.opentelemetry.io/otel/metric/x.Settable][[]attribute.KeyValue], which can be
// used to replace the option's attributes and reuse the option without
// additional allocations. This behavior is experimental and may be changed or
// removed in a future release without notice.
func WithAttributes(attributes ...attribute.KeyValue) MeasurementOption {
cp := make([]attribute.KeyValue, len(attributes))
copy(cp, attributes)
return attrOpt{set: attribute.NewSet(cp...)}
return &attrOpt{set: attribute.NewSet(cp...)}
}
+12
View File
@@ -51,6 +51,9 @@ type Float64CounterConfig struct {
func NewFloat64CounterConfig(opts ...Float64CounterOption) Float64CounterConfig {
var config Float64CounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyFloat64Counter(config)
}
return config
@@ -116,6 +119,9 @@ type Float64UpDownCounterConfig struct {
func NewFloat64UpDownCounterConfig(opts ...Float64UpDownCounterOption) Float64UpDownCounterConfig {
var config Float64UpDownCounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyFloat64UpDownCounter(config)
}
return config
@@ -182,6 +188,9 @@ type Float64HistogramConfig struct {
func NewFloat64HistogramConfig(opts ...Float64HistogramOption) Float64HistogramConfig {
var config Float64HistogramConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyFloat64Histogram(config)
}
return config
@@ -251,6 +260,9 @@ type Float64GaugeConfig struct {
func NewFloat64GaugeConfig(opts ...Float64GaugeOption) Float64GaugeConfig {
var config Float64GaugeConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyFloat64Gauge(config)
}
return config
+12
View File
@@ -51,6 +51,9 @@ type Int64CounterConfig struct {
func NewInt64CounterConfig(opts ...Int64CounterOption) Int64CounterConfig {
var config Int64CounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyInt64Counter(config)
}
return config
@@ -116,6 +119,9 @@ type Int64UpDownCounterConfig struct {
func NewInt64UpDownCounterConfig(opts ...Int64UpDownCounterOption) Int64UpDownCounterConfig {
var config Int64UpDownCounterConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyInt64UpDownCounter(config)
}
return config
@@ -182,6 +188,9 @@ type Int64HistogramConfig struct {
func NewInt64HistogramConfig(opts ...Int64HistogramOption) Int64HistogramConfig {
var config Int64HistogramConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyInt64Histogram(config)
}
return config
@@ -251,6 +260,9 @@ type Int64GaugeConfig struct {
func NewInt64GaugeConfig(opts ...Int64GaugeOption) Int64GaugeConfig {
var config Int64GaugeConfig
for _, o := range opts {
if _, ok := o.(experimentalOption); ok {
continue
}
config = o.applyInt64Gauge(config)
}
return config
+60 -12
View File
@@ -5,6 +5,9 @@ package propagation // import "go.opentelemetry.io/otel/propagation"
import (
"context"
"errors"
"fmt"
"sync"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/internal/errorhandler"
@@ -13,11 +16,18 @@ import (
const (
baggageHeader = "baggage"
maxParseErrors = 5
// W3C Baggage specification limits.
// https://www.w3.org/TR/baggage/#limits
maxMembers = 64
maxMembers = 64
maxBytesPerBaggageString = 8192
)
// handleExtractErrOnce limits error reporting for attacker-controlled baggage headers
// to one process-wide emission, preventing repeated extraction from flooding logs.
var handleExtractErrOnce sync.Once
// Baggage is a propagator that supports the W3C Baggage format.
//
// This propagates user-defined baggage associated with a trace. The complete
@@ -57,7 +67,9 @@ func extractSingleBaggage(parent context.Context, carrier TextMapCarrier) contex
bag, err := baggage.Parse(bStr)
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
handleExtractErrOnce.Do(func() {
errorhandler.GetErrorHandler().Handle(err)
})
}
if bag.Len() == 0 {
return parent
@@ -72,24 +84,60 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
}
var members []baggage.Member
for _, bStr := range bVals {
currBag, err := baggage.Parse(bStr)
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
var totalBytes int
var parseErrors int
var truncateErr error
for i, bStr := range bVals {
if i > 0 {
totalBytes++ // comma separator between combined header values
}
if currBag.Len() == 0 {
continue
totalBytes += len(bStr)
if totalBytes > maxBytesPerBaggageString {
// Per the W3C Baggage spec, the byte limit applies to the
// combination of all baggage headers, not each header
// individually. Mirror the single-header behavior of
// reporting the error and returning the parent context
// with no baggage attached.
handleExtractErrOnce.Do(func() {
errorhandler.GetErrorHandler().Handle(fmt.Errorf(
"baggage: aggregate header size %d exceeds %d byte limit",
totalBytes,
maxBytesPerBaggageString,
))
})
return parent
}
members = append(members, currBag.Members()...)
if len(members) >= maxMembers {
break
// If members exceed the limit, stop parsing baggage.
if len(members) <= maxMembers {
currBag, err := baggage.Parse(bStr)
if err != nil {
parseErrors++
if parseErrors <= maxParseErrors {
truncateErr = errors.Join(truncateErr, err)
}
}
if currBag.Len() == 0 {
continue
}
members = append(members, currBag.Members()...)
}
}
if dropped := parseErrors - maxParseErrors; dropped > 0 {
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more error(s)", dropped))
}
b, err := baggage.New(members...)
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
truncateErr = errors.Join(truncateErr, err)
}
if truncateErr != nil {
handleExtractErrOnce.Do(func() {
errorhandler.GetErrorHandler().Handle(truncateErr)
})
}
if b.Len() == 0 {
return parent
}
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
)
type (
@@ -79,7 +79,7 @@ func (sd stringDetector) Detect(context.Context) (*Resource, error) {
}
a := sd.K.String(value)
if !a.Valid() {
return nil, fmt.Errorf("invalid attribute: %q -> %q", a.Key, a.Value.Emit())
return nil, fmt.Errorf("invalid attribute: %q -> %q", a.Key, a.Value.String())
}
return NewWithAttributes(sd.schemaURL, sd.K.String(value)), nil
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"os"
"regexp"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
)
type containerIDProvider func() (string, error)
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
)
const (
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"errors"
"strings"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
)
type hostIDProvider func() (string, error)
+5 -2
View File
@@ -5,10 +5,13 @@
package resource // import "go.opentelemetry.io/otel/sdk/resource"
import "os/exec"
import (
"context"
"os/exec"
)
func execCommand(name string, arg ...string) (string, error) {
cmd := exec.Command(name, arg...)
cmd := exec.CommandContext(context.Background(), name, arg...)
b, err := cmd.Output()
if err != nil {
return "", err
Loaded 100 of 273 files, more files were not shown because too many files have changed in this diff. Show more