mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-08 11:53:07 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75bc05ae02 | ||
|
|
1727aa1162 |
No files matched your search
@@ -1,153 +0,0 @@
|
||||
---
|
||||
name: bumping-reva
|
||||
description: Use when the user asks to bump, update, or upgrade the OpenCloud reva dependency to a specific version (e.g. "reva bump to 2.48.0", "bump reva to v2.48.0"). Covers editing go.mod, re-vendoring, bumping the OpenCloud LatestTag, the single commit, and opening the PR against main.
|
||||
---
|
||||
|
||||
# Bumping Reva
|
||||
|
||||
## Overview
|
||||
|
||||
Bumping reva updates the pinned [opencloud-eu/reva](https://github.com/opencloud-eu/reva) dependency (`github.com/opencloud-eu/reva/v2`) to a tagged release, re-vendors the module graph, and bumps the OpenCloud dev version. It touches `go.mod`, `go.sum`, `vendor/**` and `pkg/version/version.go`, lands as one commit, and ships as a PR whose body is the reva changelog for that version.
|
||||
|
||||
Template PR: https://github.com/opencloud-eu/opencloud/pull/3127
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **The reva release must already be tagged.** A reva release is cut by merging its release PR (title `🎉 Release X.Y.Z`, branch `next-release/main`). The **user merges that PR themselves** — this skill starts *after* the tag `vX.Y.Z` exists. Verify the tag before doing anything (step 1); if it's missing, stop and ask the user to merge the reva release PR first.
|
||||
- **`gh` (GitHub CLI), authenticated** — tag lookup and PR creation go through `gh api` / `gh pr`. Verify with `gh auth status`; if it fails, ask the user to run `gh auth login` (suggest `! gh auth login` so it runs in-session).
|
||||
- `gh` needs the system keyring, so run all `gh` commands with the sandbox disabled.
|
||||
- **Go toolchain + network** — `go get` / `go mod tidy` / `go mod vendor` hit the Go module proxy. Run them with the sandbox disabled (network access).
|
||||
- `git` and `base64` (decoding the changelog) — standard on macOS/Linux.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `REVA_VERSION` — the new reva tag, always normalized to a leading `v` (e.g. `v2.48.0`). If the user didn't give it, ask (or take the latest reva release tag).
|
||||
- `OC_VERSION` — the OpenCloud target for `LatestTag`, **without** the `+dev` suffix (e.g. `7.4.0`). **Do not ask or guess** — derive it from the open OpenCloud release PR (step 3). It can be a new major (e.g. `8.0.0`) when release-please picked up a breaking change.
|
||||
|
||||
## What changes
|
||||
|
||||
| File | What |
|
||||
| ------------------------ | ------------------------------------------------------------------- |
|
||||
| `go.mod` | `github.com/opencloud-eu/reva/v2` → `REVA_VERSION` (+ indirect deps pulled by `go mod tidy`) |
|
||||
| `go.sum` | updated by `go get` / `go mod tidy` |
|
||||
| `vendor/**` | re-vendored by `go mod vendor` (incl. `vendor/modules.txt`) |
|
||||
| `pkg/version/version.go` | `LatestTag = "<OC_VERSION>+dev"` |
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Verify the reva tag exists
|
||||
|
||||
```bash
|
||||
gh api repos/opencloud-eu/reva/commits/$REVA_VERSION --jq '.sha'
|
||||
```
|
||||
|
||||
- Success (a sha) → the release is tagged, continue.
|
||||
- `422`/`404` → the tag does not exist yet. The reva release PR (`🎉 Release X.Y.Z`) is probably not merged. **Stop** and tell the user to merge it first. Helpful checks:
|
||||
```bash
|
||||
gh api repos/opencloud-eu/reva/releases/latest --jq '.tag_name' # current latest tag
|
||||
gh pr list --repo opencloud-eu/reva --search '🎉 Release in:title' --state open --json number,title
|
||||
```
|
||||
|
||||
### 2. Bump the dependency and re-vendor
|
||||
|
||||
```bash
|
||||
go get github.com/opencloud-eu/reva/v2@$REVA_VERSION
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
```
|
||||
|
||||
(Sandbox disabled — these need network.) Notes:
|
||||
- Right after a fresh tag the module proxy can lag; if `go get` reports the version as unknown, retry, or use `GOPROXY=direct go get github.com/opencloud-eu/reva/v2@$REVA_VERSION`.
|
||||
- `go mod tidy` will also bump indirect dependencies that reva pulled in — that is expected (the template PR did the same).
|
||||
|
||||
### 3. Bump the OpenCloud dev version
|
||||
|
||||
`OC_VERSION` is the version of the **open OpenCloud release PR** — the release-please PR from branch `next-release/main`, titled `🎉 Release X.Y.Z` (e.g. #3143). Derive it, don't ask:
|
||||
|
||||
```bash
|
||||
gh pr list --repo opencloud-eu/opencloud --head next-release/main --state open \
|
||||
--json title --jq '.[0].title' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1
|
||||
```
|
||||
|
||||
This is the next release target and tracks breaking changes — it may be a new major (e.g. `8.0.0`), not just a minor bump. If no such PR is open, stop and ask the user.
|
||||
|
||||
Edit `pkg/version/version.go`:
|
||||
|
||||
```go
|
||||
LatestTag = "<OC_VERSION>+dev" // e.g. "7.4.0+dev"
|
||||
```
|
||||
|
||||
### 4. Fetch the reva changelog for the PR body
|
||||
|
||||
```bash
|
||||
gh api "repos/opencloud-eu/reva/contents/CHANGELOG.md?ref=$REVA_VERSION" --jq '.content' | base64 -d
|
||||
```
|
||||
|
||||
Take **only the section for this version** and trim it exactly like the web bump: start at the first content heading (`### 🐛 Bug Fixes` / `### 📈 Enhancement` / `### 💥 Breaking changes`), drop the `# Changelog` title, the `## [x.y.z] - date` header and the `### ❤️ Thanks to all contributors!` block, and stop before the next `## [...]` version header.
|
||||
|
||||
Then **prepend two summary bullets** so the final PR body is:
|
||||
|
||||
```
|
||||
- bump opencloud version to <OC_VERSION>
|
||||
- reva bump <REVA_VERSION without the leading v>
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
...trimmed reva changelog...
|
||||
```
|
||||
|
||||
Write this to a file for `--body-file` (e.g. in the scratchpad).
|
||||
|
||||
### 5. Confirm before committing
|
||||
|
||||
The `vendor/` diff is huge — do **not** dump it. Show the user:
|
||||
|
||||
```bash
|
||||
git diff go.mod pkg/version/version.go # the meaningful edits
|
||||
git diff --stat | tail -1 # vendor churn summary
|
||||
```
|
||||
|
||||
Confirm the reva line in `go.mod` is exactly `github.com/opencloud-eu/reva/v2 REVA_VERSION`, show the target branch (`main`), and the PR body. **Do not commit until the user approves.**
|
||||
|
||||
### 6. Commit, push, open PR
|
||||
|
||||
- Create a branch (do not commit on `main`), e.g. `reva-bump-2.48.0`.
|
||||
- Stage everything the bump touched: `git add go.mod go.sum vendor pkg/version/version.go`.
|
||||
- One commit, conventional-commits format, **empty body**:
|
||||
```
|
||||
chore: bump reva to 2.48.0
|
||||
```
|
||||
- PR base: **`main`** (reva bumps always target main).
|
||||
- PR title: `[full-ci] chore: bump reva to 2.48.0` (the commit message prefixed with `[full-ci] `).
|
||||
- PR body: the file from step 4.
|
||||
- Add the label `Type:Maintenance`.
|
||||
|
||||
```bash
|
||||
gh pr create --base main \
|
||||
--title "[full-ci] chore: bump reva to $REVA_VERSION_NO_V" \
|
||||
--label "Type:Maintenance" \
|
||||
--body-file <body-file>
|
||||
```
|
||||
|
||||
(`gh` commands need the sandbox disabled — they require the system keyring.)
|
||||
|
||||
## Quick reference
|
||||
|
||||
```bash
|
||||
REVA_VERSION=v2.48.0
|
||||
gh api repos/opencloud-eu/reva/commits/$REVA_VERSION --jq '.sha' # verify tag exists
|
||||
OC_VERSION=$(gh pr list --repo opencloud-eu/opencloud --head next-release/main --state open \
|
||||
--json title --jq '.[0].title' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) # from open release PR
|
||||
go get github.com/opencloud-eu/reva/v2@$REVA_VERSION && go mod tidy && go mod vendor # bump + re-vendor
|
||||
# edit pkg/version/version.go -> LatestTag = "$OC_VERSION+dev"
|
||||
gh api "repos/opencloud-eu/reva/contents/CHANGELOG.md?ref=$REVA_VERSION" --jq '.content' | base64 -d # changelog
|
||||
```
|
||||
|
||||
## Common mistakes
|
||||
|
||||
- Running the bump before the reva release PR is merged — the tag won't exist and `go get` will fail. Verify the tag first (step 1).
|
||||
- Forgetting to re-run `go mod vendor` after `go mod tidy`, leaving `vendor/` out of sync with `go.mod`.
|
||||
- Asking for or hardcoding `OC_VERSION` — always derive it from the open `next-release/main` release PR (step 3); it can even be a new major after a breaking change.
|
||||
- Reading the changelog from reva `main` instead of the tag (`?ref=$REVA_VERSION`). Always pin to the tag.
|
||||
- Missing the `[full-ci] ` prefix in the PR title, the `Type:Maintenance` label, or the two summary bullets at the top of the body.
|
||||
- Dumping the full `vendor/` diff at the confirmation step instead of `go.mod` + `version.go` + a `--stat` summary.
|
||||
- Committing before the user confirms the diff.
|
||||
@@ -1,118 +0,0 @@
|
||||
---
|
||||
name: bumping-web
|
||||
description: Use when the user asks to bump, update, or upgrade the OpenCloud web assets/frontend to a specific version (e.g. "bump web to v7.0.0", "update web to v7.1.2"). Covers editing the version files, the single commit, and opening the PR against the right branch.
|
||||
---
|
||||
|
||||
# Bumping Web
|
||||
|
||||
## Overview
|
||||
|
||||
Bumping web updates the pinned OpenCloud web frontend (assets + UI-test runner) to a tagged release of [opencloud-eu/web](https://github.com/opencloud-eu/web). It touches exactly two files, lands as one commit, and ships as a PR whose body is the web changelog for that version.
|
||||
|
||||
Template PR: https://github.com/opencloud-eu/opencloud/pull/2733
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **`gh` (GitHub CLI), authenticated** — every lookup and the PR creation go through `gh api` / `gh pr`. Verify with `gh auth status`; if it fails, ask the user to run `gh auth login` (suggest `! gh auth login` so it runs in-session). Do not proceed without it.
|
||||
- `gh` needs the system keyring, so run all `gh` commands with the sandbox disabled.
|
||||
- `git` and `base64` (decoding the changelog) — standard on macOS/Linux.
|
||||
|
||||
## What changes (exactly two files)
|
||||
|
||||
| File | Variable | New value |
|
||||
| ----------------------- | -------------------- | ------------------------------------------------ |
|
||||
| `services/web/Makefile` | `WEB_ASSETS_VERSION` | the version tag, e.g. `v7.0.0` |
|
||||
| `services/web/Makefile` | `WEB_ASSETS_BRANCH` | branch carrying the tag (`main` or `stable-X.Y`) |
|
||||
| `.woodpecker.env` | `WEB_COMMITID` | full commit sha the tag points to |
|
||||
| `.woodpecker.env` | `WEB_BRANCH` | same branch as `WEB_ASSETS_BRANCH` |
|
||||
|
||||
`WEB_ASSETS_BRANCH` and `WEB_BRANCH` are always the same value.
|
||||
|
||||
## Procedure
|
||||
|
||||
Let `VERSION` be the requested tag (always normalize to a leading `v`, e.g. `v7.0.0`).
|
||||
|
||||
### 1. Resolve the commit sha the tag points to
|
||||
|
||||
```bash
|
||||
gh api repos/opencloud-eu/web/commits/$VERSION --jq '.sha'
|
||||
```
|
||||
|
||||
This full sha is the new `WEB_COMMITID`.
|
||||
|
||||
### 2. Determine the branch (`main` vs `stable-X.Y`)
|
||||
|
||||
The branch is the line of development that carries the tag. Detect it:
|
||||
|
||||
```bash
|
||||
gh api "repos/opencloud-eu/web/compare/main...$VERSION" --jq '.status'
|
||||
```
|
||||
|
||||
- `identical` or `behind` → the tagged commit is reachable from `main` → use **`main`**.
|
||||
- `ahead` or `diverged` → the tag lives on a release line → use **`stable-X.Y`** matching the version's major.minor (e.g. `v7.1.2` → `stable-7.1`).
|
||||
|
||||
Sanity-check that the stable branch actually exists:
|
||||
|
||||
```bash
|
||||
gh api repos/opencloud-eu/web/branches --paginate --jq '.[].name' | grep -E 'main|stable'
|
||||
```
|
||||
|
||||
Typically a freshly released minor (`v7.1.0`) still sits on `main`, while later patches on an older line (`v7.0.3` after `7.1` exists) sit on `stable-7.0`.
|
||||
|
||||
### 3. Fetch the changelog for the PR body
|
||||
|
||||
```bash
|
||||
gh api "repos/opencloud-eu/web/contents/CHANGELOG.md?ref=$VERSION" --jq '.content' | base64 -d
|
||||
```
|
||||
|
||||
Take **only the section for this version**. Match the template: start the PR body at the first content heading (e.g. `### 💥 Breaking changes` / `### 📈 Enhancement`) and drop the `# Changelog` title, the `## [x.y.z] - date` header, and the `### ❤️ Thanks to all contributors!` block. Stop before the next `## [...]` version header.
|
||||
|
||||
### 4. Apply the edits
|
||||
|
||||
Edit `services/web/Makefile` (`WEB_ASSETS_VERSION`, `WEB_ASSETS_BRANCH`) and `.woodpecker.env` (`WEB_COMMITID`, `WEB_BRANCH`).
|
||||
|
||||
### 5. Pick the target branch for the PR
|
||||
|
||||
- Major or minor release → target **`main`**.
|
||||
- Patch release → may need to target a stable branch instead. If the user did not specify, **ask them** which branch to target before continuing.
|
||||
|
||||
### 6. Confirm before committing
|
||||
|
||||
Show the user the diff of both files and the target branch, and ask them to confirm. Do not commit until they approve.
|
||||
|
||||
### 7. Commit, push, open PR
|
||||
|
||||
- Create a branch (do not commit on `main`).
|
||||
- One commit, conventional-commits format, **empty body**:
|
||||
```
|
||||
chore: bump web to v7.0.0
|
||||
```
|
||||
- PR title: `[full-ci] chore: bump web to v7.0.0` (the commit message prefixed with `[full-ci] `).
|
||||
- PR base: the branch chosen in step 5.
|
||||
- PR body: the trimmed changelog from step 3.
|
||||
- Add the label `Type:Dependencies`.
|
||||
|
||||
```bash
|
||||
gh pr create --base <target-branch> \
|
||||
--title "[full-ci] chore: bump web to $VERSION" \
|
||||
--label "Type:Dependencies" \
|
||||
--body-file <changelog-file>
|
||||
```
|
||||
|
||||
(`gh` commands need the sandbox disabled — they require the system keyring.)
|
||||
|
||||
## Quick reference
|
||||
|
||||
```bash
|
||||
VERSION=v7.0.0
|
||||
gh api repos/opencloud-eu/web/commits/$VERSION --jq '.sha' # WEB_COMMITID
|
||||
gh api "repos/opencloud-eu/web/compare/main...$VERSION" --jq '.status' # main vs stable
|
||||
gh api "repos/opencloud-eu/web/contents/CHANGELOG.md?ref=$VERSION" --jq '.content' | base64 -d # changelog
|
||||
```
|
||||
|
||||
## Common mistakes
|
||||
|
||||
- Reading `WEB_COMMITID` or the changelog from web `main` instead of from the version tag (`?ref=$VERSION`). Always pin to the tag.
|
||||
- Leaving `WEB_ASSETS_BRANCH`/`WEB_BRANCH` on `main` for a patch that belongs on a stable line.
|
||||
- Forgetting `[full-ci] ` in the PR title or adding a commit body.
|
||||
- Committing before the user confirms the diff and target branch.
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"repoOwner": "opencloud-eu",
|
||||
"repoName": "opencloud",
|
||||
"targetBranchChoices": ["main", "stable-2.0", "stable-4.0"],
|
||||
"fork": false
|
||||
}
|
||||
+4
-10
@@ -65,12 +65,6 @@ $(GOVULNCHECK): $(BINGO_DIR)/govulncheck.mod
|
||||
@echo "(re)installing $(GOBIN)/govulncheck-v1.1.4"
|
||||
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=govulncheck.mod -o=$(GOBIN)/govulncheck-v1.1.4 "golang.org/x/vuln/cmd/govulncheck"
|
||||
|
||||
GOWRAP := $(GOBIN)/gowrap-v1.4.3
|
||||
$(GOWRAP): $(BINGO_DIR)/gowrap.mod
|
||||
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
|
||||
@echo "(re)installing $(GOBIN)/gowrap-v1.4.3"
|
||||
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=gowrap.mod -o=$(GOBIN)/gowrap-v1.4.3 "github.com/hexdigest/gowrap/cmd/gowrap"
|
||||
|
||||
MOCKERY := $(GOBIN)/mockery-v3.4.0
|
||||
$(MOCKERY): $(BINGO_DIR)/mockery.mod
|
||||
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
|
||||
@@ -89,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.6.2
|
||||
$(PROTOC_GEN_GO_GRPC): $(BINGO_DIR)/protoc-gen-go-grpc.mod
|
||||
PROTOC_GEN_DOC := $(GOBIN)/protoc-gen-doc-v1.5.1
|
||||
$(PROTOC_GEN_DOC): $(BINGO_DIR)/protoc-gen-doc.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.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"
|
||||
@echo "(re)installing $(GOBIN)/protoc-gen-doc-v1.5.1"
|
||||
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=protoc-gen-doc.mod -o=$(GOBIN)/protoc-gen-doc-v1.5.1 "github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc"
|
||||
|
||||
PROTOC_GEN_GO := $(GOBIN)/protoc-gen-go-v1.28.1
|
||||
$(PROTOC_GEN_GO): $(BINGO_DIR)/protoc-gen-go.mod
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
|
||||
|
||||
go 1.25.8
|
||||
|
||||
require github.com/hexdigest/gowrap v1.4.3 // cmd/gowrap
|
||||
@@ -1,55 +0,0 @@
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hexdigest/gowrap v1.4.3 h1:m+t8aj1pUiFQbEiE8QJg2xdYVH5DAMluLgZ9P/qEF0k=
|
||||
github.com/hexdigest/gowrap v1.4.3/go.mod h1:XWL8oQW2H3fX5ll8oT3Fduh4mt2H3cUAGQHQLMUbmG4=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0=
|
||||
github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
|
||||
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -0,0 +1,5 @@
|
||||
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/pseudomuto/protoc-gen-doc v1.5.1 // cmd/protoc-gen-doc
|
||||
@@ -0,0 +1,169 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.15.0+incompatible h1:0gSxPGWS9PAr7U2NsQ2YQg6juRDINkUyuvbb4b2Xm8w=
|
||||
github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
|
||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0-java h1:bV5JGEB1ouEzZa0hgVDFFiClrUEuGWRaAc/3mxR2QK0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c h1:jWtZjFEUE/Bz0IeIhqCnyZ3HG6KRXSntXe4SjtuTH7c=
|
||||
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/huandu/xstrings v1.0.0 h1:pO2K/gKgKaat5LdpAhxhluX2GPQMaI3W5FUz/I/UnWk=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/imdario/mergo v0.3.4 h1:mKkfHkZWD8dC7WxKx3N9WCF0Y+dLau45704YQmY6H94=
|
||||
github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007 h1:28i1IjGcx8AofiB4N3q5Yls55VEaitzuEPkFJEVgGkA=
|
||||
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/pseudomuto/protoc-gen-doc v1.5.0 h1:pHZp0MEiT68jrZV8js8BS7E9ZEnlSLegoQbbtXj5lfo=
|
||||
github.com/pseudomuto/protoc-gen-doc v1.5.0/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg=
|
||||
github.com/pseudomuto/protoc-gen-doc v1.5.1 h1:Ah259kcrio7Ix1Rhb6u8FCaOkzf9qRBqXnvAufg061w=
|
||||
github.com/pseudomuto/protoc-gen-doc v1.5.1/go.mod h1:XpMKYg6zkcpgfpCfQ8GcWBDRtRxOmMR5w7pz4Xo+dYM=
|
||||
github.com/pseudomuto/protokit v0.2.0 h1:hlnBDcy3YEDXH7kc9gV+NLaN0cDzhDvD1s7Y6FZ8RpM=
|
||||
github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307 h1:O5C+XK++apFo5B+Vq4ujc/LkLwHxg9fDdgjgoIikBdA=
|
||||
golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362 h1:b69RmkJsx8NyRJsKF2mQ/AF8s4BNxwNsT4rQ3wON1U0=
|
||||
google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w=
|
||||
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1,5 +0,0 @@
|
||||
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2
|
||||
@@ -1,6 +0,0 @@
|
||||
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
|
||||
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
@@ -24,15 +24,13 @@ GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.64.6"
|
||||
|
||||
GOVULNCHECK="${GOBIN}/govulncheck-v1.1.4"
|
||||
|
||||
GOWRAP="${GOBIN}/gowrap-v1.4.3"
|
||||
|
||||
MOCKERY="${GOBIN}/mockery-v3.4.0"
|
||||
|
||||
MUTAGEN="${GOBIN}/mutagen-v0.18.1"
|
||||
|
||||
PIGEON="${GOBIN}/pigeon-v1.3.0"
|
||||
|
||||
PROTOC_GEN_GO_GRPC="${GOBIN}/protoc-gen-go-grpc-v1.6.2"
|
||||
PROTOC_GEN_DOC="${GOBIN}/protoc-gen-doc-v1.5.1"
|
||||
|
||||
PROTOC_GEN_GO="${GOBIN}/protoc-gen-go-v1.28.1"
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../.agents/skills
|
||||
+2
-13
@@ -1,7 +1,6 @@
|
||||
---
|
||||
exclude_paths:
|
||||
- '.github/**'
|
||||
- '.agents/**'
|
||||
- 'CHANGELOG.md'
|
||||
- '**/CHANGELOG.md'
|
||||
- 'changelog/**'
|
||||
@@ -9,25 +8,15 @@ exclude_paths:
|
||||
- 'docs/**'
|
||||
- '**/docs/**'
|
||||
- '**/pkg/proto/**'
|
||||
- 'protogen/**'
|
||||
- 'scripts/**'
|
||||
- 'idp/ui_config/**'
|
||||
- 'idp/scripts/**'
|
||||
- 'idp/src/**'
|
||||
- 'devtools/**'
|
||||
- 'settings/rollup.config.js'
|
||||
- 'accounts/rollup.config.js'
|
||||
- 'deployments/**'
|
||||
- "release-config.ts"
|
||||
- 'tests/acceptance/expected-failures-*.md'
|
||||
# written by the search engine parity suite, table rows are as wide as they are
|
||||
- 'services/search/pkg/parity/README.md'
|
||||
- 'tests/acceptance/bootstrap/**'
|
||||
- 'tests/acceptance/TestHelpers/**'
|
||||
- 'tests/acceptance/scripts/run.sh'
|
||||
- 'vendor/**/*'
|
||||
- 'vendor-bin/**'
|
||||
- 'tests/ocwrapper/vendor/**'
|
||||
- '**/mocks/**'
|
||||
- '**/pkg/config/**'
|
||||
- '**/pkg/metrics/**'
|
||||
- '**/pkg/revaconfig/**'
|
||||
...
|
||||
@@ -1 +0,0 @@
|
||||
github: opencloud-eu
|
||||
@@ -4,12 +4,11 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 4
|
||||
open-pull-requests-limit: 2
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/services/idp"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 4
|
||||
cooldown:
|
||||
default-days: 1
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 2
|
||||
|
||||
+64
-68
@@ -3,83 +3,79 @@
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* [ ] DEV/QA: bump web version
|
||||
* [ ] DEV/QA: bump reva version
|
||||
* [ ] DEV/QA: DEV: Create rc tag `vx.y.z-rc.x`
|
||||
* [ ] DEV: update introductionVersion
|
||||
* [ ] DEV: add new production version
|
||||
* [ ] DEV/QA: Kickoff meeting [Kickoff meeting] (https://???)
|
||||
* [ ] DEV/QA: Define client versions and provide list of breaking changes for desktop/mobile team
|
||||
* [ ] DEV/QA: Check new strings and align with clients
|
||||
* [ ] DEV/DOCS: Create list of pending docs tasks
|
||||
* [ ] DEV: Create branch `release-x.x.x-rc.x` -> CODEFREEZE
|
||||
* [ ] DEV: bump opencloud version in necessary files
|
||||
* [ ] DEV: `changelog/CHANGELOG.tmpl`
|
||||
* [ ] DEV: `pkg/version/version.go`
|
||||
* [ ] DEV: `sonar-project.properties`
|
||||
* [ ] DEV: prepare changelog folder in `changelog/x.x.x_????_??_??`
|
||||
* [ ] DEV: Check successful CI run on release branch
|
||||
* [ ] DEV: Create signed tag `vx.y.z-rc.x`
|
||||
* [ ] DEV: Check successful CI run on `vx.y.z-rc.x` tag / BLOCKING for all further activity
|
||||
* [ ] DEV: Merge back release branch
|
||||
* [ ] DEV: bump released deployments to `vx.y.z-rc.x`
|
||||
* [ ] DEV: https://cloud.opencloud.eu/
|
||||
* [ ] DEV: needs snapshot and migration
|
||||
|
||||
### QA Phase
|
||||
|
||||
* [ ] QA: Compatibility test with posix fs
|
||||
* [ ] QA: Compatibility test with decomposed fs
|
||||
* [ ] DEV/QA: Performance test
|
||||
* [ ] STORAGE_USERS_DRIVER=posix
|
||||
* [ ] 75vu's, 60m
|
||||
* [ ] 75vu's, 60m
|
||||
* [ ] STORAGE_USERS_DRIVER=decomposed
|
||||
* [ ] 75vu's, 60m
|
||||
* [ ] 75vu's, 60m
|
||||
* [ ] QA: documentation test
|
||||
* [ ] QA: Review documentation
|
||||
* [ ] QA: Verify all new features documented
|
||||
* [ ] QA: Create upgrade documentation
|
||||
* [ ] QA: Check installation guides
|
||||
|
||||
* [ ] QA: Confirmatory testing (if needed)
|
||||
* [ ] QA: [Compatibility test](???)
|
||||
* [ ] QA: [Performance test](https://github.com/opencloud-eu/cdperf/tree/main/packages/k6-tests/src)
|
||||
* [ ] QA: Documentation test:
|
||||
* [ ] QA: Single binary - setup
|
||||
* [ ] QA: Docker - setup
|
||||
* [ ] QA: Docker-compose - setup
|
||||
* [ ] QA: helm/k8s - setup
|
||||
* [ ] QA: e2e with different deployment:
|
||||
* [ ] QA: [wopi](???.works)
|
||||
* [ ] QA: [traefik](???.works)
|
||||
* [ ] QA: [ldap](???.works)
|
||||
* [ ] QA: e2e with different storage:
|
||||
* [ ] QA: decomposed
|
||||
* [ ] QA: decomposeds3
|
||||
* [ ] QA: posix
|
||||
* [ ] QA: posix with enabled watch_fs
|
||||
* [ ] QA: e2e with different deployments deployments:
|
||||
* [ ] QA: e2e tests agains opencloud-charts
|
||||
* [ ] QA: binary
|
||||
* [ ] QA: multitanacy
|
||||
* [ ] QA: docker using [docker-compose_test_plan](https://github.com/opencloud-eu/qa/blob/main/.github/ISSUE_TEMPLATE/docker-compose_test_plan_template.md)
|
||||
* [ ] QA: local
|
||||
* [ ] QA: nfs
|
||||
* [ ] QA: s3
|
||||
* [ ] QA: Different clients:
|
||||
* [ ] QA: desktop (define version) https://github.com/opencloud-eu/client/releases
|
||||
* [ ] QA: against mac - exploratory testing
|
||||
* [ ] QA: against windows - exploratory testing
|
||||
* [ ] QA: against mac - smoke test
|
||||
* [ ] QA: against windows - smoke test
|
||||
* [ ] QA: against linux (use auto tests)
|
||||
* [ ] QA: android (define version) https://github.com/opencloud-eu/android/releases
|
||||
* [ ] QA: ios (define version)
|
||||
* [ ] QA: check docs german translation
|
||||
* [ ] QA: german translations desktop at 100%
|
||||
* [ ] QA: exploratory testing
|
||||
* [ ] QA: [Smoke test](???) on Web Office (Collabora, Onlyoffice, Microsoft office)
|
||||
* [ ] QA: Smoke test Hello extension
|
||||
* [ ] QA: [Smoke test](???) ldap
|
||||
* [ ] QA: Collecting errors found
|
||||
|
||||
### Collected bugs
|
||||
* [ ] Please place all bugs found here
|
||||
### After QA Phase
|
||||
|
||||
### After QA Phase (IT related)
|
||||
* [ ] Brief company-wide heads up via mail @tbsbdr
|
||||
* [ ] Create list of changed ENV vars and send to release-coordination@opencloud.eu
|
||||
* [ ] Variable Name
|
||||
* [ ] Introduced in version
|
||||
* [ ] Default Value
|
||||
* [ ] Description
|
||||
* [ ] dependencies with user other components
|
||||
* [ ] DEV: Create branch `release-x.x.x`
|
||||
* [ ] DEV: bump OpenCloud version in necessary files
|
||||
* [ ] DEV: `pkg/version/version.go`
|
||||
* [ ] DEV: `sonar-project.properties`
|
||||
* [ ] DEV: released deployment versions
|
||||
* [ ] DEV: prepare changelog folder in `changelog/x.x.x_???`
|
||||
* [ ] Release Notes + Breaking Changes @tbsbdr
|
||||
* [ ] Migration + Breaking Changes Admin Doc @???
|
||||
* [ ] DEV: Create final signed tag
|
||||
* [ ] DEV: Check successful CI run on `vx.y.z` tag / BLOCKING for all further activity
|
||||
* [ ] Merge release notes
|
||||
|
||||
* [ ] QA:bump version in pkg/version.go
|
||||
* [ ] QA: Run CI
|
||||
* [ ] DEV/QA: create final tag
|
||||
* [ ] QA: observe CI Run on tag
|
||||
* [ ] DEV/QA: Create a new `stable-*` branch
|
||||
* [ ] (opencloud)[https://github.com/opencloud-eu/opencloud/branches]
|
||||
* [ ] (web)[https://github.com/opencloud-eu/web/branches]
|
||||
* [ ] (reva)[https://github.com/opencloud-eu/reva/branches]
|
||||
* [ ] (opencloud-compose)[https://github.com/opencloud-eu/opencloud-compose/branches]
|
||||
* [ ] DEV/QA:: publish release notes to the docs
|
||||
* [ ] DEV/QA:: update (demo.opencloud.eu)[https://demo.opencloud.eu/]
|
||||
|
||||
### After QA Phase ( Marketing / Product / Sales related )
|
||||
|
||||
* [ ] notify marketing that the release is ready @tbsbdr
|
||||
* [ ] announce in the public channel (matrix channel)[https://matrix.to/#/#opencloud:matrix.org]
|
||||
* [ ] press information @AnneGo137
|
||||
* [ ] press information @AnneGo137
|
||||
* [ ] Blogentry @AnneGo137
|
||||
* [ ] Internal meeting (Groupe Pre-Webinar) @db-ot
|
||||
* [ ] Partner briefing (Partner should be informed about features, new) @matthias
|
||||
* [ ] Webinar DE & EN @AnneGo137
|
||||
* [ ] Präsentation DE @tbsbdr / @db-ot
|
||||
* [ ] Präsentation EN @tbsbdr / @db-ot
|
||||
* [ ] Website ergänzen @AnneGo137
|
||||
* [ ] Features @AnneGo137
|
||||
* [ ] Service & Support - New Enterprise Features @tbsbdr
|
||||
* [ ] OpenCloud_Benefits.pdf updates @AnneGo137
|
||||
* [ ] Welcome Files: Features as media @tbsbdr
|
||||
* [ ] Flyer update @AnneGo137
|
||||
* [ ] Sales presentation @matthias
|
||||
### Post-release communication
|
||||
* [ ] DEV: Create a `docs-stable-x.y` branch based on the docs folder in the OpenCloud repo @micbar
|
||||
* [ ] DEV/QA: Ping documentation in RC about the new release tag (for opencloud/helm chart version bump in docs)
|
||||
* [ ] DEV/QA: Ping marketing to update all download links (download mirrors are updated at the full hour, wait with ping until download is actually available)
|
||||
* [ ] DEV/QA: Ping @??? once the demo instances are running this release
|
||||
* [ ] DEV: Merge back release branch
|
||||
* [ ] DEV: Create stable-x.y branch in the OpenCloud repo from final tag
|
||||
@@ -1,32 +0,0 @@
|
||||
### Rolling release template
|
||||
[Release Template](https://github.com/opencloud-eu/opencloud/blob/main/.github/rolling_release_template.md)
|
||||
|
||||
## Prerequisites
|
||||
* [ ] replace `%%NEXT%%` with the release version
|
||||
|
||||
* [ ] web release
|
||||
* [ ] bump web version
|
||||
* [ ] squash and merge the web release PR
|
||||
* [ ] bump web v.x.y.z in opencloud
|
||||
|
||||
* [ ] reva release
|
||||
* [ ] squash and merge the reva Release PR
|
||||
* [ ] bump reva and update opencloud version in `pkg/version.go`
|
||||
|
||||
## QA Phase
|
||||
* [ ] bump `opencloud_commitid` in web and run all working tests in CI
|
||||
* [ ] compatibility test
|
||||
* [ ] confirmatory testing, if needed
|
||||
* [ ] squash and merge Release PR
|
||||
|
||||
## Collected bugs
|
||||
|
||||
## After QA Phase
|
||||
* [ ] publish release notes to the docs
|
||||
* [ ] add migration guide to changelog with prefix `**ACTION REQUIRED:**`, if needed.
|
||||
* [ ] add release notes from web and reva to opencloud changelog
|
||||
* [ ] n8n integration - update new opencloud version
|
||||
* [ ] docker-compose - update new opencloud version
|
||||
* [ ] update the public matrix channel topic
|
||||
* [ ] update https://update.opencloud.eu/server.json
|
||||
* [ ] update the version on demo.opencloud.eu
|
||||
@@ -1,2 +1,4 @@
|
||||
_extends: gh-labels
|
||||
|
||||
|
||||
|
||||
+4
-3
@@ -36,11 +36,12 @@ vendor-bin/**/composer.lock
|
||||
vendor-php
|
||||
|
||||
# API acceptance tests - auto-generated files
|
||||
.php-cs-fixer.cache
|
||||
suite-logs
|
||||
tests/acceptance/filesForUpload/filesWithVirus/
|
||||
|
||||
# Generated docs page (published to the server-testing-docs branch by CI)
|
||||
tests/.docs-dist/
|
||||
# QA activity reports
|
||||
tests/qa-activity-report/reports/
|
||||
|
||||
# drone CI is in .drone.star, do not let someone accidentally commit a local .drone.yml
|
||||
.drone.yml
|
||||
@@ -64,4 +65,4 @@ go.work.sum
|
||||
.DS_Store
|
||||
|
||||
# example deployments
|
||||
**/opencloud-sandbox-*
|
||||
**/opencloud-sandbox-*
|
||||
+4
-14
@@ -36,18 +36,8 @@ ifndef DATE
|
||||
DATE := $(shell date -u '+%Y%m%d')
|
||||
endif
|
||||
|
||||
LDFLAGS += -X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn -s -w \
|
||||
-X "$(OC_REPO)/pkg/version.Edition=$(EDITION)" \
|
||||
-X "$(OC_REPO)/pkg/version.String=$(STRING)" \
|
||||
-X "$(OC_REPO)/pkg/version.Tag=$(VERSION)" \
|
||||
-X "$(OC_REPO)/pkg/version.Date=$(DATE)"
|
||||
|
||||
DEBUG_LDFLAGS += -X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn \
|
||||
-X "$(OC_REPO)/pkg/version.Edition=$(EDITION)" \
|
||||
-X "$(OC_REPO)/pkg/version.String=$(STRING)" \
|
||||
-X "$(OC_REPO)/pkg/version.Tag=$(VERSION)" \
|
||||
-X "$(OC_REPO)/pkg/version.Date=$(DATE)"
|
||||
|
||||
LDFLAGS += -X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn -s -w -X "$(OC_REPO)/pkg/version.String=$(STRING)" -X "$(OC_REPO)/pkg/version.Tag=$(VERSION)" -X "$(OC_REPO)/pkg/version.Date=$(DATE)"
|
||||
DEBUG_LDFLAGS += -X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn -X "$(OC_REPO)/pkg/version.String=$(STRING)" -X "$(OC_REPO)/pkg/version.Tag=$(VERSION)" -X "$(OC_REPO)/pkg/version.Date=$(DATE)"
|
||||
DOCKER_LDFLAGS += -X "$(OC_REPO)/pkg/config/defaults.BaseDataPathType=path" -X "$(OC_REPO)/pkg/config/defaults.BaseDataPathValue=/var/lib/opencloud"
|
||||
DOCKER_LDFLAGS += -X "$(OC_REPO)/pkg/config/defaults.BaseConfigPathType=path" -X "$(OC_REPO)/pkg/config/defaults.BaseConfigPathValue=/etc/opencloud"
|
||||
|
||||
@@ -113,7 +103,7 @@ debug-linux-docker-amd64: release-dirs
|
||||
GOARCH=amd64 \
|
||||
go build \
|
||||
-gcflags="all=-N -l" \
|
||||
-tags 'netgo,$(TAGS)' \
|
||||
-tags 'netgo $(TAGS)' \
|
||||
-buildmode=exe \
|
||||
-trimpath \
|
||||
-ldflags '-extldflags "-static" $(DEBUG_LDFLAGS) $(DOCKER_LDFLAGS)' \
|
||||
@@ -125,7 +115,7 @@ debug-linux-docker-arm64: release-dirs
|
||||
GOARCH=arm64 \
|
||||
go build \
|
||||
-gcflags="all=-N -l" \
|
||||
-tags 'netgo,$(TAGS)' \
|
||||
-tags 'netgo $(TAGS)' \
|
||||
-buildmode=exe \
|
||||
-trimpath \
|
||||
-ldflags '-extldflags "-static" $(DEBUG_LDFLAGS) $(DOCKER_LDFLAGS)' \
|
||||
|
||||
+1
-1
@@ -10,8 +10,8 @@ protoc-deps: $(BINGO)
|
||||
@cd ../.. && GOPATH="" GOBIN=".bingo" $(BINGO) get -l github.com/go-micro/generator/cmd/protoc-gen-micro
|
||||
@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/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
|
||||
@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,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Purpose of this script is to parse the go.mod file to retrieve the version of
|
||||
# Go that we are using, either from a "toolchain" directive (preferred), or from
|
||||
# a "go" directive as a fallback.
|
||||
#
|
||||
# The script then outputs that version, where it will be picked up as an environment
|
||||
# variable in mise.
|
||||
|
||||
F="./go.mod"
|
||||
|
||||
AWK=awk
|
||||
# prefer 'gawk' over 'awk' to make sure we get GNU awk
|
||||
command -v gawk &>/dev/null && AWK=gawk
|
||||
|
||||
[[ -e $F ]] || { echo "ERROR: failed to find $F" >&2; exit 1;}
|
||||
|
||||
GO_VERSION=$("$AWK" '/^toolchain/ {print $2; exit} /^go / {print $2}' "$F")
|
||||
GO_VERSION=${GO_VERSION#go} # strip the potential 'go' prefix:
|
||||
echo "${GO_VERSION}"
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
$dirToParse = 'tests/acceptance/';
|
||||
$dirIterator = new DirectoryIterator(__DIR__ . '/' . $dirToParse);
|
||||
|
||||
$excludeDirs = [
|
||||
'node_modules',
|
||||
'vendor-php'
|
||||
];
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->exclude($excludeDirs)
|
||||
->in(__DIR__);
|
||||
|
||||
$ocRule = (new OC\CodingStandard\Config())->getRules();
|
||||
$config = new PhpCsFixer\Config();
|
||||
$config->setFinder($finder)
|
||||
->setIndent("\t")
|
||||
->setRules(
|
||||
array_merge(
|
||||
$ocRule,
|
||||
[
|
||||
"return_type_declaration" => [
|
||||
"space_before" => "none",
|
||||
],
|
||||
'single_space_around_construct' => true
|
||||
]
|
||||
)
|
||||
);
|
||||
$config->setFinder($finder);
|
||||
return $config;
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"alexkrechik.cucumberautocomplete"
|
||||
]
|
||||
}
|
||||
Vendored
+3
-1
@@ -36,7 +36,7 @@
|
||||
// demo users
|
||||
"IDM_CREATE_DEMO_USERS": "true",
|
||||
// OC_RUN_SERVICES allows to start a subset of services even in the supervised mode
|
||||
//"OC_RUN_SERVICES": "settings,storage-system,graph,idp,idm,ocs,store,thumbnails,web,webdav,frontend,gateway,users,groups,auth-basic,storage-authmachine,storage-users,storage-shares,storage-publiclink,storage-system,app-provider,sharing,proxy",
|
||||
//"OC_RUN_SERVICES": "settings,storage-system,graph,idp,idm,ocs,store,thumbnails,web,webdav,frontend,gateway,users,groups,auth-basic,storage-authmachine,storage-users,storage-shares,storage-publiclink,storage-system,app-provider,sharing,proxy,ocdav",
|
||||
|
||||
/*
|
||||
* Keep secrets and passwords in one block to allow easy uncommenting
|
||||
@@ -129,6 +129,8 @@
|
||||
"IDP_HTTP_ADDR": "127.0.0.1:10130",
|
||||
"NATS_DEBUG_ADDR": "127.0.0.1:10234",
|
||||
"NATS_NATS_PORT": "10233",
|
||||
"OCDAV_HTTP_ADDR": "127.0.0.1:10350",
|
||||
"OCDAV_DEBUG_ADDR": "127.0.0.1:10163",
|
||||
"OCM_DEBUG_ADDR": "127.0.0.1:10281",
|
||||
"OCM_HTTP_ADDR": "127.0.0.1:10280",
|
||||
"OCM_GRPC_ADDR": "127.0.0.1:10282",
|
||||
|
||||
Vendored
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"cucumberautocomplete.steps": [
|
||||
"tests/acceptance/bootstrap/*.php"
|
||||
],
|
||||
"cucumberautocomplete.syncfeatures": "tests/acceptance/features/**/*.feature",
|
||||
"cucumberautocomplete.strictGherkinCompletion": false,
|
||||
"cucumberautocomplete.strictGherkinValidation": false,
|
||||
"cucumberautocomplete.customParameters": [
|
||||
{ "parameter": ":string", "value": "\"([^\"]*)\"" },
|
||||
{ "parameter": "'/\\^(.*)\\$/'", "value": "'^$1$$'", "isRegex": true, "flags": "g" },
|
||||
{ "parameter": "\\$\\w+", "value": "(.*)", "isRegex": true, "flags": "g" },
|
||||
{ "parameter": "(?<!\\?):[a-zA-Z_][a-zA-Z0-9_]*", "value": "\"([^\"]*)\"", "isRegex": true, "flags": "g" }
|
||||
],
|
||||
"cucumberautocomplete.gherkinDefinitionPart": "(Given|When|Then|But)\\(",
|
||||
"cucumberautocomplete.pureTextSteps": false
|
||||
}
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
# The test runner source for UI tests
|
||||
WEB_COMMITID=dffb3808702dab198065faa7a8aa16228e048385
|
||||
WEB_COMMITID=6abffcc9cff31c46a341105eb6030fec56338126
|
||||
WEB_BRANCH=main
|
||||
|
||||
+716
-1099
File diff suppressed because it is too large.
Load diff
-707
@@ -1,712 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [7.5.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.5.0) - 2026-08-25
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @JammingBen, @Svanvith, @aduffeck, @butonic, @fschade, @junkerderprovinz, @kulmann, @maki5, @pbleser-oc, @rhafer, @saw-jan, @schweigisito, @v-scharf
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- test(api): update php test dependencies [[#3335](https://github.com/opencloud-eu/opencloud/pull/3335)]
|
||||
- fix(acceptance): fix running acceptance tests against host on Linux [[#3358](https://github.com/opencloud-eu/opencloud/pull/3358)]
|
||||
- fix(graph): adding the same user as multiple members in a group (#3354) [[#3356](https://github.com/opencloud-eu/opencloud/pull/3356)]
|
||||
- test(api): fix share role update test scenario [[#3322](https://github.com/opencloud-eu/opencloud/pull/3322)]
|
||||
- test: add api tests for cross-space search index mutation [[#3320](https://github.com/opencloud-eu/opencloud/pull/3320)]
|
||||
- ci: run search acceptance tests against OpenSearch in nightly [[#3302](https://github.com/opencloud-eu/opencloud/pull/3302)]
|
||||
- api-test: notification settings and getting email notifications [[#3281](https://github.com/opencloud-eu/opencloud/pull/3281)]
|
||||
- api-test: add posixfs scan and consistency CLI tests [[#3263](https://github.com/opencloud-eu/opencloud/pull/3263)]
|
||||
- api-test: replace sleeps with WaitHelper poll for async state [[#3239](https://github.com/opencloud-eu/opencloud/pull/3239)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- feat(thumbnails): extend list of default resolutions [[#3386](https://github.com/opencloud-eu/opencloud/pull/3386)]
|
||||
- ability to disable grpc and/or event consumer for event history service [[#3279](https://github.com/opencloud-eu/opencloud/pull/3279)]
|
||||
- feat(graph): add LibreGraphContentType on drive [[#3355](https://github.com/opencloud-eu/opencloud/pull/3355)]
|
||||
- feat: update space template image [[#3324](https://github.com/opencloud-eu/opencloud/pull/3324)]
|
||||
- feat(web): add rclone-crypt to default apps [[#3313](https://github.com/opencloud-eu/opencloud/pull/3313)]
|
||||
- enhance: send events for adding/removing favourite items [[#3229](https://github.com/opencloud-eu/opencloud/pull/3229)]
|
||||
- allow tuning the proxies http client [[#3278](https://github.com/opencloud-eu/opencloud/pull/3278)]
|
||||
- feat(web): add yjsServerUrl config [[#3259](https://github.com/opencloud-eu/opencloud/pull/3259)]
|
||||
- feat(posixfs): #3182 add basepath option in the "posixfs scan" command [[#3235](https://github.com/opencloud-eu/opencloud/pull/3235)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix(search): refresh the opensearch index after a write [[#3388](https://github.com/opencloud-eu/opencloud/pull/3388)]
|
||||
- fix(thumbnails): respect the requested height in libvips builds [[#3377](https://github.com/opencloud-eu/opencloud/pull/3377)]
|
||||
- refactor datagateway into proxy middleware [[#3289](https://github.com/opencloud-eu/opencloud/pull/3289)]
|
||||
- Only log a debug message when an item is still in processing state [[#3158](https://github.com/opencloud-eu/opencloud/pull/3158)]
|
||||
- fix(postprocessing): retry publishing events instead of killing the server [[#3347](https://github.com/opencloud-eu/opencloud/pull/3347)]
|
||||
- actually log error on exit [[#3344](https://github.com/opencloud-eu/opencloud/pull/3344)]
|
||||
- fix(activitylog): log missing parent id cache entry at debug level [[#3325](https://github.com/opencloud-eu/opencloud/pull/3325)]
|
||||
- fix restore file version for shared resource [[#3268](https://github.com/opencloud-eu/opencloud/pull/3268)]
|
||||
- Fix missing favorite flag on opensearch hits [[#3252](https://github.com/opencloud-eu/opencloud/pull/3252)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- docs: clarify custom role bootstrap behavior [[#3366](https://github.com/opencloud-eu/opencloud/pull/3366)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [full-ci] chore: bump web to v7.4.0 [[#3399](https://github.com/opencloud-eu/opencloud/pull/3399)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.83.0 to 1.83.1 [[#3396](https://github.com/opencloud-eu/opencloud/pull/3396)]
|
||||
- build(deps): bump github.com/stretchr/testify from 1.12.0 to 1.12.1 [[#3395](https://github.com/opencloud-eu/opencloud/pull/3395)]
|
||||
- build(deps): bump github.com/go-chi/chi/v5 from 5.3.1 to 5.3.2 [[#3394](https://github.com/opencloud-eu/opencloud/pull/3394)]
|
||||
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.29.0 to 2.30.0 [[#3380](https://github.com/opencloud-eu/opencloud/pull/3380)]
|
||||
- build(deps): bump golang.org/x/image from 0.44.0 to 0.45.0 [[#3381](https://github.com/opencloud-eu/opencloud/pull/3381)]
|
||||
- build(deps): bump github.com/beevik/etree from 1.7.0 to 1.7.1 [[#3382](https://github.com/opencloud-eu/opencloud/pull/3382)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.14.4 to 2.14.5 [[#3357](https://github.com/opencloud-eu/opencloud/pull/3357)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/zpages from 0.69.0 to 0.70.0 [[#3360](https://github.com/opencloud-eu/opencloud/pull/3360)]
|
||||
- build(deps): bump github.com/sirupsen/logrus from 1.9.4 to 1.10.0 [[#3359](https://github.com/opencloud-eu/opencloud/pull/3359)]
|
||||
- chore: bump reva to latest main [[#3329](https://github.com/opencloud-eu/opencloud/pull/3329)]
|
||||
- build(deps): bump github.com/stretchr/testify from 1.11.1 to 1.12.0 [[#3341](https://github.com/opencloud-eu/opencloud/pull/3341)]
|
||||
- build(deps): bump golang.org/x/net from 0.57.0 to 0.58.0 [[#3342](https://github.com/opencloud-eu/opencloud/pull/3342)]
|
||||
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.32.0 to 2.32.1 [[#3340](https://github.com/opencloud-eu/opencloud/pull/3340)]
|
||||
- build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch from 0.43.0 to 0.44.0 [[#3303](https://github.com/opencloud-eu/opencloud/pull/3303)]
|
||||
- build(deps): bump github.com/opencloud-eu/libre-graph-api-go [[#3326](https://github.com/opencloud-eu/opencloud/pull/3326)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace from 1.44.0 to 1.45.0 [[#3306](https://github.com/opencloud-eu/opencloud/pull/3306)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.69.0 to 0.70.0 [[#3305](https://github.com/opencloud-eu/opencloud/pull/3305)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.44.0 to 1.45.0 [[#3280](https://github.com/opencloud-eu/opencloud/pull/3280)]
|
||||
- build(deps): bump github.com/rogpeppe/go-internal from 1.15.0 to 1.16.0 [[#3250](https://github.com/opencloud-eu/opencloud/pull/3250)]
|
||||
- build(deps): bump github.com/kovidgoyal/imaging from 1.8.22 to 1.8.23 [[#3135](https://github.com/opencloud-eu/opencloud/pull/3135)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/trace from 1.44.0 to 1.45.0 [[#3266](https://github.com/opencloud-eu/opencloud/pull/3266)]
|
||||
|
||||
## [7.4.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.4.0) - 2026-08-03
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @JammingBen, @aduffeck, @dschmidt, @fschade, @michaelstingl, @pbleser-oc, @rhafer, @schweigisito, @v-scharf
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- Do not check ignored paths [[#3233](https://github.com/opencloud-eu/opencloud/pull/3233)]
|
||||
- Extend posixfs consistency check [[#3220](https://github.com/opencloud-eu/opencloud/pull/3220)]
|
||||
- Improve reindex command [[#3213](https://github.com/opencloud-eu/opencloud/pull/3213)]
|
||||
- Reindex spaces concurrently [[#3207](https://github.com/opencloud-eu/opencloud/pull/3207)]
|
||||
- feat: add announcement banner [[#3189](https://github.com/opencloud-eu/opencloud/pull/3189)]
|
||||
- feat: add space viewer with versions role [[#2961](https://github.com/opencloud-eu/opencloud/pull/2961)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix(runtime): log service startup errors instead of printing them beside the log [[#3140](https://github.com/opencloud-eu/opencloud/pull/3140)]
|
||||
- fix(posixfs scan): Setup logger for scan command [[#3185](https://github.com/opencloud-eu/opencloud/pull/3185)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- api-test: mark group last-manager removal scenario as flaky [[#3194](https://github.com/opencloud-eu/opencloud/pull/3194)]
|
||||
- rerun flaky tests [[#3183](https://github.com/opencloud-eu/opencloud/pull/3183)]
|
||||
- api-test: fix removeAccessToDrive.feature:145 [[#3179](https://github.com/opencloud-eu/opencloud/pull/3179)]
|
||||
- api-test: cover additional unified roles in acceptance tests [[#3169](https://github.com/opencloud-eu/opencloud/pull/3169)]
|
||||
- test(apiAuthApp): fix flaky token pattern [[#3163](https://github.com/opencloud-eu/opencloud/pull/3163)]
|
||||
- test(apiArchiver): the single-resource archive is named after the resource [[#3080](https://github.com/opencloud-eu/opencloud/pull/3080)]
|
||||
- test(coreApiWebdavUploadTUS): assert etag and permissions on the finalizing TUS chunk [[#3078](https://github.com/opencloud-eu/opencloud/pull/3078)]
|
||||
- test(coreApiWebdavOperations): download a file with a literal "%" via its oc:downloadURL [[#3079](https://github.com/opencloud-eu/opencloud/pull/3079)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- maint: clean-up auth-app documentation [[#3155](https://github.com/opencloud-eu/opencloud/pull/3155)]
|
||||
- ci: sync tests/README.md to docs [[#3164](https://github.com/opencloud-eu/opencloud/pull/3164)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [full-ci] chore: bump web to v7.3.0 [[#3223](https://github.com/opencloud-eu/opencloud/pull/3223)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.18.2 to 1.19.0 [[#3231](https://github.com/opencloud-eu/opencloud/pull/3231)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.82.0 to 1.83.0 [[#3232](https://github.com/opencloud-eu/opencloud/pull/3232)]
|
||||
- chore(idp): bump dependencies [[#3226](https://github.com/opencloud-eu/opencloud/pull/3226)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.14.3 to 2.14.4 [[#3221](https://github.com/opencloud-eu/opencloud/pull/3221)]
|
||||
- build(deps): bump github.com/go-ldap/ldap/v3 from 3.4.13 to 3.4.14 [[#3222](https://github.com/opencloud-eu/opencloud/pull/3222)]
|
||||
- build(deps): bump github.com/prometheus/client_golang from 1.23.2 to 1.24.1 [[#3218](https://github.com/opencloud-eu/opencloud/pull/3218)]
|
||||
- build(deps): bump github.com/gabriel-vasile/mimetype from 1.4.13 to 1.4.15 [[#3217](https://github.com/opencloud-eu/opencloud/pull/3217)]
|
||||
- build(deps): bump golang.org/x/net from 0.56.0 to 0.57.0 [[#3136](https://github.com/opencloud-eu/opencloud/pull/3136)]
|
||||
- build(deps): bump github.com/beevik/etree from 1.6.0 to 1.7.0 [[#3134](https://github.com/opencloud-eu/opencloud/pull/3134)]
|
||||
|
||||
## [7.3.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.3.0) - 2026-07-14
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix(proxy): honor access token cache ttl [[#3056](https://github.com/opencloud-eu/opencloud/pull/3056)]
|
||||
- chore(idp): update axios [[#3094](https://github.com/opencloud-eu/opencloud/pull/3094)]
|
||||
- fix: make the collaboration service events optional [[#3001](https://github.com/opencloud-eu/opencloud/pull/3001)]
|
||||
- Revert "fix: disallow thumbnails for tiff and jpeg2000 images" [[#2973](https://github.com/opencloud-eu/opencloud/pull/2973)]
|
||||
- bump reva [[#2950](https://github.com/opencloud-eu/opencloud/pull/2950)]
|
||||
- change error level for trashing items interaction with search [[#2951](https://github.com/opencloud-eu/opencloud/pull/2951)]
|
||||
- fix: Send SSE events for SpaceEnabled/Disabled to affected users [[#2871](https://github.com/opencloud-eu/opencloud/pull/2871)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- update collaboration readme [[#3076](https://github.com/opencloud-eu/opencloud/pull/3076)]
|
||||
- fix: fix typo in proxy service documentation [[#3089](https://github.com/opencloud-eu/opencloud/pull/3089)]
|
||||
- roling release template [[#2972](https://github.com/opencloud-eu/opencloud/pull/2972)]
|
||||
- enhance: fix typos in webfinger service description [[#2958](https://github.com/opencloud-eu/opencloud/pull/2958)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- [full-ci] gherkin steps refactoring. cleaning code [[#3100](https://github.com/opencloud-eu/opencloud/pull/3100)]
|
||||
- [decomposed] more cli command tests [[#3087](https://github.com/opencloud-eu/opencloud/pull/3087)]
|
||||
- test(apiSpaces): a space admin can delete a space with no manager [[#3040](https://github.com/opencloud-eu/opencloud/pull/3040)]
|
||||
- Update tests for opencloud-eu/reva#655 [[#2889](https://github.com/opencloud-eu/opencloud/pull/2889)]
|
||||
- api-test: deleting space [[#2970](https://github.com/opencloud-eu/opencloud/pull/2970)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- feat: add disableSponsorLink web config option [[#3093](https://github.com/opencloud-eu/opencloud/pull/3093)]
|
||||
- feat(graph): add MS Graph colon-syntax path lookup middleware [[#2688](https://github.com/opencloud-eu/opencloud/pull/2688)]
|
||||
- feat: adjust theme chrome colors and logos [[#2599](https://github.com/opencloud-eu/opencloud/pull/2599)]
|
||||
- add tls support for all nats connections [[#2063](https://github.com/opencloud-eu/opencloud/pull/2063)]
|
||||
- feat: add more roles [[#2928](https://github.com/opencloud-eu/opencloud/pull/2928)]
|
||||
- next to main [[#2924](https://github.com/opencloud-eu/opencloud/pull/2924)]
|
||||
- feat: add core apps env variable to override the default core apps [[#2930](https://github.com/opencloud-eu/opencloud/pull/2930)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- build(deps): bump golang.org/x/image from 0.43.0 to 0.44.0 [[#3112](https://github.com/opencloud-eu/opencloud/pull/3112)]
|
||||
- build(deps): bump golang.org/x/text from 0.39.0 to 0.40.0 [[#3101](https://github.com/opencloud-eu/opencloud/pull/3101)]
|
||||
- [full-ci] chore: bump web to v7.2.0 [[#3121](https://github.com/opencloud-eu/opencloud/pull/3121)]
|
||||
- build(deps): bump golang.org/x/term from 0.44.0 to 0.45.0 [[#3103](https://github.com/opencloud-eu/opencloud/pull/3103)]
|
||||
- build(deps): bump github.com/coreos/go-oidc/v3 from 3.19.0 to 3.20.0 [[#3102](https://github.com/opencloud-eu/opencloud/pull/3102)]
|
||||
- build(deps): bump golang.org/x/text from 0.38.0 to 0.39.0 [[#3085](https://github.com/opencloud-eu/opencloud/pull/3085)]
|
||||
- build(deps): bump github.com/go-chi/chi/v5 from 5.3.0 to 5.3.1 [[#3073](https://github.com/opencloud-eu/opencloud/pull/3073)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.14.2 to 2.14.3 [[#3063](https://github.com/opencloud-eu/opencloud/pull/3063)]
|
||||
- build(deps): bump github.com/gookit/config/v2 from 2.2.8 to 2.2.9 [[#3075](https://github.com/opencloud-eu/opencloud/pull/3075)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.18.1 to 1.18.2 [[#3061](https://github.com/opencloud-eu/opencloud/pull/3061)]
|
||||
- build(deps): bump github.com/kovidgoyal/imaging from 1.8.21 to 1.8.22 [[#3060](https://github.com/opencloud-eu/opencloud/pull/3060)]
|
||||
- build(deps): bump github.com/libregraph/lico from 0.66.0 to 0.67.0 [[#3028](https://github.com/opencloud-eu/opencloud/pull/3028)]
|
||||
- chore: bump web to v7.2.0-beta.3 [[#2953](https://github.com/opencloud-eu/opencloud/pull/2953)]
|
||||
- chore: bump reva to latest main [[#2943](https://github.com/opencloud-eu/opencloud/pull/2943)]
|
||||
|
||||
## [7.2.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.2.0) - 2026-06-24
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@Heiko-Pohl, @JammingBen, @ScharfViktor, @aduffeck, @butonic, @dragonchaser, @kulmann, @rhafer
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- [stable-7.2] Backport fixes from main [[#2999](https://github.com/opencloud-eu/opencloud/pull/2999)]
|
||||
- fix(idp): aarch64 build [[#2906](https://github.com/opencloud-eu/opencloud/pull/2906)]
|
||||
- use ldap instead of ldaps internally [[#2880](https://github.com/opencloud-eu/opencloud/pull/2880)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Rename role_name from "guest" to "user-light" [[#2912](https://github.com/opencloud-eu/opencloud/pull/2912)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [full-ci] chore: bump web to v7.1.2 [[#3012](https://github.com/opencloud-eu/opencloud/pull/3012)]
|
||||
- [full-ci] chore: bump web to v7.1.1 [[#2998](https://github.com/opencloud-eu/opencloud/pull/2998)]
|
||||
- bump reva to latest main [[#2922](https://github.com/opencloud-eu/opencloud/pull/2922)]
|
||||
- build(deps-dev): bump webpack-manifest-plugin from 5.0.0 to 6.0.1 in /services/idp [[#2884](https://github.com/opencloud-eu/opencloud/pull/2884)]
|
||||
- build(deps): bump axios from 1.16.0 to 1.16.1 in /services/idp [[#2883](https://github.com/opencloud-eu/opencloud/pull/2883)]
|
||||
|
||||
## [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! ❤️
|
||||
|
||||
@AlexAndBear, @SAY-5, @ScharfViktor, @Svanvith, @butonic, @dragonchaser, @dschmidt, @fschade, @micbar, @michaelstingl, @rhafer
|
||||
|
||||
### 💥 Breaking changes
|
||||
|
||||
- Persist space memberships in share manager [[#2760](https://github.com/opencloud-eu/opencloud/pull/2760)]
|
||||
- [feature/guest-links] bump reva, add service user config to "sharing" service [[#2735](https://github.com/opencloud-eu/opencloud/pull/2735)]
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- fix: disallow thumbnails for tiff and jpeg2000 images [[#2758](https://github.com/opencloud-eu/opencloud/pull/2758)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix(notifications): don't re-escape email vars for each recipient [[#2805](https://github.com/opencloud-eu/opencloud/pull/2805)]
|
||||
- fix: remove unnecessary error log it the oidc access token verify method is set to none [[#2795](https://github.com/opencloud-eu/opencloud/pull/2795)]
|
||||
- fix(debug): drop duplicate service field from probe fallback log [[#2786](https://github.com/opencloud-eu/opencloud/pull/2786)]
|
||||
- No registry lookup in cli [[#2755](https://github.com/opencloud-eu/opencloud/pull/2755)]
|
||||
- fix(webdav): register chi REPORT method in init to avoid race with settings [[#2712](https://github.com/opencloud-eu/opencloud/pull/2712)]
|
||||
- fix: use runner to start activitylog service [[#2748](https://github.com/opencloud-eu/opencloud/pull/2748)]
|
||||
- docs(search): fix force-rescan flag name in README [[#2747](https://github.com/opencloud-eu/opencloud/pull/2747)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- [full-ci] preview-tests. update fixtures for different processors [[#2767](https://github.com/opencloud-eu/opencloud/pull/2767)]
|
||||
- test: modify exclude list and add coverage upload [[#2762](https://github.com/opencloud-eu/opencloud/pull/2762)]
|
||||
- fix: cleaner debounce timer test [[#2743](https://github.com/opencloud-eu/opencloud/pull/2743)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Update README with LDAP certificate details [[#2759](https://github.com/opencloud-eu/opencloud/pull/2759)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- feat(graph): populate driveItem.webUrl per Libre Graph spec [[#2744](https://github.com/opencloud-eu/opencloud/pull/2744)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- build(deps): bump github.com/go-jose/go-jose/v3 from 3.0.4 to 3.0.5 [[#2798](https://github.com/opencloud-eu/opencloud/pull/2798)]
|
||||
- build(deps): bump golang.org/x/image from 0.38.0 to 0.40.0 [[#2740](https://github.com/opencloud-eu/opencloud/pull/2740)]
|
||||
- build(deps): bump github.com/tidwall/gjson from 1.18.0 to 1.19.0 [[#2750](https://github.com/opencloud-eu/opencloud/pull/2750)]
|
||||
- build(deps-dev): bump dotenv-expand from 12.0.3 to 13.0.0 in /services/idp [[#2710](https://github.com/opencloud-eu/opencloud/pull/2710)]
|
||||
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.28.1 to 2.28.3 [[#2739](https://github.com/opencloud-eu/opencloud/pull/2739)]
|
||||
|
||||
## [6.2.0](https://github.com/opencloud-eu/opencloud/releases/tag/v6.2.0) - 2026-05-11
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@JammingBen, @ScharfViktor, @Sweeistaken, @aduffeck, @dragonchaser, @dschmidt, @fschade, @pedropintosilva, @rhafer, @schweigisito
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- feat: enable EnableRemoteLinkPicker WOPI flag for Collabora Online [[#2663](https://github.com/opencloud-eu/opencloud/pull/2663)]
|
||||
- feat(kql): support dotted keys in property restrictions [[#2632](https://github.com/opencloud-eu/opencloud/pull/2632)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Set new defaults for caches and stores [[#2702](https://github.com/opencloud-eu/opencloud/pull/2702)]
|
||||
- fix: remove typo in error message [[#2701](https://github.com/opencloud-eu/opencloud/pull/2701)]
|
||||
- fix(search): preserve value case for non-lowercased bleve fields [[#2633](https://github.com/opencloud-eu/opencloud/pull/2633)]
|
||||
- More graceful shutdown fixes [[#2690](https://github.com/opencloud-eu/opencloud/pull/2690)]
|
||||
- Hotfix for https://github.com/opencloud-eu/opencloud/issues/2282 [[#2631](https://github.com/opencloud-eu/opencloud/pull/2631)]
|
||||
- fix(search): read --force-rescan flag with its registered name [[#2639](https://github.com/opencloud-eu/opencloud/pull/2639)]
|
||||
- fix(search): parse tika xmpDM:duration as a float [[#2638](https://github.com/opencloud-eu/opencloud/pull/2638)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- [api-tests] delete PROPATCH favorite tests [[#2689](https://github.com/opencloud-eu/opencloud/pull/2689)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- enhancement: increase display size of graph flow diagram [[#2620](https://github.com/opencloud-eu/opencloud/pull/2620)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- build(deps): bump go.opentelemetry.io/contrib/zpages from 0.67.0 to 0.68.0 [[#2666](https://github.com/opencloud-eu/opencloud/pull/2666)]
|
||||
- build(deps): bump @types/node from 22.19.17 to 25.6.0 in /services/idp [[#2687](https://github.com/opencloud-eu/opencloud/pull/2687)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace from 1.42.0 to 1.43.0 [[#2601](https://github.com/opencloud-eu/opencloud/pull/2601)]
|
||||
- build(deps): bump github.com/davidbyttow/govips/v2 from 2.17.0 to 2.18.0 [[#2656](https://github.com/opencloud-eu/opencloud/pull/2656)]
|
||||
- build(deps): bump i18next from 25.10.10 to 26.0.4 in /services/idp [[#2609](https://github.com/opencloud-eu/opencloud/pull/2609)]
|
||||
- build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch from 0.41.0 to 0.42.0 [[#2645](https://github.com/opencloud-eu/opencloud/pull/2645)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.15.1 to 1.15.2 [[#2602](https://github.com/opencloud-eu/opencloud/pull/2602)]
|
||||
|
||||
## [6.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v6.1.0) - 2026-04-20
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@JammingBen, @ScharfViktor, @aduffeck, @dragonchaser, @pedropintosilva, @rhafer
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Update CI badge URL in README.md [[#2614](https://github.com/opencloud-eu/opencloud/pull/2614)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Add a flag to the reindex command to force a full reindex [[#2606](https://github.com/opencloud-eu/opencloud/pull/2606)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- proxy: Allow mapping from an external tenant id to the internal id [[#2569](https://github.com/opencloud-eu/opencloud/pull/2569)]
|
||||
- feat: enable EnableInsertRemoteFile WOPI flag for Collabora [[#2555](https://github.com/opencloud-eu/opencloud/pull/2555)]
|
||||
- feat(multi-tenancy): verify tenant via OIDC claim [[#2559](https://github.com/opencloud-eu/opencloud/pull/2559)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- Bump reva [[#2611](https://github.com/opencloud-eu/opencloud/pull/2611)]
|
||||
- chore(idp): clean up js dependencies [[#2607](https://github.com/opencloud-eu/opencloud/pull/2607)]
|
||||
- build(deps-dev): bump dotenv from 16.4.7 to 17.4.2 in /services/idp [[#2603](https://github.com/opencloud-eu/opencloud/pull/2603)]
|
||||
- chore: bump IDP javascript dependencies [[#2600](https://github.com/opencloud-eu/opencloud/pull/2600)]
|
||||
- build(deps): bump github.com/nats-io/nats.go from 1.49.0 to 1.50.0 [[#2587](https://github.com/opencloud-eu/opencloud/pull/2587)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.42.0 to 1.43.0 [[#2586](https://github.com/opencloud-eu/opencloud/pull/2586)]
|
||||
- chore: bump reva to latest main [[#2584](https://github.com/opencloud-eu/opencloud/pull/2584)]
|
||||
- build(deps): bump golang.org/x/image from 0.36.0 to 0.38.0 [[#2581](https://github.com/opencloud-eu/opencloud/pull/2581)]
|
||||
- build(deps-dev): bump css-minimizer-webpack-plugin from 7.0.4 to 8.0.0 in /services/idp [[#2551](https://github.com/opencloud-eu/opencloud/pull/2551)]
|
||||
- build(deps): bump github.com/go-ldap/ldap/v3 from 3.4.12 to 3.4.13 [[#2526](https://github.com/opencloud-eu/opencloud/pull/2526)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.14.1 to 1.15.0 [[#2535](https://github.com/opencloud-eu/opencloud/pull/2535)]
|
||||
|
||||
## [6.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v6.0.0) - 2026-03-30
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@ScharfViktor, @aduffeck, @dragonchaser, @micbar, @pascalwengerter, @smoothscholar
|
||||
|
||||
### 💥 Breaking changes
|
||||
|
||||
- Improve opensearch highlighting, fix favorites [[#2514](https://github.com/opencloud-eu/opencloud/pull/2514)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- feat: add userid to spans [[#2536](https://github.com/opencloud-eu/opencloud/pull/2536)]
|
||||
- feat: add openFilesInNewTab web config option [[#2522](https://github.com/opencloud-eu/opencloud/pull/2522)]
|
||||
- Always enable favorites, remove FRONTEND_ENABLE_FAVORITES flag [[#2494](https://github.com/opencloud-eu/opencloud/pull/2494)]
|
||||
- Implement favorites [[#2454](https://github.com/opencloud-eu/opencloud/pull/2454)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Fix bleve batches [[#2524](https://github.com/opencloud-eu/opencloud/pull/2524)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- api-tests: search for favorites [[#2487](https://github.com/opencloud-eu/opencloud/pull/2487)]
|
||||
- [test-only] favorites tests [[#2474](https://github.com/opencloud-eu/opencloud/pull/2474)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.12.5 to 2.12.6 [[#2525](https://github.com/opencloud-eu/opencloud/pull/2525)]
|
||||
- build(deps-dev): bump postcss-preset-env from 10.1.3 to 11.2.0 in /services/idp [[#2392](https://github.com/opencloud-eu/opencloud/pull/2392)]
|
||||
- build(deps): bump github.com/tus/tusd/v2 from 2.8.0 to 2.9.2 [[#2485](https://github.com/opencloud-eu/opencloud/pull/2485)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.79.2 to 1.79.3 [[#2519](https://github.com/opencloud-eu/opencloud/pull/2519)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.12.4 to 2.12.5 [[#2499](https://github.com/opencloud-eu/opencloud/pull/2499)]
|
||||
- build(deps): bump github.com/russellhaering/goxmldsig from 1.5.0 to 1.6.0 [[#2503](https://github.com/opencloud-eu/opencloud/pull/2503)]
|
||||
- build(deps): bump golang.org/x/net from 0.51.0 to 0.52.0 [[#2472](https://github.com/opencloud-eu/opencloud/pull/2472)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc from 0.65.0 to 0.67.0 [[#2473](https://github.com/opencloud-eu/opencloud/pull/2473)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 1.1.3 to 1.1.4 [[#2468](https://github.com/opencloud-eu/opencloud/pull/2468)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/zpages from 0.65.0 to 0.67.0 [[#2467](https://github.com/opencloud-eu/opencloud/pull/2467)]
|
||||
- build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch from 0.40.0 to 0.41.0 [[#2458](https://github.com/opencloud-eu/opencloud/pull/2458)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.41.0 to 1.42.0 [[#2459](https://github.com/opencloud-eu/opencloud/pull/2459)]
|
||||
- build(deps): bump github.com/testcontainers/testcontainers-go from 0.40.0 to 0.41.0 [[#2453](https://github.com/opencloud-eu/opencloud/pull/2453)]
|
||||
- build(deps): bump golang.org/x/oauth2 from 0.35.0 to 0.36.0 [[#2452](https://github.com/opencloud-eu/opencloud/pull/2452)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace from 1.40.0 to 1.42.0 [[#2441](https://github.com/opencloud-eu/opencloud/pull/2441)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.65.0 to 0.67.0 [[#2442](https://github.com/opencloud-eu/opencloud/pull/2442)]
|
||||
|
||||
## [5.2.0](https://github.com/opencloud-eu/opencloud/releases/tag/v5.2.0) - 2026-03-09
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @JammingBen, @MahdiBaghbani, @ScharfViktor, @aduffeck, @butonic, @dragonchaser, @dragotin, @fschade, @pat-s, @rhafer
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- update links and references in CONTRIBUTING.md [[#2411](https://github.com/opencloud-eu/opencloud/pull/2411)]
|
||||
- adr(webfinger): Align example config with implementation [[#2353](https://github.com/opencloud-eu/opencloud/pull/2353)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- feat(graph/education): Add support of 'eq' filters on users [[#2421](https://github.com/opencloud-eu/opencloud/pull/2421)]
|
||||
- feat(web): change surface colors to more modern ones [[#2377](https://github.com/opencloud-eu/opencloud/pull/2377)]
|
||||
- Add openCloudEducationExternalId to user [[#2357](https://github.com/opencloud-eu/opencloud/pull/2357)]
|
||||
- feat: app-registry adjust default mime-types [[#2354](https://github.com/opencloud-eu/opencloud/pull/2354)]
|
||||
- feat: support desktop and mobile specific `client_id` and `scopes` [[#2072](https://github.com/opencloud-eu/opencloud/pull/2072)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- [SKIP CI] Fix simple install script, use admin-password switch [[#2413](https://github.com/opencloud-eu/opencloud/pull/2413)]
|
||||
- resolve logout token subject:sessions for the idp backchannel logout [[#2328](https://github.com/opencloud-eu/opencloud/pull/2328)]
|
||||
- fix(oidc_auth): Fix userinfo cache expiration logic [[#2360](https://github.com/opencloud-eu/opencloud/pull/2360)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.13.2 to 1.14.0 [[#2427](https://github.com/opencloud-eu/opencloud/pull/2427)]
|
||||
- build(deps): bump go.opentelemetry.io/otel from 1.40.0 to 1.41.0 [[#2425](https://github.com/opencloud-eu/opencloud/pull/2425)]
|
||||
- build(deps): bump github.com/davidbyttow/govips/v2 from 2.16.0 to 2.17.0 [[#2420](https://github.com/opencloud-eu/opencloud/pull/2420)]
|
||||
- build(deps): bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0 [[#2390](https://github.com/opencloud-eu/opencloud/pull/2390)]
|
||||
- build(deps): bump golang.org/x/net from 0.50.0 to 0.51.0 [[#2412](https://github.com/opencloud-eu/opencloud/pull/2412)]
|
||||
- build(deps): bump github.com/kovidgoyal/imaging from 1.8.19 to 1.8.20 [[#2391](https://github.com/opencloud-eu/opencloud/pull/2391)]
|
||||
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.27.7 to 2.28.0 [[#2375](https://github.com/opencloud-eu/opencloud/pull/2375)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.13.1 to 1.13.2 [[#2374](https://github.com/opencloud-eu/opencloud/pull/2374)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.78.0 to 1.79.1 [[#2362](https://github.com/opencloud-eu/opencloud/pull/2362)]
|
||||
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.28.0 to 2.28.1 [[#2366](https://github.com/opencloud-eu/opencloud/pull/2366)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/zpages from 0.64.0 to 0.65.0 [[#2363](https://github.com/opencloud-eu/opencloud/pull/2363)]
|
||||
- build(deps): bump golang.org/x/net from 0.49.0 to 0.50.0 [[#2356](https://github.com/opencloud-eu/opencloud/pull/2356)]
|
||||
- build(deps): bump github.com/go-resty/resty/v2 from 2.17.1 to 2.17.2 [[#2355](https://github.com/opencloud-eu/opencloud/pull/2355)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.64.0 to 0.65.0 [[#2321](https://github.com/opencloud-eu/opencloud/pull/2321)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.12.3 to 1.13.1 [[#2350](https://github.com/opencloud-eu/opencloud/pull/2350)]
|
||||
|
||||
## [5.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v5.1.0) - 2026-02-16
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@ScharfViktor, @VicDeo, @aduffeck, @dragonchaser, @individual-it, @kulmann, @micbar, @rhafer, @schweigisito
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- [full-ci] Bump reva v2.42.4 [[#2348](https://github.com/opencloud-eu/opencloud/pull/2348)]
|
||||
- fix: fix typo in variable description [[#2333](https://github.com/opencloud-eu/opencloud/pull/2333)]
|
||||
- fix: include sessionID in sse logout event [[#2327](https://github.com/opencloud-eu/opencloud/pull/2327)]
|
||||
- fix: fix typo in gateway service documentation [[#2332](https://github.com/opencloud-eu/opencloud/pull/2332)]
|
||||
- Sanitize web config only once [[#2286](https://github.com/opencloud-eu/opencloud/pull/2286)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- external tenant id [[#2258](https://github.com/opencloud-eu/opencloud/pull/2258)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- fix: make file urls [[#2304](https://github.com/opencloud-eu/opencloud/pull/2304)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- build(deps): bump github.com/gabriel-vasile/mimetype from 1.4.12 to 1.4.13 [[#2316](https://github.com/opencloud-eu/opencloud/pull/2316)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace from 1.39.0 to 1.40.0 [[#2279](https://github.com/opencloud-eu/opencloud/pull/2279)]
|
||||
- update reva after merge #514 [[#2309](https://github.com/opencloud-eu/opencloud/pull/2309)]
|
||||
- build(deps): bump github.com/go-chi/chi/v5 from 5.2.4 to 5.2.5 [[#2278](https://github.com/opencloud-eu/opencloud/pull/2278)]
|
||||
|
||||
## [5.0.2](https://github.com/opencloud-eu/opencloud/releases/tag/v5.0.2) - 2026-02-05
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @ScharfViktor, @flimmy, @individual-it, @rhafer, @saw-jan
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- [full-ci] reva-bump-2.42.3 [[#2276](https://github.com/opencloud-eu/opencloud/pull/2276)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- adapt test for #514 [[#2255](https://github.com/opencloud-eu/opencloud/pull/2255)]
|
||||
- api-test: upload-rename-download file with back slash [[#2239](https://github.com/opencloud-eu/opencloud/pull/2239)]
|
||||
- [full-ci][tests-only] test: add hook failures to the test failures list [[#2041](https://github.com/opencloud-eu/opencloud/pull/2041)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- docs(proxy): Clarify PROXY_OIDC_USERINFO_CACHE_TTL value [[#2256](https://github.com/opencloud-eu/opencloud/pull/2256)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [full-ci] reva-bump-2.42.2 [[#2270](https://github.com/opencloud-eu/opencloud/pull/2270)]
|
||||
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.27.5 to 2.27.6 [[#2238](https://github.com/opencloud-eu/opencloud/pull/2238)]
|
||||
|
||||
## [5.0.1](https://github.com/opencloud-eu/opencloud/releases/tag/v5.0.1) - 2026-01-28
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@ScharfViktor, @aduffeck, @saw-jan
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Do not ever set a TTL for the ID cache. It's not supposed to expire. [[#2223](https://github.com/opencloud-eu/opencloud/pull/2223)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- test(api): wait for web-office readiness by checking discovery endpoint [[#2217](https://github.com/opencloud-eu/opencloud/pull/2217)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- reva-bump-2.42.1 [[#2225](https://github.com/opencloud-eu/opencloud/pull/2225)]
|
||||
|
||||
## [5.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v5.0.0) - 2026-01-26
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@ScharfViktor, @butonic, @dragonchaser, @flimmy, @fschade, @micbar, @rhafer, @saw-jan
|
||||
|
||||
### 💥 Breaking changes
|
||||
|
||||
- merge ocdav into frontend [[#1958](https://github.com/opencloud-eu/opencloud/pull/1958)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- [test-only] replace exception to assertions [[#2196](https://github.com/opencloud-eu/opencloud/pull/2196)]
|
||||
- test(api): auto-generate test virus files before test run [[#2191](https://github.com/opencloud-eu/opencloud/pull/2191)]
|
||||
- test(api): remove accountsHashDifficulty test suite [[#2190](https://github.com/opencloud-eu/opencloud/pull/2190)]
|
||||
- test(api): update without-remotephp expected-failures list [[#2184](https://github.com/opencloud-eu/opencloud/pull/2184)]
|
||||
- [full-ci] test: use single command to run the containers and the API tests [[#2169](https://github.com/opencloud-eu/opencloud/pull/2169)]
|
||||
- [tests-only] test: setup for running wopi API tests locally [[#2139](https://github.com/opencloud-eu/opencloud/pull/2139)]
|
||||
- fix flaky #2145 [[#2161](https://github.com/opencloud-eu/opencloud/pull/2161)]
|
||||
- Run wopi validator tests localy [[#2151](https://github.com/opencloud-eu/opencloud/pull/2151)]
|
||||
- ci: fix unwanted workflow skip in the cron pipelines [[#2117](https://github.com/opencloud-eu/opencloud/pull/2117)]
|
||||
- [POC] ci: skip previously passed workflows on pipeline restart [[#2099](https://github.com/opencloud-eu/opencloud/pull/2099)]
|
||||
- [tests-only] test: wait post-processing to finish for MKCOL requests [[#2092](https://github.com/opencloud-eu/opencloud/pull/2092)]
|
||||
- [tests-only] test: fix API tests [[#2087](https://github.com/opencloud-eu/opencloud/pull/2087)]
|
||||
- [full-ci] use graph api in the enforcePasswordPublicLink.feature [[#2050](https://github.com/opencloud-eu/opencloud/pull/2050)]
|
||||
- [full-ci][tests-only] test: check last email content with retries as emails can be delayed [[#2038](https://github.com/opencloud-eu/opencloud/pull/2038)]
|
||||
- skip collaborativePosix tests in CI [[#2039](https://github.com/opencloud-eu/opencloud/pull/2039)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Update release template [[#2182](https://github.com/opencloud-eu/opencloud/pull/2182)]
|
||||
- Clarify what the two requests are used for [[#2179](https://github.com/opencloud-eu/opencloud/pull/2179)]
|
||||
- fix: markdown links formatting [[#2143](https://github.com/opencloud-eu/opencloud/pull/2143)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix: Show username in unprivileged search results [[#2104](https://github.com/opencloud-eu/opencloud/pull/2104)]
|
||||
- fix(thumbnailer): missing font panic [[#2097](https://github.com/opencloud-eu/opencloud/pull/2097)]
|
||||
- Remove sub-service binary entrypoints and fix antivirus only server cmd [[#2043](https://github.com/opencloud-eu/opencloud/pull/2043)]
|
||||
- fix(thumbnailer): respect image boundaries and text wrappings [[#2062](https://github.com/opencloud-eu/opencloud/pull/2062)]
|
||||
- fix: cobra viper flags and env [[#2047](https://github.com/opencloud-eu/opencloud/pull/2047)]
|
||||
- fix service name in suture logs [[#2052](https://github.com/opencloud-eu/opencloud/pull/2052)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- benchmark client enhancements [[#1856](https://github.com/opencloud-eu/opencloud/pull/1856)]
|
||||
- allow http2 connections to proxy [[#2040](https://github.com/opencloud-eu/opencloud/pull/2040)]
|
||||
- migrate from urfave/cli to spf13/cobra [[#1954](https://github.com/opencloud-eu/opencloud/pull/1954)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- reva-bump-2.42.0 [[#2215](https://github.com/opencloud-eu/opencloud/pull/2215)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 1.1.2 to 1.1.3 [[#2186](https://github.com/opencloud-eu/opencloud/pull/2186)]
|
||||
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.27.4 to 2.27.5 [[#2204](https://github.com/opencloud-eu/opencloud/pull/2204)]
|
||||
- build(deps): bump github.com/go-resty/resty/v2 from 2.7.0 to 2.17.1 [[#2197](https://github.com/opencloud-eu/opencloud/pull/2197)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.11.1 to 1.12.3 [[#2166](https://github.com/opencloud-eu/opencloud/pull/2166)]
|
||||
- build(deps): bump github.com/kovidgoyal/imaging from 1.8.18 to 1.8.19 [[#2167](https://github.com/opencloud-eu/opencloud/pull/2167)]
|
||||
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.27.3 to 2.27.4 [[#2164](https://github.com/opencloud-eu/opencloud/pull/2164)]
|
||||
- build(deps): bump github.com/sirupsen/logrus from 1.9.4-0.20230606125235-dd1b4c2e81af to 1.9.4 [[#2163](https://github.com/opencloud-eu/opencloud/pull/2163)]
|
||||
- build(deps): bump github.com/go-chi/chi/v5 from 5.2.3 to 5.2.4 [[#2162](https://github.com/opencloud-eu/opencloud/pull/2162)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/zpages from 0.63.0 to 0.64.0 [[#2158](https://github.com/opencloud-eu/opencloud/pull/2158)]
|
||||
- build(deps): bump github.com/blevesearch/bleve/v2 from 2.5.5 to 2.5.7 [[#2157](https://github.com/opencloud-eu/opencloud/pull/2157)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace from 1.38.0 to 1.39.0 [[#2154](https://github.com/opencloud-eu/opencloud/pull/2154)]
|
||||
- build(deps): bump golang.org/x/image from 0.34.0 to 0.35.0 [[#2153](https://github.com/opencloud-eu/opencloud/pull/2153)]
|
||||
- build(deps): bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0 [[#2147](https://github.com/opencloud-eu/opencloud/pull/2147)]
|
||||
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.27.2 to 2.27.5 [[#2148](https://github.com/opencloud-eu/opencloud/pull/2148)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 1.1.1 to 1.1.2 [[#2144](https://github.com/opencloud-eu/opencloud/pull/2144)]
|
||||
- build(deps): bump github.com/spf13/cobra from 1.10.1 to 1.10.2 [[#2141](https://github.com/opencloud-eu/opencloud/pull/2141)]
|
||||
- build(deps): bump golang.org/x/net from 0.48.0 to 0.49.0 [[#2140](https://github.com/opencloud-eu/opencloud/pull/2140)]
|
||||
- build(deps): bump github.com/onsi/gomega from 1.38.2 to 1.39.0 [[#2133](https://github.com/opencloud-eu/opencloud/pull/2133)]
|
||||
- build(deps): bump golang.org/x/crypto from 0.46.0 to 0.47.0 [[#2132](https://github.com/opencloud-eu/opencloud/pull/2132)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.63.0 to 0.64.0 [[#2109](https://github.com/opencloud-eu/opencloud/pull/2109)]
|
||||
- build(deps): bump github.com/kovidgoyal/imaging from 1.8.17 to 1.8.18 [[#2107](https://github.com/opencloud-eu/opencloud/pull/2107)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.77.0 to 1.78.0 [[#2106](https://github.com/opencloud-eu/opencloud/pull/2106)]
|
||||
- build(deps): bump go.opentelemetry.io/otel/sdk from 1.38.0 to 1.39.0 [[#2069](https://github.com/opencloud-eu/opencloud/pull/2069)]
|
||||
- build(deps): bump github.com/opensearch-project/opensearch-go/v4 from 4.5.0 to 4.6.0 [[#2068](https://github.com/opencloud-eu/opencloud/pull/2068)]
|
||||
- build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch from 0.39.0 to 0.40.0 [[#1967](https://github.com/opencloud-eu/opencloud/pull/1967)]
|
||||
- build(deps): bump golang.org/x/net from 0.47.0 to 0.48.0 [[#2061](https://github.com/opencloud-eu/opencloud/pull/2061)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.10.1 to 1.11.0 [[#1930](https://github.com/opencloud-eu/opencloud/pull/1930)]
|
||||
|
||||
## [4.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v4.1.0) - 2025-12-15
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@JammingBen, @ScharfViktor, @Svanvith, @butonic, @flimmy, @fschade, @individual-it, @kulmann, @micbar, @prashant-gurung899, @saw-jan
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- fix typo [[#2024](https://github.com/opencloud-eu/opencloud/pull/2024)]
|
||||
- [docs] update policies link [[#1996](https://github.com/opencloud-eu/opencloud/pull/1996)]
|
||||
- fix the link in quickstart script for itself [[#1956](https://github.com/opencloud-eu/opencloud/pull/1956)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- [full-ci][tests-only] test: fix some test flakiness [[#2003](https://github.com/opencloud-eu/opencloud/pull/2003)]
|
||||
- [tests-only] Skip test related pipelines for ready-release-go PRs [[#2011](https://github.com/opencloud-eu/opencloud/pull/2011)]
|
||||
- [full-ci][tests-only] test: add test to check mismatch offset during TUS upload [[#1993](https://github.com/opencloud-eu/opencloud/pull/1993)]
|
||||
- [full-ci][tests-only] test: proper resource existence check [[#1990](https://github.com/opencloud-eu/opencloud/pull/1990)]
|
||||
- check propfing after renaming data in file system [[#1809](https://github.com/opencloud-eu/opencloud/pull/1809)]
|
||||
- fix-get-attribute-test [[#1974](https://github.com/opencloud-eu/opencloud/pull/1974)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- Show edition in opencloud version command [[#2019](https://github.com/opencloud-eu/opencloud/pull/2019)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix: enforce trailing slash for server url [[#1995](https://github.com/opencloud-eu/opencloud/pull/1995)]
|
||||
- fix: enhance resource creation with detailed process information [[#1978](https://github.com/opencloud-eu/opencloud/pull/1978)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- chore: bump web to v4.3.0 [[#2030](https://github.com/opencloud-eu/opencloud/pull/2030)]
|
||||
- reva-bump-2.41.0 [[#2032](https://github.com/opencloud-eu/opencloud/pull/2032)]
|
||||
- build(deps): bump github.com/testcontainers/testcontainers-go from 0.39.0 to 0.40.0 [[#1931](https://github.com/opencloud-eu/opencloud/pull/1931)]
|
||||
|
||||
## [4.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v4.0.0) - 2025-12-01
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @MahdiBaghbani, @ScharfViktor, @butonic, @dragonchaser, @flimmy, @fschade, @individual-it, @jnweiger, @kulmann, @micbar, @mikelolasagasti, @pbleser-oc, @rhafer, @schweigisito
|
||||
|
||||
### 💥 Breaking changes
|
||||
|
||||
- collaboration: Enable `InsertRemoteImage` option [[#1692](https://github.com/opencloud-eu/opencloud/pull/1692)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Fix typos in antivirus README documentation [[#1940](https://github.com/opencloud-eu/opencloud/pull/1940)]
|
||||
- fix: add missing service README.md files with basic description [[#1859](https://github.com/opencloud-eu/opencloud/pull/1859)]
|
||||
- Fix README.md files which contain broken or missing links [[#1854](https://github.com/opencloud-eu/opencloud/pull/1854)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- introduce OC_EVENTS_TLS_INSECURE [[#1936](https://github.com/opencloud-eu/opencloud/pull/1936)]
|
||||
- kill unused env vars [[#1888](https://github.com/opencloud-eu/opencloud/pull/1888)]
|
||||
- rc-handling was only active for the dryrun, not the real build-and-push [[#1919](https://github.com/opencloud-eu/opencloud/pull/1919)]
|
||||
- handle objectguid endianess [[#1901](https://github.com/opencloud-eu/opencloud/pull/1901)]
|
||||
- fix: add update server to default csp rules [[#1875](https://github.com/opencloud-eu/opencloud/pull/1875)]
|
||||
- fix: add missing capability flag support-radicale [[#1891](https://github.com/opencloud-eu/opencloud/pull/1891)]
|
||||
- fix opensearch client certificate [[#1890](https://github.com/opencloud-eu/opencloud/pull/1890)]
|
||||
- Bump reva [[#1882](https://github.com/opencloud-eu/opencloud/pull/1882)]
|
||||
- load two yaml configs [[#1617](https://github.com/opencloud-eu/opencloud/pull/1617)]
|
||||
- make user cache tenant aware [[#1732](https://github.com/opencloud-eu/opencloud/pull/1732)]
|
||||
- fix: sanitise markdow code to make docusaurus happy [[#1851](https://github.com/opencloud-eu/opencloud/pull/1851)]
|
||||
- update launch.json [[#1843](https://github.com/opencloud-eu/opencloud/pull/1843)]
|
||||
- docs: Fix auth-app examples in README [[#1844](https://github.com/opencloud-eu/opencloud/pull/1844)]
|
||||
- fix: fix typo in treesize logging [[#1826](https://github.com/opencloud-eu/opencloud/pull/1826)]
|
||||
- fix: set global signing secret fallback correctly [[#1781](https://github.com/opencloud-eu/opencloud/pull/1781)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- feat(ocm): add WAYF configuration for reva OCM service [[#1714](https://github.com/opencloud-eu/opencloud/pull/1714)]
|
||||
- log missing name or id attributes [[#1914](https://github.com/opencloud-eu/opencloud/pull/1914)]
|
||||
- collabora: Set IsAdminUser and IsAnonymousUser in CheckFileInfo [[#1745](https://github.com/opencloud-eu/opencloud/pull/1745)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- [full-ci] disable running ci with watch fs when full-ci [[#1902](https://github.com/opencloud-eu/opencloud/pull/1902)]
|
||||
- api-tests: delete spaces before users [[#1877](https://github.com/opencloud-eu/opencloud/pull/1877)]
|
||||
- update tika version [[#1872](https://github.com/opencloud-eu/opencloud/pull/1872)]
|
||||
- add share sync to collaborativePosix suite [[#1806](https://github.com/opencloud-eu/opencloud/pull/1806)]
|
||||
- removed test virus files from repo [[#1812](https://github.com/opencloud-eu/opencloud/pull/1812)]
|
||||
- increase timeouts waiting for notification & search [[#1802](https://github.com/opencloud-eu/opencloud/pull/1802)]
|
||||
- Sync share before action [[#1795](https://github.com/opencloud-eu/opencloud/pull/1795)]
|
||||
- correct STORAGE_USERS_POSIX_WATCH_FS env typo in CI [[#1746](https://github.com/opencloud-eu/opencloud/pull/1746)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [full-ci] revaBump-v2.40.1 [[#1927](https://github.com/opencloud-eu/opencloud/pull/1927)]
|
||||
- [full-ci] chore: bump web to v4.2.1 [[#1938](https://github.com/opencloud-eu/opencloud/pull/1938)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.76.0 to 1.77.0 [[#1923](https://github.com/opencloud-eu/opencloud/pull/1923)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.12.1 to 2.12.2 [[#1922](https://github.com/opencloud-eu/opencloud/pull/1922)]
|
||||
- build(deps): bump github.com/kovidgoyal/imaging from 1.7.2 to 1.8.17 [[#1912](https://github.com/opencloud-eu/opencloud/pull/1912)]
|
||||
- build(deps): bump golang.org/x/crypto from 0.44.0 to 0.45.0 [[#1911](https://github.com/opencloud-eu/opencloud/pull/1911)]
|
||||
- [decomposed]Update version 4.0.0 rc.2 [[#1917](https://github.com/opencloud-eu/opencloud/pull/1917)]
|
||||
- chore: bump web to v4.2.1-rc.1 [[#1900](https://github.com/opencloud-eu/opencloud/pull/1900)]
|
||||
- revaBump-getting#428 [[#1887](https://github.com/opencloud-eu/opencloud/pull/1887)]
|
||||
- build(deps): bump github.com/blevesearch/bleve/v2 from 2.5.4 to 2.5.5 [[#1884](https://github.com/opencloud-eu/opencloud/pull/1884)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 1.1.0 to 1.1.1 [[#1869](https://github.com/opencloud-eu/opencloud/pull/1869)]
|
||||
- build(deps): bump golang.org/x/term from 0.36.0 to 0.37.0 [[#1845](https://github.com/opencloud-eu/opencloud/pull/1845)]
|
||||
- reva-bump-2.39.2. update opencloud 4.0.0-rc.1 [[#1849](https://github.com/opencloud-eu/opencloud/pull/1849)]
|
||||
- build(deps): bump golang.org/x/sync from 0.17.0 to 0.18.0 [[#1836](https://github.com/opencloud-eu/opencloud/pull/1836)]
|
||||
- build(deps): bump golang.org/x/oauth2 from 0.32.0 to 0.33.0 [[#1828](https://github.com/opencloud-eu/opencloud/pull/1828)]
|
||||
- build(deps): bump github.com/KimMachineGun/automemlimit from 0.7.4 to 0.7.5 [[#1787](https://github.com/opencloud-eu/opencloud/pull/1787)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.9.0 to 1.10.1 [[#1788](https://github.com/opencloud-eu/opencloud/pull/1788)]
|
||||
- Bump reva [[#1786](https://github.com/opencloud-eu/opencloud/pull/1786)]
|
||||
- build(deps): bump github.com/gabriel-vasile/mimetype from 1.4.10 to 1.4.11 [[#1775](https://github.com/opencloud-eu/opencloud/pull/1775)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.12.0 to 2.12.1 [[#1706](https://github.com/opencloud-eu/opencloud/pull/1706)]
|
||||
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.27.1 to 2.27.2 [[#1754](https://github.com/opencloud-eu/opencloud/pull/1754)]
|
||||
|
||||
## [3.7.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.7.0) - 2025-11-03
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
+10
-8
@@ -40,7 +40,7 @@ but it should be easily transferable to other (sub)projects.
|
||||
|
||||
> **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below.
|
||||
|
||||
For general questions, please refer to [OpenCloud's FAQs](https://docs.opencloud.eu/docs/admin/resources/faq/) or check the [project page](https://github.com/opencloud-eu) for communication channels.
|
||||
For general questions, please refer to [OpenCloud's FAQs](https://opencloud.eu/faq/) or check the [project page](https://github.com/opencloud-eu) for communication channels.
|
||||
|
||||
## What to know before getting started
|
||||
|
||||
@@ -55,7 +55,7 @@ The OpenCloud project follows the strict GitHub workflow of development as brief
|
||||
### OpenCloud Company, Engineering Partners and Community
|
||||
|
||||
OpenCloud is largely created by developers who are employed by the [OpenCloud company](https://opencloud.eu), which is located in Germany.
|
||||
It is providing support for OpenCloud for customers mainly in the EU. In addition, there are engineering partners who also work full-time on OpenCloud related code, for example, on the component [REVA](https://github.com/opencloud-eu/reva/).
|
||||
It is providing support for OpenCloud for customers mainly in the EU. In addition, there are engineering partners who also work full-time on OpenCloud related code, for example, on the component [REVA](https://github.com/cs3org/reva/).
|
||||
|
||||
Because of that fact, the pace that the development is moving forward is sometimes high for people who are not willing and/or able to spend a comparable amount of time to contribute.
|
||||
Even though this can be a challenge, it should not scare anybody away. Here is our clear commitment that we feel honored by everybody who is interested in our work and improves it, no matter how big the contribution might be.
|
||||
@@ -113,7 +113,7 @@ Explain the problem and include additional details to help maintainers reproduce
|
||||
Provide more context by answering these questions:
|
||||
|
||||
* **Did the problem start happening recently** (e.g. after updating to a new version) or was this always a problem?
|
||||
* If the problem started happening recently, **can you reproduce the problem in an older version?** What's the most recent version in which the problem doesn't happen? You can find more information about how to set up in the [Getting Started guide](https://docs.opencloud.eu/docs/admin/getting-started).
|
||||
* If the problem started happening recently, **can you reproduce the problem in an older version?** What's the most recent version in which the problem doesn't happen? You can find more information about how to set up [test environments](https://docs.opencloud.eu/devel/testing) in the [developer documentation](https://docs.opencloud.eu/docs/dev/intro).
|
||||
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
|
||||
|
||||
Include details about your configuration and environment as asked for in the template.
|
||||
@@ -146,13 +146,15 @@ Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com
|
||||
|
||||
Unsure where to begin contributing to OpenCloud? You can start by looking through these `Needs-help` issues:
|
||||
|
||||
* The [Good first issue](https://github.com/opencloud-eu/opencloud/labels/Type%3Agood-first-issue) label marks good items to start with.
|
||||
* The [Feature Request](https://github.com/opencloud-eu/opencloud/issues?q=state%3Aopen%20label%3AType%3AFeature-Request) label lists features the community would like to see implemented.
|
||||
* The [Good first issue](https://github.com/opencloud-eu/opencloud/labels/Topic%3Agood-first-issue) label marks good items to start with.
|
||||
* [Tests needed](https://github.com/opencloud-eu/opencloud/labels/Interaction%3ANeeds-tests) - issues which would benefit from a test.
|
||||
* [Help wanted issues](https://github.com/opencloud-eu/opencloud/labels/Interaction%3ANeeds-help) - issues which should be a bit more involved.
|
||||
|
||||
It is fine to pick one of the lists following personal preference.
|
||||
While not perfect, the number of comments is a reasonable proxy for the impact a given change will have.
|
||||
|
||||
To find out how to set up OpenCloud for local development, please refer to the [Developer Documentation](https://docs.opencloud.eu/docs/dev/web/getting-started) for the web side, and the general server [README](https://github.com/opencloud-eu/opencloud/blob/main/README.md) for backend setup. Both contain information that will come in handy when starting to work on the project.
|
||||
To find out how to set up OpenCloud for local development, please refer to the [Developer Documentation](https://docs.opencloud.eu/docs/dev/web/getting-started).
|
||||
It contains a lot of information that will come in handy when starting to work on the project.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
@@ -219,7 +221,7 @@ To help you find issues and pull requests, each label can be used in search link
|
||||
The labels are loosely grouped by their purpose, but it's not required that every issue has a label from every group or that an issue can't have more than one label from the same group.
|
||||
|
||||
The list here contains all the more general categories of issues which are followed by a colon and a specific value.
|
||||
For example, severity 1 looks like `Priority:p1-urgent`.
|
||||
For example, severity 1 looks like `Severity:sev1-critical`.
|
||||
|
||||
#### Platform
|
||||
|
||||
@@ -255,7 +257,7 @@ Categorizes the issue to also indicate the type of the issue.
|
||||
|
||||
#### Status
|
||||
|
||||
The status in the ticket life cycle. Keep an eye on that one, especially for the `Status:Needs-Review` tag which might indicate that the reporter is asked for feedback.
|
||||
The status in the ticket life cycle. Keep an eye on that one, especially for the `Waiting-for-Feedback` tag which might indicate that the reporter is asked for feedback.
|
||||
|
||||
#### Interaction
|
||||
|
||||
|
||||
+4
-3
@@ -14,21 +14,22 @@
|
||||
# is a lot faster than the build steps below.
|
||||
|
||||
|
||||
FROM quay.io/opencloudeu/nodejs-ci:24 AS generate
|
||||
FROM owncloudci/nodejs:18 AS generate
|
||||
|
||||
COPY ./ /opencloud/
|
||||
|
||||
WORKDIR /opencloud/opencloud
|
||||
RUN make node-generate-prod
|
||||
|
||||
FROM quay.io/opencloudeu/golang-ci:1.25 AS build
|
||||
FROM golang:1.24-alpine AS build
|
||||
RUN apk add bash make git curl gcc musl-dev libc-dev binutils-gold inotify-tools vips-dev
|
||||
|
||||
COPY --from=generate /opencloud /opencloud
|
||||
|
||||
WORKDIR /opencloud/opencloud
|
||||
RUN make go-generate build ENABLE_VIPS=true
|
||||
|
||||
FROM alpine:3.24
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache attr ca-certificates curl mailcap tree vips && \
|
||||
echo 'hosts: files dns' >| /etc/nsswitch.conf
|
||||
|
||||
@@ -44,6 +44,7 @@ OC_MODULES = \
|
||||
services/invitations \
|
||||
services/nats \
|
||||
services/notifications \
|
||||
services/ocdav \
|
||||
services/ocm \
|
||||
services/ocs \
|
||||
services/policies \
|
||||
@@ -68,8 +69,11 @@ OC_MODULES = \
|
||||
protogen
|
||||
|
||||
# bin file definitions
|
||||
PHP_CODESNIFFER=vendor-bin/opencloud-codestyle/vendor/bin/phpcs
|
||||
PHP_CODEBEAUTIFIER=vendor-bin/opencloud-codestyle/vendor/bin/phpcbf
|
||||
PHP_CS_FIXER=php -d zend.enable_gc=0 vendor-bin/opencloud-codestyle/vendor/bin/php-cs-fixer
|
||||
PHP_CODESNIFFER=vendor-bin/php_codesniffer/vendor/bin/phpcs
|
||||
PHP_CODEBEAUTIFIER=vendor-bin/php_codesniffer/vendor/bin/phpcbf
|
||||
PHAN=php -d zend.enable_gc=0 vendor-bin/phan/vendor/bin/phan
|
||||
PHPSTAN=php -d zend.enable_gc=0 vendor-bin/phpstan/vendor/bin/phpstan
|
||||
|
||||
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
|
||||
include .bingo/Variables.mk
|
||||
@@ -80,7 +84,7 @@ help:
|
||||
@echo "Please use 'make <target>' where <target> is one of the following:"
|
||||
@echo
|
||||
@echo -e "${GREEN}Testing with test suite natively installed:${RESET}\n"
|
||||
@echo -e "${PURPLE}\ttests: https://github.com/opencloud-eu/opencloud/blob/main/tests/README.md#running-test-suite-in-local-environment${RESET}\n"
|
||||
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/testing/#testing-with-test-suite-natively-installed${RESET}\n"
|
||||
@echo -e "\tmake test-acceptance-api\t\t${BLUE}run API acceptance tests${RESET}"
|
||||
@echo -e "\tmake clean-tests\t\t\t${BLUE}delete API tests framework dependencies${RESET}"
|
||||
@echo
|
||||
@@ -89,11 +93,17 @@ help:
|
||||
@echo -e "${RED}You also should have a look at other available Makefiles:${RESET}"
|
||||
@echo
|
||||
@echo -e "${GREEN}opencloud:${RESET}\n"
|
||||
@echo -e "${PURPLE}\tdocs: https://github.com/opencloud-eu/opencloud/blob/main/README.md#build-opencloud${RESET}\n"
|
||||
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/build/${RESET}\n"
|
||||
@echo -e "\tsee ./opencloud/Makefile"
|
||||
@echo -e "\tor run ${YELLOW}make -C opencloud help${RESET}"
|
||||
@echo
|
||||
@echo -e "${GREEN}Documentation:${RESET}\n"
|
||||
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/build-docs/${RESET}\n"
|
||||
@echo -e "\tsee ./docs/Makefile"
|
||||
@echo -e "\tor run ${YELLOW}make -C docs help${RESET}"
|
||||
@echo
|
||||
@echo -e "${GREEN}Testing with test suite in docker:${RESET}\n"
|
||||
@echo -e "${PURPLE}\ttests: https://github.com/opencloud-eu/opencloud/blob/main/tests/README.md#running-test-suite-in-docker${RESET}\n"
|
||||
@echo -e "${PURPLE}\tdocs: https://opencloud.dev/opencloud/development/testing/#testing-with-test-suite-in-docker${RESET}\n"
|
||||
@echo -e "\tsee ./tests/acceptance/docker/Makefile"
|
||||
@echo -e "\tor run ${YELLOW}make -C tests/acceptance/docker help${RESET}"
|
||||
@echo
|
||||
@@ -104,7 +114,6 @@ help:
|
||||
@echo -e "${GREEN}Tools for linting gherkin feature files:\n${RESET}"
|
||||
@echo -e "\tmake test-gherkin-lint\t\t${BLUE}run lint checks on Gherkin feature files${RESET}"
|
||||
@echo -e "\tmake test-gherkin-lint-fix\t${BLUE}apply lint fixes to gherkin feature files${RESET}"
|
||||
@echo -e "\tmake find-unused-steps\t\t${BLUE}list Behat step definitions unused by any .feature file${RESET}"
|
||||
@echo
|
||||
|
||||
.PHONY: clean-tests
|
||||
@@ -189,10 +198,6 @@ go-mod-tidy:
|
||||
test:
|
||||
@go test -v -tags '$(TAGS)' -coverprofile coverage.out ./...
|
||||
|
||||
.PHONY: test-with-race
|
||||
test-with-race:
|
||||
@go test -race -v -tags '$(TAGS)' -coverprofile coverage.out ./...
|
||||
|
||||
.PHONY: go-coverage
|
||||
go-coverage:
|
||||
@if [ ! -f coverage.out ]; then $(MAKE) test &>/dev/null; fi;
|
||||
@@ -226,10 +231,6 @@ test-gherkin-lint:
|
||||
test-gherkin-lint-fix:
|
||||
gherlint --fix tests/acceptance/features -c tests/acceptance/config/.gherlintrc.json
|
||||
|
||||
.PHONY: find-unused-steps
|
||||
find-unused-steps: vendor-bin/behat/vendor
|
||||
tests/acceptance/scripts/find-unused-steps.sh
|
||||
|
||||
.PHONY: bingo-update
|
||||
bingo-update: $(BINGO)
|
||||
$(BINGO) get -l -v -t 20
|
||||
@@ -316,15 +317,37 @@ ci-format: $(BUILDIFIER)
|
||||
$(BUILDIFIER) --mode=fix .woodpecker.star
|
||||
|
||||
.PHONY: test-php-style
|
||||
test-php-style: vendor-bin/opencloud-codestyle/vendor
|
||||
test-php-style: vendor-bin/opencloud-codestyle/vendor vendor-bin/php_codesniffer/vendor
|
||||
$(PHP_CS_FIXER) fix -v --diff --allow-risky yes --dry-run
|
||||
$(PHP_CODESNIFFER) --cache --runtime-set ignore_warnings_on_exit --standard=phpcs.xml tests/acceptance tests/acceptance/TestHelpers
|
||||
|
||||
.PHONY: test-php-style-fix
|
||||
test-php-style-fix: vendor-bin/opencloud-codestyle/vendor
|
||||
$(PHP_CS_FIXER) fix -v --diff --allow-risky yes
|
||||
$(PHP_CODEBEAUTIFIER) --cache --runtime-set ignore_warnings_on_exit --standard=phpcs.xml tests/acceptance
|
||||
|
||||
.PHONY: vendor-bin-codestyle
|
||||
vendor-bin-codestyle: vendor-bin/opencloud-codestyle/vendor
|
||||
|
||||
.PHONY: vendor-bin-codesniffer
|
||||
vendor-bin-codesniffer: vendor-bin/php_codesniffer/vendor
|
||||
|
||||
vendor-bin/opencloud-codestyle/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/opencloud-codestyle/composer.lock
|
||||
composer bin opencloud-codestyle install --no-progress
|
||||
|
||||
vendor-bin/opencloud-codestyle/composer.lock: vendor-bin/opencloud-codestyle/composer.json
|
||||
@echo opencloud-codestyle composer.lock is not up to date.
|
||||
|
||||
vendor-bin/php_codesniffer/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/php_codesniffer/composer.lock
|
||||
composer bin php_codesniffer install --no-progress
|
||||
|
||||
vendor-bin/php_codesniffer/composer.lock: vendor-bin/php_codesniffer/composer.json
|
||||
@echo php_codesniffer composer.lock is not up to date.
|
||||
|
||||
.PHONY: generate-qa-activity-report
|
||||
generate-qa-activity-report: node_modules
|
||||
@if [ -z "${MONTH}" ] || [ -z "${YEAR}" ]; then \
|
||||
echo "Please set the MONTH and YEAR environment variables. Usage: make generate-qa-activity-report MONTH=<month> YEAR=<year>"; \
|
||||
exit 1; \
|
||||
fi
|
||||
go run tests/qa-activity-report/generate-qa-activity-report.go --month ${MONTH} --year ${YEAR}
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
[](https://ci.opencloud.rocks/repos/3)
|
||||
[](https://ci.opencloud.eu/repos/3)
|
||||
[](https://app.element.io/#/room/#opencloud:matrix.org)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Table of Contents
|
||||
|
||||
{{ range . -}}
|
||||
* [Changelog for {{ .Version }}](#changelog-for-{{ .Version | replace "." ""}}-{{ .Date | lower -}})
|
||||
{{ end -}}
|
||||
{{ $allVersions := . }}
|
||||
{{- range $index, $changes := . }}{{ with $changes -}}
|
||||
{{ if gt (len $allVersions) 1 }}
|
||||
# Changelog for [{{ .Version }}] ({{ .Date }})
|
||||
|
||||
The following sections list the changes for {{ .Version}}.
|
||||
|
||||
{{/* creating version compare links */ -}}
|
||||
{{ $next := add1 $index -}}
|
||||
{{ if ne (len $allVersions) $next -}}
|
||||
{{ $previousVersion := (index $allVersions $next).Version -}}
|
||||
{{ if eq .Version "unreleased" -}}
|
||||
[{{ .Version}}]: https://github.com/opencloud-eu/opencloud/compare/v{{ $previousVersion }}...master
|
||||
|
||||
{{ else -}}
|
||||
[{{ .Version}}]: https://github.com/opencloud-eu/opencloud/compare/v{{ $previousVersion }}...v{{ .Version}}
|
||||
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- /* last version managed by calens, end of the loop */ -}}
|
||||
{{ if eq .Version "0.1.0" -}}
|
||||
[{{ .Version }}]: https://github.com/opencloud-eu/opencloud/compare/94f19e653e30cdf16dcf23dbaf36c6d753d37ae9...v{{ .Version }}
|
||||
|
||||
{{ end -}}
|
||||
{{ else -}}
|
||||
# Changes in {{ .Version}}
|
||||
|
||||
{{ end -}}
|
||||
|
||||
## Summary
|
||||
{{ range $entry := .Entries }}{{ with $entry }}
|
||||
* {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }})
|
||||
{{- end }}{{ end }}
|
||||
|
||||
## Details
|
||||
{{ range $entry := .Entries }}{{ with $entry }}
|
||||
* {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }})
|
||||
{{ range $par := .Paragraphs -}}
|
||||
{{/* Workaround for keeping lists inside of changelog items well formatted */ -}}
|
||||
{{ if hasPrefix "*" $par }}
|
||||
{{ $par | replace " *" "\n *" }}
|
||||
{{- else }}
|
||||
{{ wrapIndent $par 80 3 -}}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ range $url := .IssueURLs }}
|
||||
{{ $url -}}
|
||||
{{ end -}}
|
||||
{{ range $url := .PRURLs }}
|
||||
{{ $url -}}
|
||||
{{ end -}}
|
||||
{{ range $url := .OtherURLs }}
|
||||
{{ $url -}}
|
||||
{{ end }}
|
||||
{{ end }}{{ end -}}
|
||||
{{ end }}{{ end -}}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
We are using [calens](https://github.com/restic/calens) to properly generate a
|
||||
changelog before we are tagging a new release. To get an idea how this could
|
||||
look like <https://github.com/restic/restic/tree/master/changelog> would be the
|
||||
best reference.
|
||||
|
||||
## Create changelog items
|
||||
|
||||
Create a file according to the template for each changelog in the unreleased folder.
|
||||
The following change types are possible:
|
||||
- Bugfix (general Bugfix)
|
||||
- Enhancement (new feature)
|
||||
- Change (breaking change)
|
||||
- Security (security related issues)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
Bugfix: Fix behavior for foobar (in present tense)
|
||||
|
||||
We've fixed the behavior for foobar, a long-standing annoyance for users. The
|
||||
text should be wrapped at 80 characters length.
|
||||
|
||||
The text in the paragraphs is written in past tense. The last section is a list
|
||||
of issue URLs, PR URLs and other URLs. The first issue ID (or the first PR ID,
|
||||
in case there aren't any issue links) is used as the primary ID.
|
||||
|
||||
https://github.com/opencloud-eu/opencloud/pull/55555
|
||||
https://github.com/opencloud-eu/opencloud/issues/1234
|
||||
|
||||
Note: Possible keywords are Bugfixes (for bug fixes), Enhancement (for new features),
|
||||
Change (for breaking changes), Security (for security related topics)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
## Release, Date, Type, Title, Primary ID, Primary URL
|
||||
{{ range . -}}
|
||||
{{ $v := .Version -}}
|
||||
{{ $d := .Date -}}
|
||||
{{ range $entry := .Entries -}}
|
||||
{{ $v }},{{ $d }},{{ .Type }},'{{ .Title }}',{{ .PrimaryID }},{{ .PrimaryURL }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
Whitespace-only changes.
@@ -1,21 +0,0 @@
|
||||
Bugfix: Retry publishing postprocessing events before giving up
|
||||
|
||||
A single transient failure while publishing an event to the event system took
|
||||
the whole server down. The postprocessing service treated every publish error
|
||||
as fatal and called log.Fatal, which exits the process and so also stopped all
|
||||
the other services running in the same binary. A burst of uploads was enough to
|
||||
run into one nats publish timeout and lose the server with it.
|
||||
|
||||
Publishing is now retried using the same exponential backoff that is already
|
||||
used for failed postprocessing steps. Between the attempts the source event is
|
||||
marked as in progress and the backoff is capped at half the ack wait, so the
|
||||
event should not be redelivered to another worker while we are still retrying.
|
||||
The number of retries is configurable via POSTPROCESSING_PUBLISH_MAX_RETRIES.
|
||||
|
||||
Should all attempts fail, the source event is no longer acknowledged. Before,
|
||||
the event was acknowledged even though its successor was never published, so
|
||||
the upload was left half processed in the store and did not recover on restart.
|
||||
|
||||
https://github.com/opencloud-eu/opencloud/issues/3271
|
||||
https://github.com/opencloud-eu/opencloud/issues/2232
|
||||
https://github.com/opencloud-eu/opencloud/issues/2422
|
||||
+4
-5
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "opencloud-eu/opencloud",
|
||||
"config": {
|
||||
"config" : {
|
||||
"platform": {
|
||||
"php": "8.4"
|
||||
"php": "8.2"
|
||||
},
|
||||
"vendor-dir": "./vendor-php",
|
||||
"allow-plugins": {
|
||||
@@ -11,12 +11,11 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-simplexml": "*",
|
||||
"bamarni/composer-bin-plugin": "^1.9"
|
||||
"bamarni/composer-bin-plugin": "^1.8"
|
||||
},
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": false,
|
||||
"forward-command": false
|
||||
"bin-links": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ set -euo pipefail
|
||||
# OC_VERSION: Version to download, e.g. OC_VERSION="1.2.0"
|
||||
|
||||
# Call this script directly from opencloud:
|
||||
# curl -L https://opencloud.eu/install | /bin/bash
|
||||
# curl -L https://opencloud.eu/quickinstall.sh | /bin/bash
|
||||
|
||||
# This function is borrowed from openSUSEs /usr/bin/old, thanks.
|
||||
function backup_file () {
|
||||
@@ -88,7 +88,7 @@ mkdir -p "$OC_CONFIG_DIR" "$OC_BASE_DATA_PATH"
|
||||
# to certain host names for example in WSL
|
||||
host="${OC_HOST:-localhost}"
|
||||
|
||||
./${dlfile} init --insecure yes --admin-password admin
|
||||
./${dlfile} init --insecure yes --ap admin
|
||||
|
||||
echo '#!/bin/bash
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "${0}")")"
|
||||
|
||||
@@ -4,11 +4,10 @@ directives:
|
||||
connect-src:
|
||||
- '''self'''
|
||||
- 'blob:'
|
||||
- 'https://${COMPANION_DOMAIN|companion.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'wss://${COMPANION_DOMAIN|companion.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://${COMPANION_DOMAIN|companion.opencloud.test}/'
|
||||
- 'wss://${COMPANION_DOMAIN|companion.opencloud.test}/'
|
||||
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
|
||||
- 'https://${IDP_DOMAIN|keycloak.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://update.opencloud.eu/'
|
||||
- 'https://${IDP_DOMAIN|keycloak.opencloud.test}/'
|
||||
default-src:
|
||||
- '''none'''
|
||||
font-src:
|
||||
@@ -20,7 +19,7 @@ directives:
|
||||
- 'blob:'
|
||||
- 'https://embed.diagrams.net/'
|
||||
# In contrary to bash and docker the default is given after the | character
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}/'
|
||||
# This is needed for the external-sites web extension when embedding sites
|
||||
- 'https://docs.opencloud.eu'
|
||||
img-src:
|
||||
@@ -28,9 +27,8 @@ directives:
|
||||
- 'data:'
|
||||
- 'blob:'
|
||||
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
|
||||
- 'https://tile.openstreetmap.org/'
|
||||
# In contrary to bash and docker the default is given after the | character
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.opencloud.test}/'
|
||||
manifest-src:
|
||||
- '''self'''
|
||||
media-src:
|
||||
@@ -41,8 +39,6 @@ directives:
|
||||
script-src:
|
||||
- '''self'''
|
||||
- '''unsafe-inline'''
|
||||
- 'https://${IDP_DOMAIN|keycloak.opencloud.test}${TRAEFIK_PORT_HTTPS}/'
|
||||
style-src:
|
||||
- '''self'''
|
||||
- '''unsafe-inline'''
|
||||
- 'blob:'
|
||||
@@ -24,40 +24,34 @@ var demoTenants = []tenantWithUsers{
|
||||
{
|
||||
tenant: libregraph.EducationSchool{
|
||||
DisplayName: libregraph.PtrString("Famous Coders"),
|
||||
ExternalId: libregraph.PtrString("famouscodersExternalID"),
|
||||
},
|
||||
users: []libregraph.EducationUser{
|
||||
{
|
||||
DisplayName: libregraph.PtrString("Dennis Ritchie"),
|
||||
OnPremisesSamAccountName: libregraph.PtrString("dennis"),
|
||||
Mail: libregraph.PtrString("dennis@example.org"),
|
||||
ExternalId: libregraph.PtrString("ExternalID1"),
|
||||
},
|
||||
{
|
||||
DisplayName: libregraph.PtrString("Grace Hopper"),
|
||||
OnPremisesSamAccountName: libregraph.PtrString("grace"),
|
||||
Mail: libregraph.PtrString("grace@example.org"),
|
||||
ExternalId: libregraph.PtrString("ExternalID2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
tenant: libregraph.EducationSchool{
|
||||
DisplayName: libregraph.PtrString("Scientists"),
|
||||
ExternalId: libregraph.PtrString("scientistsExternalID"),
|
||||
},
|
||||
users: []libregraph.EducationUser{
|
||||
{
|
||||
DisplayName: libregraph.PtrString("Albert Einstein"),
|
||||
OnPremisesSamAccountName: libregraph.PtrString("einstein"),
|
||||
Mail: libregraph.PtrString("einstein@example.org"),
|
||||
ExternalId: libregraph.PtrString("ExternalID3"),
|
||||
},
|
||||
{
|
||||
DisplayName: libregraph.PtrString("Marie Curie"),
|
||||
OnPremisesSamAccountName: libregraph.PtrString("marie"),
|
||||
Mail: libregraph.PtrString("marie@example.org"),
|
||||
ExternalId: libregraph.PtrString("ExternalID4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -96,7 +90,6 @@ func main() {
|
||||
}
|
||||
|
||||
resetAllUserPasswords()
|
||||
setUserRoles()
|
||||
}
|
||||
|
||||
func createUser(client *libregraph.APIClient, user libregraph.EducationUser) (string, error) {
|
||||
@@ -119,34 +112,6 @@ func createTenant(client *libregraph.APIClient, tenant libregraph.EducationSchoo
|
||||
return newTenant.GetId(), nil
|
||||
}
|
||||
|
||||
func setUserRoles() {
|
||||
tls := tls.Config{InsecureSkipVerify: true}
|
||||
restyClient := resty.New().SetTLSClientConfig(&tls)
|
||||
client := gocloak.NewClient("https://keycloak.opencloud.test")
|
||||
client.SetRestyClient(restyClient)
|
||||
ctx := context.Background()
|
||||
token, err := client.LoginAdmin(ctx, "kcadmin", "admin", "master")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to login: %s\n", err)
|
||||
panic("Something wrong with the credentials or url")
|
||||
}
|
||||
|
||||
role, _ := client.GetRealmRole(ctx, token.AccessToken, "openCloud", "opencloudUser")
|
||||
users, err := client.GetUsers(ctx, token.AccessToken, "openCloud", gocloak.GetUsersParams{})
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
panic("Oh no!, failed to list users :(")
|
||||
}
|
||||
for _, user := range users {
|
||||
err := client.AddRealmRoleToUser(ctx, token.AccessToken, "openCloud", *user.ID, []gocloak.Role{
|
||||
*role,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to assign role to user %s: %s\n", *user.Username, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resetAllUserPasswords() {
|
||||
tls := tls.Config{InsecureSkipVerify: true}
|
||||
restyClient := resty.New().SetTLSClientConfig(&tls)
|
||||
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
- "traefik.http.services.opencloud.loadbalancer.server.port=9200"
|
||||
- "traefik.http.routers.opencloud.${TRAEFIK_SERVICES_TLS_CONFIG}"
|
||||
traefik:
|
||||
image: traefik:v3.6.7
|
||||
image: traefik:v3.3.1
|
||||
# release notes: https://github.com/traefik/traefik/releases
|
||||
networks:
|
||||
opencloud-net:
|
||||
|
||||
@@ -42,4 +42,3 @@ directives:
|
||||
style-src:
|
||||
- '''self'''
|
||||
- '''unsafe-inline'''
|
||||
- 'blob:'
|
||||
@@ -19,11 +19,6 @@ services:
|
||||
- "--entryPoints.http.http.redirections.entryPoint.to=https"
|
||||
- "--entryPoints.http.http.redirections.entryPoint.scheme=https"
|
||||
- "--entryPoints.https.address=:443"
|
||||
# http2 optimizations
|
||||
- "--entryPoints.https.http2.maxConcurrentStreams=512"
|
||||
- "--serversTransport.maxIdleConnsPerHost=100"
|
||||
# allow self signed certificate from OpenCloud
|
||||
- "--serversTransport.insecureSkipVerify=true"
|
||||
# change default timeouts for long-running requests
|
||||
# this is needed for webdav clients that do not support the TUS protocol
|
||||
- "--entryPoints.https.transport.respondingTimeouts.readTimeout=12h"
|
||||
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
OC_LOG_COLOR: "${LOG_PRETTY:-false}"
|
||||
OC_LOG_PRETTY: "${LOG_PRETTY:-false}"
|
||||
# do not use SSL between Traefik and OpenCloud
|
||||
PROXY_TLS: "true"
|
||||
PROXY_TLS: "false"
|
||||
# make the REVA gateway accessible to the app drivers
|
||||
GATEWAY_GRPC_ADDR: 0.0.0.0:9142
|
||||
# INSECURE: needed if OpenCloud / Traefik is using self generated certificates
|
||||
@@ -72,7 +72,6 @@ services:
|
||||
- "traefik.http.routers.opencloud.tls.certresolver=http"
|
||||
- "traefik.http.routers.opencloud.service=opencloud"
|
||||
- "traefik.http.services.opencloud.loadbalancer.server.port=9200"
|
||||
- "traefik.http.services.opencloud.loadbalancer.server.scheme=https"
|
||||
logging:
|
||||
driver: ${LOG_DRIVER:-local}
|
||||
restart: always
|
||||
|
||||
@@ -32,7 +32,7 @@ olcObjectClasses: ( openCloudOid:1.2.3 NAME 'openCloudEducationUser'
|
||||
DESC 'OpenCloud education user objectclass'
|
||||
SUP openCloudUser
|
||||
AUXILIARY
|
||||
MAY ( openCloudEducationExternalId $ userClass $ openCloudMemberOfSchool ) )
|
||||
MAY ( userClass $ openCloudMemberOfSchool ) )
|
||||
olcObjectClasses: ( openCloudOid:1.2.4 NAME 'openCloudEducationClass'
|
||||
DESC 'OpenCloud education class objectclass'
|
||||
SUP openCloudObject
|
||||
@@ -42,4 +42,4 @@ olcObjectClasses: ( openCloudOid:1.2.5 NAME 'openCloudEducationSchool'
|
||||
DESC 'OpenCloud education school objectclass'
|
||||
SUP openCloudObject
|
||||
AUXILIARY
|
||||
MAY ( openCloudEducationSchoolNumber $ openCloudEducationSchoolTerminationTimestamp $ openCloudEducationExternalId) )
|
||||
MAY ( openCloudEducationSchoolNumber $ openCloudEducationSchoolTerminationTimestamp ) )
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
title: "Discover OIDC Client configuration via WebFinger"
|
||||
---
|
||||
|
||||
* Status: accepted
|
||||
* Deciders: [@TheOneRing @kulmann @rhafer @dragotin]
|
||||
* Date: 2026-02-02
|
||||
|
||||
Reference: https://github.com/opencloud-eu/opencloud/pull/2072, https://github.com/opencloud-eu/desktop/issues/217
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Up to now our client applications used hard-coded OIDC client configurations.
|
||||
So it is not possible to change the client id that a client should use or the
|
||||
list of scopes that a client needs to request. This makes it hard to integrate
|
||||
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 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.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* Support broader set of IDPs
|
||||
* avoid any manual configuration adjustments on the client side
|
||||
|
||||
## Decision
|
||||
|
||||
Enhance the WebFinger service in OpenCloud to provide platform-specific OIDC
|
||||
discovery, enabling clients to query for the correct OIDC `client_id` and
|
||||
`scopes` based on their application type (e.g., web, desktop, android, ios).
|
||||
|
||||
This is achieved by allowing an additional `platform` query parameter to be used
|
||||
when querying the WebFinger endpoint. The response will include the appropriate
|
||||
`client_id` and `scopes` in the `properties` section of the response.
|
||||
|
||||
This is implemented in a backward-compatible way, so existing clients that do not
|
||||
specify the `platform` parameter will continue to receive just the issuer information.
|
||||
|
||||
## Example
|
||||
|
||||
### Client Request
|
||||
|
||||
```
|
||||
GET /.well-known/webfinger?resource=https://cloud.opencloud.test&rel=http://openid.net/specs/connect/1.0/issuer&platform=desktop
|
||||
```
|
||||
|
||||
### Server Response
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "https://cloud.opencloud.test",
|
||||
"links": [{
|
||||
"rel": "http://openid.net/specs/connect/1.0/issuer",
|
||||
"href": "https://idp.example.com"
|
||||
}],
|
||||
"properties": {
|
||||
"http://opencloud.eu/ns/oidc/client_id": "desktop-client-id",
|
||||
"http://opencloud.eu/ns/oidc/scopes": ["openid", "profile", "email", "offline_access"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Server configuration (suggestion)
|
||||
|
||||
To configure the OpenCloud server a couple of new config settings need to be introduced. This would
|
||||
be two new settings per client, e.g.:
|
||||
|
||||
|
||||
```
|
||||
WEBFINGER_ANDROID_OIDC_CLIENT_ID
|
||||
WEBFINGER_ANDROID_OIDC_CLIENT_SCOPES
|
||||
WEBFINGER_DESKTOP_OIDC_CLIENT_ID
|
||||
WEBFINGER_DESKTOP_OIDC_CLIENT_SCOPES
|
||||
WEBFINGER_IOS_OIDC_CLIENT_ID
|
||||
WEBFINGER_IOS_OIDC_CLIENT_SCOPES
|
||||
WEBFINGER_WEB_OIDC_CLIENT_ID
|
||||
WEBFINGER_WEB_OIDC_CLIENT_SCOPES
|
||||
```
|
||||
|
||||
Additionally for backwards compatibility the existing `WEB_OIDC_CLIENT_ID` and
|
||||
`WEB_OIDC_CLIENT_SCOPE` settings should be used as fallback for the `web`
|
||||
platform. Also we should make it easy to configure the same settings for all
|
||||
platforms at once by using `OC_OIDC_CLIENT_ID` and `OC_OIDC_CLIENT_SCOPE` as
|
||||
fallback for all platforms if the platform-specific settings are not set.
|
||||
@@ -1,212 +0,0 @@
|
||||
---
|
||||
title: "Inviting Guest users as fully integrated user in OpenCloud"
|
||||
---
|
||||
|
||||
* Status: postponed
|
||||
* Deciders: []
|
||||
* Date: 2026-01-20
|
||||
|
||||
Reference: https://github.com/opencloud-eu/opencloud/issues/2111
|
||||
|
||||
## Important Disclaimer
|
||||
|
||||
The approach discussed here has been postponed (as of March, 2026) in favor
|
||||
of a different solution that does not require full-blown user-accounts in
|
||||
OpenCloud. That approach is currently tracked here:
|
||||
https://github.com/opencloud-eu/opencloud/issues/2513
|
||||
|
||||
## Context and Problem statement
|
||||
|
||||
To allow collaboration with external Users (Users that don't yet have an
|
||||
account in the IDP, and might be external to the organization), it should
|
||||
be possible to invite "Guest Users" into an OpenCloud instance.
|
||||
|
||||
## Requirements
|
||||
|
||||
- the audit trail of the external user accessing the resource needs to
|
||||
be maintained, that means sharing via a password protected public link
|
||||
is not sufficient as access to that one is tracked as if the creator
|
||||
of the link accessed the resource
|
||||
- external users need to be authenticated just like "normal" users, when
|
||||
accessing the shared resource (including the possibility to use 2FA)
|
||||
- the ability to invite external users is tied to a separate permission
|
||||
(e.g. "can invite guest users")
|
||||
- make it work with all (most) of the user-management configurations we support.
|
||||
The built-in IDP (lico) does not need to be supported.
|
||||
- avoid creating "Shadow IT" Infrastructure, e.g. we don't want to
|
||||
create/maintain a separate IDP instance just for Guest User that would
|
||||
allow bypassing corporate rules for Identity Management
|
||||
- the process of inviting a guest must can be asynchrounous, i.e. the user
|
||||
account of the guest user might not be created at the moment of
|
||||
creating the share/sending the invitaion as the whole process crosses
|
||||
multiple systems (OpenCloud, Identity Management System, Email) and might
|
||||
even require manual steps.
|
||||
- It should be possible to "convert" a guest user into a "normal" user without
|
||||
the user loosing their shares.
|
||||
- Guest user invitations should have an expiration date, after which they can
|
||||
no longer be accepted.
|
||||
|
||||
|
||||
### Privileges of guest users
|
||||
|
||||
- guest users can not share or invite other users to a space or create public
|
||||
links. (primary focus of the feature is to provide a simple way to grant
|
||||
external, authorized access. anything else like resharing would undermine
|
||||
regular user accounts).
|
||||
- guestusers can use the desktop and mobile client to access their shares or spaces
|
||||
|
||||
|
||||
- all "normal" users are able to share with guest users, just as if they where "normal" users.
|
||||
|
||||
|
||||
## Questions still to be answered
|
||||
|
||||
- what's the life cycle of a guest user?
|
||||
- Who's responsible for deprovisioning?
|
||||
- Do guest users expire after a certain time?
|
||||
- Do we need to keep track of who invited whom and when? (not just in
|
||||
the audit log?)
|
||||
- What if the user already exists but used a different mail address in
|
||||
his account (e.g. sub-addressing?).
|
||||
|
||||
## Obstacles
|
||||
|
||||
### UserIDs
|
||||
|
||||
- Every user in OpenCloud needs to have a userid assigned
|
||||
- Sharing, as many other features, needs that userid for storing the
|
||||
share (share service) and for assigning the grants on the shared
|
||||
resources (storage provider)
|
||||
- When an external IDP is used the generation of that userid is usually
|
||||
not in control of OpenCloud (exception User-Autoprovisioning, or when
|
||||
the Provisioning/Education API is used). In that case, the userid is
|
||||
taken from a LDAP Attribute maintained in the external system
|
||||
|
||||
### Lots of identity management options
|
||||
|
||||
- OpenCloud provides many different ways to consume user-accounts. Guest
|
||||
users are supposed to be working with all/most of them:
|
||||
- External IDP, with external LDAP service
|
||||
- External IDP, with manual provisioning via the
|
||||
Education/Provisioning APIs (to a local OpenCloud specific LDAP
|
||||
service) - e.g. in multi-tenant setups
|
||||
- External IDP, with User-Autoprovisioning (also to a local OpenCloud
|
||||
specific LDAP service)
|
||||
- everything in-between and outside of the above
|
||||
- Each of these options have different ways for user-provisioning and in
|
||||
the way userids are generated and managed
|
||||
|
||||
### How do we keep track of invitations?
|
||||
|
||||
- Completely rely on external system?
|
||||
- Track creation and acceptance of invitations somehow?
|
||||
- Do invitation expire at some point?
|
||||
|
||||
## Possible solutions
|
||||
|
||||
### Re-vitalize the PoC implementation of the invitations service and finalize it (<https://github.com/opencloud-eu/opencloud/blob/main/services/invitations/README.md>)
|
||||
|
||||
- Implements parts of the MSGraph Invitation Specification
|
||||
(<https://learn.microsoft.com/en-us/graph/api/resources/invitation?view=graph-rest-1.0>)
|
||||
- Currently there's just a single backend that allows creating users,
|
||||
using the Keycloak Admin API
|
||||
- As part of the user creation keycloak triggers an email to be sent to
|
||||
the invited user to get him to verify his email address and set a
|
||||
password. This is not really and invitation email.
|
||||
|
||||
#### Pros
|
||||
|
||||
- A partial implementation already exists
|
||||
- no shadow IT
|
||||
|
||||
#### Cons
|
||||
|
||||
- while the emails sent by keycloak can be themed. There is no way
|
||||
to add custom content, like: "you've being invited by user X to
|
||||
access resource Y"
|
||||
- the keycloak admin API does not return the password reset link in
|
||||
the response, so we can't use that to send a custom email
|
||||
- the keycloak implementation is not a real "user invitation"
|
||||
workflow, the user experience for the invited user is not ideal
|
||||
- The workflow likely only works with a limited set of setups.
|
||||
(Specifically: a keycloak that is able to write into a connected
|
||||
LDAP database, that OpenCloud can consume)
|
||||
- As the invitations are not really tracked, e.g. we don't really
|
||||
"know" if an invitation was accepted
|
||||
- Requires direct access to the Identity Management System
|
||||
|
||||
### Invitation Service + support for pending shares in the share manager
|
||||
|
||||
- Create some form in invitation manager and provide tools/documentation
|
||||
for customers to hook that up with their Identity Management System
|
||||
- User's with the "right" privileges are able to create invitations,
|
||||
invitations get a unique identifier. Other data maintained on the
|
||||
invitation:
|
||||
- Invited user's email address
|
||||
- Invited user's userid (once the user account was provisioned)
|
||||
- Inviting user's userid
|
||||
- Creation timestamp
|
||||
- Invitation State (Pending, Accepted, …)
|
||||
- (more probably)
|
||||
- our sharing API
|
||||
('graph/v1beta1/drives/{drive-id}/items/{item-id}/invite') is enhanced
|
||||
to allow creating shares that target an invitation as the share
|
||||
recipient. (That share would only be persisted in the 'shares' service
|
||||
and would not yet crate any grants on the filesystem, or send out
|
||||
sharing notifications). (Requires changes to the CS3 sharing APIs)
|
||||
- A middleware (specific to the Identity Management System) is
|
||||
"informed" (e.g. via web hooks or a message queue) when a new
|
||||
invitation is created. That middleware is responsible for provisioning
|
||||
the user account of the guest user. Whatever this process looks like
|
||||
it completely up to the middleware (maybe it triggers some invitation
|
||||
workflow or it could just even open a support ticket with the IDP
|
||||
admin)
|
||||
- once the user is provisioned the middleware calls back into our
|
||||
invitations service,marks the invitation as "accepted" and provides
|
||||
the "userid" of the guest user. The invitations service then triggers
|
||||
the "pending" shares to be processes, which causes the filesystem
|
||||
grants to be written and notifications to be send out to the guest
|
||||
user.
|
||||
- We'd provide a reference implementation of that middleware, that works
|
||||
with keycloak
|
||||
|
||||
#### Pros
|
||||
|
||||
- Agnostic to whatever Identity Management System is used
|
||||
- We have an audittrail about who was invited by whom at what point
|
||||
in time
|
||||
- no shadow IT
|
||||
|
||||
#### Cons
|
||||
|
||||
- somewhat complex
|
||||
- likely requires changes to the CS3 APIs
|
||||
|
||||
#### Implementation Obstacles
|
||||
|
||||
- Permissions on spaces are currently not tracked in the share
|
||||
manager, the are purely managed via grants. So currently the share
|
||||
manager service currently does not know anything about (invited)
|
||||
users being assigned to spaces
|
||||
|
||||
## Additional thoughts
|
||||
|
||||
If OpenCloud were responsible for allocating the UserIDs of all users
|
||||
the solution sketch above would likely loose some of its complexity. We
|
||||
would "roll" the userid for the invited user already when creating the invite.
|
||||
That would allow to skip the step of creating a "pending" Share with an
|
||||
invitation assigned. As we have an ID already, we could just create a "normal"
|
||||
share and even populate the grants on the filesystem for that share (or space)
|
||||
|
||||
We've been pondering on the idea of making OpenCloud manage all UserIDs
|
||||
for quite a while as it would have some additional benefits for the
|
||||
whole user management story.
|
||||
|
||||
- We wouldn't rely anymore on the external Identity Management system to
|
||||
provide a unique id with certain properties. Ideally the only unique
|
||||
thing we'd need from the external system is the `iss` and `sub`
|
||||
claims of the IDP and those are required by the OIDC standards.
|
||||
|
||||
It could be worth to spend some time on figuring out a migration path
|
||||
towards such a solution, before spending resources on a complex guest
|
||||
features implementation.
|
||||
@@ -1,226 +0,0 @@
|
||||
---
|
||||
title: "5. Unified Search Index Mapping"
|
||||
---
|
||||
|
||||
* Status: accepted
|
||||
* Deciders: @aduffeck, @butonic, @dschmidt, @fschade
|
||||
* Date: 2026-04-23, accepted and updated to the implemented state 2026-08-31
|
||||
|
||||
Reference: implemented by https://github.com/opencloud-eu/opencloud/pull/3345 (reflection-based mapping, search siblings, shared query lowering) and https://github.com/opencloud-eu/opencloud/pull/3197 (schema versioning and startup checks). https://github.com/opencloud-eu/opencloud/pull/2659 was the original proof-of-concept.
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
This section describes the state at decision time (April 2026); the implementation has since resolved the problems listed here.
|
||||
|
||||
The search service supports two backends, bleve (embedded) and
|
||||
OpenSearch (external). Each backend currently carries its own,
|
||||
independently maintained description of the index layout:
|
||||
|
||||
- The bleve backend hand-builds a document mapping that explicitly
|
||||
declares only Name, Tags, Favorites and Content. Everything else,
|
||||
including the entire facet block (audio, image, photo, location),
|
||||
is left to bleve's dynamic mapping.
|
||||
- The OpenSearch backend ships a static JSON template that covers a
|
||||
similar but not identical subset, plus a few OpenSearch-specific
|
||||
primitives (path_hierarchy analyzer, wildcard MimeType). It does
|
||||
not list the facet sub-fields either; they are produced by
|
||||
OpenSearch's dynamic templating at first write.
|
||||
- The graph DriveItem assembly path keeps its own private copy of a
|
||||
reflection-based walker to turn CS3 ArbitraryMetadata back into
|
||||
typed libregraph facets, parallel to the search service's
|
||||
reflection helpers but maintained separately.
|
||||
- The bleve KQL compiler keeps a hand-maintained set of field names
|
||||
whose query values need to be pre-lowercased, with a comment that
|
||||
literally says "Keep in sync with index.go".
|
||||
|
||||
The current implementation has three concrete problems:
|
||||
|
||||
1. **The two backends do not behave the same.** Both rely on their
|
||||
own implicit defaults for fields that are not explicitly
|
||||
declared. The inferred shapes differ: bleve produces keyword-
|
||||
analyzed text, OpenSearch produces a `text + keyword` multi-field
|
||||
with auto-detected dates. Nobody has written down which behavior
|
||||
is the intended one. Two concrete instances surfaced while building
|
||||
#2659:
|
||||
- **mtime** is stored as an RFC3339 string. OpenSearch's dynamic
|
||||
mapping auto-detects it as `date`; bleve leaves it `keyword`. So
|
||||
`mtime:>...` is a chronological range on OpenSearch but a
|
||||
lexicographic string compare on bleve.
|
||||
- **name/tags**: bleve indexes a single lowercase token (exact or
|
||||
wildcard match only); OpenSearch word-tokenizes, so a bare
|
||||
`name:report` matches "My Report.txt" on OpenSearch but not on
|
||||
bleve.
|
||||
2. **Drift risk.** The OpenSearch JSON template is a subset of what
|
||||
actually gets indexed. Even where it overlaps with the bleve
|
||||
mapping it diverges on analyzer choices. Because the facet
|
||||
fields were not reachable from user queries at the time (no dot
|
||||
syntax in the KQL compilers, no facet exposure on the hit and
|
||||
REPORT paths), the divergence has been invisible, but it would
|
||||
surface the moment the first working cross-backend facet query
|
||||
landed.
|
||||
3. **Per-facet cost.** Adding a new facet (motionPhoto, etc.)
|
||||
requires coordinated edits across the proto message, both backend
|
||||
mappings, the bleve hit converters, the OpenSearch convert
|
||||
closures, the search service's metadata persistence, the graph
|
||||
DriveItem assembly, and the KQL compiler's lowercasing set. Most
|
||||
of those edits are boilerplate following a copy-paste pattern.
|
||||
Adding a genuinely new index capability (geopoint, wildcard,
|
||||
...) means wiring it in at every one of those sites, and there
|
||||
is no single place to hook a type-specific adapter.
|
||||
|
||||
### A note on backwards compatibility
|
||||
|
||||
That the facet fields were unreachable at decision time has a
|
||||
useful corollary for this ADR: **changing the indexed shape of the
|
||||
facet fields cannot break any existing client of the search
|
||||
service**, because no client could successfully read them. The behavior changes
|
||||
discussed below are therefore additive in a literal sense; nothing
|
||||
that works today stops working as a result.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* **Predictable OpenCloud API behavior independent of backend.**
|
||||
Consumers of the search service should be able to rely on the
|
||||
documented behavior of the API, not on which backend happens to
|
||||
be configured. Today the same query can give different results
|
||||
depending on whether bleve or OpenSearch is wired in (bleve's
|
||||
dynamic default is `keyword`, exact match; OpenSearch's dynamic
|
||||
default is `text + keyword`, also matches sub-tokens of a
|
||||
string). That is backend-implementation leakage, and trying to
|
||||
keep the two implicit defaults synchronized has not worked.
|
||||
* Single source of truth for the indexed schema, so the two backends
|
||||
cannot drift silently again.
|
||||
* Reduce the per-facet cost so future facets (motionPhoto and
|
||||
whatever comes next) can be added with minimal boilerplate.
|
||||
* Establish a single place to hook index-type-specific behavior, so
|
||||
a new capability needs to be implemented at most once per backend
|
||||
and then becomes available for any field uniformly.
|
||||
* A one-time reindex is an acceptable upgrade path. Both bleve and
|
||||
OpenSearch store their mapping alongside the data; existing
|
||||
indexes keep serving queries against their stored shape without
|
||||
any automatic reshaping. Benefiting from the new behavior is done
|
||||
by creating a fresh index and re-ingesting, which is the normal
|
||||
reindex flow, rather than by inventing migration tooling.
|
||||
|
||||
## Considered Options
|
||||
|
||||
### Option 1: Do nothing, keep relying on implicit backend defaults
|
||||
|
||||
Accept that bleve and OpenSearch each fall back to their own
|
||||
dynamic-mapping defaults for whatever is not explicitly declared,
|
||||
and treat the observable search behavior of OpenCloud as "whatever
|
||||
the configured backend happens to do". Adding a facet stays a
|
||||
copy-paste coordination across half a dozen sites; the existing
|
||||
divergence between bleve (keyword) and OpenSearch (`text + keyword`
|
||||
multi-field plus auto-date detection) stays silently in place
|
||||
until a working query actually reaches the diverging field and
|
||||
returns different answers on the two backends.
|
||||
|
||||
Low upfront work, but it makes the OpenCloud API behavior a
|
||||
function of the backend rather than a contract, and it keeps the
|
||||
per-facet boilerplate cost for every new field.
|
||||
|
||||
### Option 2: Generate one backend's mapping from the other
|
||||
|
||||
Treat one backend as canonical (likely bleve, because Go types) and
|
||||
derive the other. Partial answer; it still does not help the reader
|
||||
path or the graph walker, and still leaves per-facet boilerplate in
|
||||
non-mapping code.
|
||||
|
||||
### Option 3: A struct-driven mapping (chosen)
|
||||
|
||||
Let the Go struct that represents an indexed document, together
|
||||
with a small overrides map, be the single source of truth. A
|
||||
reflection-based helper walks the struct via json tags and emits
|
||||
each backend's index mapping. The same definition drives the
|
||||
write-time path, the hit-decoding path, and the query compiler's
|
||||
case-folding rules. Any future field follows one declaration in
|
||||
one place and falls through the whole pipeline consistently.
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Adopt Option 3. The Go struct that represents an indexed document,
|
||||
together with a small overrides map, becomes the single source of
|
||||
truth for the search index. The bleve and OpenSearch index
|
||||
mappings, the write-time conversion, the hit-decoding path, and
|
||||
the query compiler's case-folding rules are all derived from that
|
||||
same definition. Drift between backends is prevented by
|
||||
construction, because there is no second place to edit.
|
||||
|
||||
The overrides surface stays small. Each entry declares one of a
|
||||
handful of things per field: a semantic type for fields whose
|
||||
intent cannot be inferred from the Go type (for example a path-
|
||||
analyzed field, a fulltext field, a geopoint field), or search-
|
||||
behavior flags (case-insensitivity, word breaking, inclusion in
|
||||
the catch-all field). Any field that needs something beyond the
|
||||
inferred defaults gets one line in the overrides map and that one
|
||||
line flows through every derived piece. Overrides are validated at
|
||||
startup so a typo fails loudly instead of silently disabling a
|
||||
setting.
|
||||
|
||||
A practical consequence of having one place to hook things: when a
|
||||
new capability is needed (a geopoint representation, a sibling
|
||||
field for a different aggregation behavior, a different analyzer
|
||||
for a class of fields, ...) it can be implemented once per backend
|
||||
in the central pipeline. After that, turning the capability on for
|
||||
a specific field is a single override entry, and both backends
|
||||
adopt it the same way. This ADR does not decide which capabilities
|
||||
to add, only that they will land in this uniform shape rather than
|
||||
through coordinated per-site edits.
|
||||
|
||||
### Facet values are indexed as case-preserving keywords
|
||||
|
||||
All facet sub-fields, meaning any leaf inside `audio`, `photo`, `image`, `location` and the facets that followed (`video`, `motionPhoto`, `livePhoto`), keep a case-preserving keyword as their stored base field on both backends. The raw value the extractor saw, or the CS3 ArbitraryMetadata string, is what lands in the index, and it is what returning, sorting and aggregations read.
|
||||
|
||||
This is the single intended semantic for facets across bleve and
|
||||
OpenSearch, and it is driven by what aggregations need.
|
||||
Aggregation buckets ("group all files by `audio.artist`", "list
|
||||
distinct `photo.cameraMake`") return bucket keys drawn from the
|
||||
indexed terms. If the indexing analyzer lowercases (OpenSearch's
|
||||
default `text + keyword` multi-field against the text leg, or a
|
||||
`lowercaseKeyword`-style analyzer), the buckets come back lower-
|
||||
cased: a distinct-artists query would answer `motörhead` and
|
||||
`queen` instead of the original display casings, and two tag
|
||||
writers using `Motörhead` versus `MOTÖRHEAD` would collapse into a
|
||||
single bucket labelled `motörhead`. For a metadata display use
|
||||
case (thumbnails, facet filters in the UI, distinct lists) that
|
||||
behavior is not what we want.
|
||||
|
||||
Searching is layered on top as exactly the strict superset the proposal reserved for later, and it shipped with the implementation: every keyword field additionally gets search-only sibling fields derived from the same definition, a `_lowercase` keyword sibling (doc values disabled; serves wildcards and `=` whole-value matches) and a `_words` text sibling (`words` analyzer: dots to spaces, unicode tokenization, lowercasing, no stemming; serves token and phrase matches). Case-insensitive, word-broken search is the default for every keyword field including facets; fields opt out per override where that is wrong: opaque ids (`ID`, `RootID`, `ParentID`, `Favorites`, `livePhoto.contentId`), the POSIX `Path`, the normalized `MimeType`, and `Content`, which is a fulltext field of its own. Aggregation buckets keep their display casing because they read the base field, never the siblings.
|
||||
|
||||
The query side derives from the same source: the shared lowering pass resolves field names case-insensitively, folds values and routes each match to the right sibling (wildcards to `_lowercase`, tokens and phrases to `_words`, `=` as a whole-value term on `_lowercase`), and both backend compilers consume that one decision. The engine parity suite pins the resulting behavior against bleve and OpenSearch, so a divergence fails CI instead of surfacing in production. The case-sensitivity alignment started in #2633 is completed by deriving both sides from the same source.
|
||||
|
||||
### Schema versioning and upgrades
|
||||
|
||||
Index names carry a schema version derived from the single `search.SchemaVersion` constant (`opencloud-resource-v4`, `bleve-v4`). On startup the service classifies the stored mapping against the code: additive changes (new fields, unchanged analyzers) are reconciled in place without a version bump, breaking changes make the service refuse to start and name the reindex steps. The upgrade path is a plain reindex (`opencloud search index --all-spaces`) into the new versioned index; older indexes stay untouched and can be deleted afterwards (services/search/MIGRATION.md). Golden mapping tests on both backends pin the rendered mappings and reuse the same classifier to tell a contributor whether a change needs only a golden regeneration or a version bump.
|
||||
|
||||
### Known trade-off
|
||||
|
||||
The write-time pipeline produces the document as a generic map via
|
||||
a json round-trip. The OpenSearch write path already does the
|
||||
equivalent today via the same json-based conversion helper, so
|
||||
that path is unchanged. The bleve write path, which previously
|
||||
handed the struct directly to bleve's reflective indexer, now goes
|
||||
through the same map-producing step and pays roughly the same
|
||||
cost. On hot paths (initial indexing of a large space) this is
|
||||
measurable but not significant; if it ever matters, a direct
|
||||
reflection walker can replace the json round-trip without changing
|
||||
any call site.
|
||||
|
||||
### Follow-ups out of scope for this ADR
|
||||
|
||||
- **WebDAV REPORT facet exposure.** The current webdav search
|
||||
endpoint renders none of the facet fields back to the client.
|
||||
This is a missing feature, not a regression of the proposal;
|
||||
its natural resolution is to let the graph-search endpoint
|
||||
(proposed in #3211) take over once graph search lands.
|
||||
- **Graph search hit conversion.** Graph search (#3211) translates
|
||||
proto hits back into libregraph DriveItems with the same
|
||||
facet-copy helper the search service uses internally.
|
||||
- **reva's PROPFIND facet listing** uses its own hand-maintained
|
||||
per-facet key lists. reva deliberately does not depend on the
|
||||
libregraph Go types, so unifying those key sets is a reva-side
|
||||
decision tracked separately.
|
||||
- **Write-path performance.** The json round-trip in the bleve
|
||||
write path is an optional optimisation target with no call-site
|
||||
impact when it lands.
|
||||
@@ -1,28 +1,29 @@
|
||||
module github.com/opencloud-eu/opencloud
|
||||
|
||||
go 1.25.9
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/CiscoM31/godata v1.0.11
|
||||
github.com/KimMachineGun/automemlimit v1.0.0
|
||||
github.com/KimMachineGun/automemlimit v0.7.5
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/MicahParks/keyfunc/v2 v2.1.0
|
||||
github.com/Nerzal/gocloak/v13 v13.9.0
|
||||
github.com/bbalet/stopwords v1.0.0
|
||||
github.com/beevik/etree v1.7.1
|
||||
github.com/blevesearch/bleve/v2 v2.6.1
|
||||
github.com/beevik/etree v1.6.0
|
||||
github.com/blevesearch/bleve/v2 v2.5.5
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/coreos/go-oidc/v3 v3.20.0
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20260424072047-8d9ef7076ae9
|
||||
github.com/davidbyttow/govips/v2 v2.18.0
|
||||
github.com/coreos/go-oidc/v3 v3.16.0
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250908152307-4ca807afe54e
|
||||
github.com/davidbyttow/govips/v2 v2.16.0
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
|
||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
||||
github.com/gabriel-vasile/mimetype v1.4.15
|
||||
github.com/gabriel-vasile/mimetype v1.4.11
|
||||
github.com/ggwhite/go-masker v1.1.0
|
||||
github.com/go-chi/chi/v5 v5.3.2
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-ldap/ldap/v3 v3.4.14
|
||||
github.com/go-jose/go-jose/v3 v3.0.4
|
||||
github.com/go-ldap/ldap/v3 v3.4.12
|
||||
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
|
||||
github.com/go-micro/plugins/v4/client/grpc v1.2.1
|
||||
github.com/go-micro/plugins/v4/logger/zerolog v1.2.0
|
||||
@@ -32,87 +33,85 @@ require (
|
||||
github.com/go-micro/plugins/v4/store/nats-js-kv v0.0.0-20240726082623-6831adfdcdc4
|
||||
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
|
||||
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
|
||||
github.com/go-playground/validator/v10 v10.30.3
|
||||
github.com/go-resty/resty/v2 v2.17.2
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-tika v0.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/config/v2 v2.2.9
|
||||
github.com/gookit/config/v2 v2.2.7
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3
|
||||
github.com/invopop/validation v0.8.0
|
||||
github.com/jellydator/ttlcache/v2 v2.11.1
|
||||
github.com/jellydator/ttlcache/v3 v3.4.1
|
||||
github.com/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.23
|
||||
github.com/leonelquinteros/gotext v1.7.3-0.20260422134830-b012b4ccae69
|
||||
github.com/kovidgoyal/imaging v1.7.2
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/libregraph/idm v0.5.0
|
||||
github.com/libregraph/lico v0.67.0
|
||||
github.com/libregraph/lico v0.66.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/mna/pigeon v1.3.0
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/nats-io/nats-server/v2 v2.14.5
|
||||
github.com/nats-io/nats.go v1.53.1
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/nats-io/nats-server/v2 v2.12.1
|
||||
github.com/nats-io/nats.go v1.47.0
|
||||
github.com/oklog/run v1.2.0
|
||||
github.com/olekukonko/tablewriter v1.1.1
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/ginkgo/v2 v2.32.1
|
||||
github.com/onsi/gomega v1.42.1
|
||||
github.com/open-policy-agent/opa v1.19.1
|
||||
github.com/onsi/ginkgo/v2 v2.27.2
|
||||
github.com/onsi/gomega v1.38.2
|
||||
github.com/open-policy-agent/opa v1.10.1
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260902170011-45af3945a067
|
||||
github.com/opencloud-eu/reva/v2 v2.49.1-0.20260903122659-26f34ec05774
|
||||
github.com/opensearch-project/opensearch-go/v4 v4.7.3
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
|
||||
github.com/opencloud-eu/reva/v2 v2.39.3-0.20251121093521-c51ed14c8397
|
||||
github.com/opensearch-project/opensearch-go/v4 v4.5.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.24.1
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/r3labs/sse/v2 v2.10.0
|
||||
github.com/riandyrn/otelchi v0.12.3
|
||||
github.com/rogpeppe/go-internal v1.16.0
|
||||
github.com/riandyrn/otelchi v0.12.2
|
||||
github.com/rogpeppe/go-internal v1.14.1
|
||||
github.com/rs/cors v1.11.1
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/shamaton/msgpack/v2 v2.4.1
|
||||
github.com/sirupsen/logrus v1.10.1
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.12.1
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/test-go/testify v1.1.4
|
||||
github.com/testcontainers/testcontainers-go v0.44.0
|
||||
github.com/testcontainers/testcontainers-go/modules/opensearch v0.44.0
|
||||
github.com/testcontainers/testcontainers-go v0.39.0
|
||||
github.com/testcontainers/testcontainers-go/modules/opensearch v0.39.0
|
||||
github.com/theckman/yacspin v0.13.12
|
||||
github.com/thejerf/suture/v4 v4.0.6
|
||||
github.com/tidwall/gjson v1.19.0
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/tidwall/sjson v1.2.5
|
||||
github.com/tus/tusd/v2 v2.10.0
|
||||
github.com/tus/tusd/v2 v2.8.0
|
||||
github.com/unrolled/secure v1.16.0
|
||||
github.com/urfave/cli/v2 v2.27.7
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||
go-micro.dev/v4 v4.11.0
|
||||
go.etcd.io/bbolt v1.5.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0
|
||||
go.opentelemetry.io/contrib/zpages v0.71.0
|
||||
go.opentelemetry.io/otel v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0
|
||||
go.opentelemetry.io/otel/sdk v1.46.0
|
||||
go.opentelemetry.io/otel/trace v1.46.0
|
||||
golang.org/x/crypto v0.55.0
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
||||
golang.org/x/image v0.45.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.22.0
|
||||
golang.org/x/term v0.45.0
|
||||
golang.org/x/text v0.41.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d
|
||||
google.golang.org/grpc v1.83.2
|
||||
google.golang.org/protobuf v1.36.12
|
||||
go.etcd.io/bbolt v1.4.3
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
|
||||
go.opentelemetry.io/contrib/zpages v0.63.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
golang.org/x/crypto v0.44.0
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
|
||||
golang.org/x/image v0.32.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/oauth2 v0.33.0
|
||||
golang.org/x/sync v0.18.0
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/text v0.31.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
@@ -121,131 +120,135 @@ require (
|
||||
|
||||
require (
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.1 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.5 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
github.com/agnivade/levenshtein v1.2.1 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/alexedwards/argon2id v1.0.0 // indirect
|
||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 // indirect
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.2 // indirect
|
||||
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.2 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.4.1 // indirect
|
||||
github.com/blevesearch/geo v0.2.6 // indirect
|
||||
github.com/blevesearch/go-faiss v1.1.5 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
|
||||
github.com/blevesearch/geo v0.2.4 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.26 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.2.0 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.10 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.2.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||
github.com/blevesearch/zapx/v17 v17.2.3 // indirect
|
||||
github.com/blevesearch/vellum v1.1.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.7 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/ceph/go-ceph v0.40.0 // indirect
|
||||
github.com/ceph/go-ceph v0.36.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.3.1 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.2 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
|
||||
github.com/cornelk/hashmap v1.0.8 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/crewjam/httperr v0.2.0 // indirect
|
||||
github.com/crewjam/saml v0.4.14 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/deckarep/golang-set v1.8.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/docker/go-connections v0.7.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/docker/docker v28.3.3+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.10.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emvi/iso-639-1 v1.1.1 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.5.0 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gdexlab/go-render v1.0.1 // indirect
|
||||
github.com/go-acme/lego/v4 v4.4.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.19.2 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.5 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.13.2 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-micro/plugins/v4/events/natsjs v1.2.2 // indirect
|
||||
github.com/go-micro/plugins/v4/store/nats-js v1.2.1 // indirect
|
||||
github.com/go-micro/plugins/v4/store/redis v1.2.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.7.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-test/deep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomodule/redigo v1.9.3 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect
|
||||
github.com/google/renameio/v2 v2.0.2 // indirect
|
||||
github.com/gookit/goutil v0.8.0 // indirect
|
||||
github.com/google/go-tpm v0.9.6 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/renameio/v2 v2.0.1 // indirect
|
||||
github.com/gookit/goutil v0.7.1 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/schema v1.4.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||
github.com/hashicorp/go-plugin v1.8.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/go-plugin v1.7.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
@@ -256,112 +259,110 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/juliangruber/go-intersect v1.1.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.19.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/kovidgoyal/go-parallel v1.1.1 // indirect
|
||||
github.com/kovidgoyal/go-shm v1.0.0 // indirect
|
||||
github.com/kovidgoyal/go-parallel v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/dsig v1.2.1 // indirect
|
||||
github.com/lestrrat-go/dsig v1.0.0 // indirect
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect
|
||||
github.com/lestrrat-go/jwx/v3 v3.1.1 // indirect
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.1 // indirect
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.11 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
||||
github.com/libregraph/oidc-go v1.1.0 // indirect
|
||||
github.com/longsleep/go-metrics v1.0.0 // indirect
|
||||
github.com/longsleep/rndm v1.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.49 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b // indirect
|
||||
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 // indirect
|
||||
github.com/miekg/dns v1.1.68 // indirect
|
||||
github.com/miekg/dns v1.1.57 // indirect
|
||||
github.com/mileusna/useragent v1.3.5 // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/highwayhash v1.0.4 // indirect
|
||||
github.com/minio/crc64nvme v1.1.0 // indirect
|
||||
github.com/minio/highwayhash v1.0.3 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.2.1 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.97 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/moby/api v1.55.0 // indirect
|
||||
github.com/moby/moby/client v0.5.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/sys/sequential v0.7.0 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.8.2 // indirect
|
||||
github.com/nats-io/nkeys v0.4.16 // indirect
|
||||
github.com/nats-io/jwt/v2 v2.8.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/oklog/run v1.2.0 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.6 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pablodz/inotifywaitgo v0.0.12 // indirect
|
||||
github.com/pablodz/inotifywaitgo v0.0.9 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/pquerna/cachecontrol v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.70.1 // indirect
|
||||
github.com/prometheus/procfs v0.21.1 // indirect
|
||||
github.com/prometheus/alertmanager v0.29.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/prometheus/statsd_exporter v0.22.8 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.6.0 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.5.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/samber/lo v1.53.0 // indirect
|
||||
github.com/samber/slog-common v0.21.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.2 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/segmentio/kafka-go v0.4.51 // indirect
|
||||
github.com/samber/lo v1.51.0 // indirect
|
||||
github.com/samber/slog-common v0.19.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/segmentio/kafka-go v0.4.49 // indirect
|
||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/sethvargo/go-diceware v0.6.0 // indirect
|
||||
github.com/sethvargo/go-password v0.4.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.6 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/sethvargo/go-diceware v0.5.0 // indirect
|
||||
github.com/sethvargo/go-password v0.3.1 // indirect
|
||||
github.com/shamaton/msgpack/v2 v2.4.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/studio-b12/gowebdav v0.9.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.4.0 // indirect
|
||||
github.com/tklauser/numcpus v0.12.0 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||
github.com/trustelem/zxcvbn v1.0.1 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.7 // indirect
|
||||
github.com/valyala/fastjson v1.6.10 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.36 // indirect
|
||||
github.com/valyala/fastjson v1.6.4 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.30 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map v1.0.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
@@ -370,27 +371,25 @@ require (
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.13 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.13 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.13 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 // 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.45.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.46.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/mod v0.38.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.3 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
@@ -398,7 +397,7 @@ require (
|
||||
|
||||
replace github.com/studio-b12/gowebdav => github.com/kobergj/gowebdav v0.0.0-20250102091030-aa65266db202
|
||||
|
||||
replace github.com/unrolled/secure => github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4
|
||||
replace github.com/unrolled/secure => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c
|
||||
|
||||
replace go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.20241115112658-b5d4de5ed9b3
|
||||
|
||||
@@ -407,3 +406,6 @@ replace go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.202411151126
|
||||
exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
|
||||
replace github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a
|
||||
|
||||
// to get the logger injection (https://github.com/pablodz/inotifywaitgo/pull/11)
|
||||
replace github.com/pablodz/inotifywaitgo v0.0.9 => github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9
|
||||
@@ -1,44 +0,0 @@
|
||||
package eventstest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
rev "github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
microevents "go-micro.dev/v4/events"
|
||||
)
|
||||
|
||||
func NewTestBus() TestBus {
|
||||
return TestBus(make(chan rev.Event))
|
||||
}
|
||||
|
||||
type TestBus chan rev.Event
|
||||
|
||||
func (tb TestBus) Consume(_ string, _ ...microevents.ConsumeOption) (<-chan microevents.Event, error) {
|
||||
ch := make(chan microevents.Event)
|
||||
go func() {
|
||||
for ev := range tb {
|
||||
b, _ := json.Marshal(ev.Event)
|
||||
ch <- microevents.Event{
|
||||
Payload: b,
|
||||
Metadata: map[string]string{
|
||||
rev.MetadatakeyEventID: ev.ID,
|
||||
rev.MetadatakeyEventType: ev.Type,
|
||||
},
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (tb TestBus) Publish(e any) string {
|
||||
ev := rev.Event{
|
||||
ID: uuid.New().String(),
|
||||
Type: reflect.TypeOf(e).String(),
|
||||
Event: e,
|
||||
}
|
||||
tb <- ev
|
||||
return ev.ID
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package metricstest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// copied and adapted from Prometheus testutil.ToFloat64(), since we don't import that package
|
||||
func collect(c prometheus.Collector) []prometheus.Metric {
|
||||
result := []prometheus.Metric{}
|
||||
ch := make(chan prometheus.Metric)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for m := range ch {
|
||||
result = append(result, m)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
c.Collect(ch)
|
||||
close(ch)
|
||||
<-done
|
||||
return result
|
||||
}
|
||||
|
||||
func RequireIsNotSet(t require.TestingT, c prometheus.Collector, msgAndArgs ...any) {
|
||||
if h, ok := t.(interface{ Helper() }); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if !IsNotSet(t, c, msgAndArgs) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func IsNotSet(t assert.TestingT, c prometheus.Collector, msgAndArgs ...any) bool {
|
||||
if h, ok := t.(interface{ Helper() }); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
m := collect(c)
|
||||
if len(m) > 0 {
|
||||
return assert.Fail(t, "Metric exists while expected to not exist", msgAndArgs)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func RequireEqual(t require.TestingT, expected float64, c prometheus.Collector, msgAndArgs ...any) {
|
||||
if h, ok := t.(interface{ Helper() }); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if !Equal(t, expected, c, msgAndArgs) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// copied and adapted from Prometheus testutil.ToFloat64(), since we don't import that package
|
||||
func Equal(t assert.TestingT, expected float64, c prometheus.Collector, msgAndArgs ...any) bool {
|
||||
if h, ok := t.(interface{ Helper() }); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
m := collect(c)
|
||||
if !assert.Len(t, m, 1, msgAndArgs...) {
|
||||
return false
|
||||
}
|
||||
pb := &dto.Metric{}
|
||||
err := m[0].Write(pb)
|
||||
if !assert.NoError(t, err, msgAndArgs...) {
|
||||
return false
|
||||
}
|
||||
if pb.Gauge != nil {
|
||||
return assert.Equal(t, expected, pb.Gauge.GetValue(), msgAndArgs...)
|
||||
} else if pb.Counter != nil {
|
||||
return assert.Equal(t, expected, pb.Counter.GetValue(), msgAndArgs...)
|
||||
} else if pb.Untyped != nil {
|
||||
return assert.Equal(t, expected, pb.Untyped.GetValue(), msgAndArgs...)
|
||||
} else {
|
||||
return assert.Fail(t, fmt.Sprintf("collected a non-gauge/counter/untyped metric: %s", pb), msgAndArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
func RequireEqualWithLabels(t require.TestingT, expectedValue float64, expectedLabels map[string]string, c prometheus.Collector, msgAndArgs ...any) {
|
||||
if h, ok := t.(interface{ Helper() }); ok {
|
||||
h.Helper()
|
||||
}
|
||||
if !EqualWithLabels(t, expectedValue, expectedLabels, c, msgAndArgs) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func EqualWithLabels(t assert.TestingT, expectedValue float64, expectedLabels map[string]string, c prometheus.Collector, msgAndArgs ...any) bool {
|
||||
if h, ok := t.(interface{ Helper() }); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
m := collect(c)
|
||||
if !assert.Len(t, m, 1, "collected %d metrics instead of exactly 1", len(m)) {
|
||||
return false
|
||||
}
|
||||
pb := &dto.Metric{}
|
||||
err := m[0].Write(pb)
|
||||
if !assert.NoError(t, err) {
|
||||
return false
|
||||
}
|
||||
if pb.Gauge != nil {
|
||||
if !assert.Equal(t, expectedValue, pb.Gauge.GetValue()) {
|
||||
return false
|
||||
}
|
||||
} else if pb.Counter != nil {
|
||||
if !assert.Equal(t, expectedValue, pb.Counter.GetValue()) {
|
||||
return false
|
||||
}
|
||||
} else if pb.Untyped != nil {
|
||||
if !assert.Equal(t, expectedValue, pb.Untyped.GetValue()) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return assert.Fail(t, "collected a non-gauge/counter/untyped metric: %s", pb)
|
||||
}
|
||||
|
||||
if !assert.NotNil(t, pb.Label) {
|
||||
return false
|
||||
}
|
||||
actualLabels := map[string]string{}
|
||||
for _, label := range pb.Label {
|
||||
if !assert.NotNil(t, label) {
|
||||
return false
|
||||
}
|
||||
if !assert.NotNil(t, label.Name) {
|
||||
return false
|
||||
}
|
||||
if !assert.NotNil(t, label.Value) {
|
||||
return false
|
||||
}
|
||||
actualLabels[*label.Name] = *label.Value
|
||||
}
|
||||
return assert.Equal(t, expectedLabels, actualLabels, msgAndArgs)
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
[env]
|
||||
GO_VERSION = "{{ exec(command='./.mise-go-version.sh') }}"
|
||||
|
||||
[tools]
|
||||
go = "{{ env.GO_VERSION }}"
|
||||
node = "24"
|
||||
pnpm = "11.1.3"
|
||||
"go:github.com/go-delve/delve/cmd/dlv" = "1.27.1"
|
||||
"aqua:nats-io/natscli" = "0.4.0"
|
||||
k6 = "2.2.0"
|
||||
ginkgo = "latest"
|
||||
|
||||
[tasks.build]
|
||||
description = "build"
|
||||
run = "make -C opencloud build"
|
||||
|
||||
[tasks."build:debug"]
|
||||
description = "build with debug symbols"
|
||||
run = "make -C opencloud build-debug"
|
||||
|
||||
[tasks."docker:build"]
|
||||
description = "docker image opencloudeu/opencloud:dev"
|
||||
run = "make -C opencloud dev-docker"
|
||||
|
||||
[tasks."docker:build:multiarch"]
|
||||
description = "docker image for amd64 + arm64"
|
||||
depends = ["gen"]
|
||||
run = "make -C opencloud dev-docker-multiarch"
|
||||
|
||||
[tasks."docker:build:debug"]
|
||||
description = "docker image with delve"
|
||||
run = "make -C opencloud debug-docker"
|
||||
|
||||
[tasks.serve]
|
||||
description = "start the server"
|
||||
depends = ["build"]
|
||||
run = "opencloud/bin/opencloud server"
|
||||
|
||||
[tasks."serve:init"]
|
||||
description = "create the local config"
|
||||
depends = ["build"]
|
||||
run = "opencloud/bin/opencloud init"
|
||||
|
||||
[tasks."serve:debug"]
|
||||
description = "start the server under delve"
|
||||
depends = ["build:debug"]
|
||||
run = "dlv exec opencloud/bin/opencloud-debug -- server"
|
||||
|
||||
[tasks.test]
|
||||
description = "go test"
|
||||
run = "go test -tags disable_crypt ./..."
|
||||
|
||||
[tasks."test:changed"]
|
||||
description = "go test, changed packages"
|
||||
run = """
|
||||
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
|
||||
files=$(git diff --name-only --diff-filter=ACM $(git merge-base "$base" HEAD) -- '*.go' | grep -v vendor/ || true)
|
||||
[ -n "$files" ] || exit 0
|
||||
go test -tags disable_crypt $(echo "$files" | xargs -n1 dirname | sort -u | sed 's|^|./|')
|
||||
"""
|
||||
|
||||
[tasks."test:race"]
|
||||
description = "go test + race"
|
||||
run = "go test -race -tags disable_crypt ./..."
|
||||
|
||||
[tasks."test:coverage"]
|
||||
description = "go test + coverage"
|
||||
run = ["make test", "make go-coverage"]
|
||||
|
||||
[tasks.check]
|
||||
description = "all checks"
|
||||
depends = ["check:fmt", "check:lint", "check:vendor", "check:env-vars", "test"]
|
||||
|
||||
[tasks."check:fmt"]
|
||||
description = "gofmt"
|
||||
run = """
|
||||
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
|
||||
files=$(git diff --name-only --diff-filter=ACM $(git merge-base "$base" HEAD) -- '*.go' | grep -v vendor/ || true)
|
||||
[ -n "$files" ] || exit 0
|
||||
out=$(gofmt -s -l $files)
|
||||
[ -z "$out" ] || { echo "$out"; exit 1; }
|
||||
"""
|
||||
|
||||
[tasks."check:lint"]
|
||||
description = "golangci-lint"
|
||||
run = """
|
||||
set -e
|
||||
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
|
||||
gobin=$(go env GOBIN)
|
||||
[ -n "$gobin" ] || gobin="$(go env GOPATH)/bin"
|
||||
bin="$gobin/golangci-lint-$(awk '/golangci-lint v/ {print $3}' .bingo/golangci-lint.mod)"
|
||||
[ -x "$bin" ] || make -s golangci-lint >/dev/null
|
||||
exec "$bin" run --modules-download-mode vendor --timeout 15m0s --new-from-merge-base "$base"
|
||||
"""
|
||||
|
||||
[tasks."check:vendor"]
|
||||
description = "vendor matches go.mod"
|
||||
run = "GOWORK=off go list -mod=vendor ./... >/dev/null"
|
||||
|
||||
[tasks."check:env-vars"]
|
||||
description = "env var annotations"
|
||||
run = "make check-env-var-annotations"
|
||||
|
||||
[tasks.fix]
|
||||
description = "gofmt + lint fixes"
|
||||
run = [{ task = "fix:fmt" }, { task = "fix:lint" }]
|
||||
|
||||
[tasks."fix:fmt"]
|
||||
description = "gofmt -w"
|
||||
run = """
|
||||
base=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo origin/main)
|
||||
files=$(git diff --name-only --diff-filter=ACM $(git merge-base "$base" HEAD) -- '*.go' | grep -v vendor/ || true)
|
||||
[ -z "$files" ] || gofmt -s -w $files
|
||||
"""
|
||||
|
||||
[tasks."fix:lint"]
|
||||
description = "golangci-lint --fix"
|
||||
run = "make golangci-lint-fix"
|
||||
|
||||
[tasks."pre:commit"]
|
||||
description = "before commit"
|
||||
run = [{ task = "fix:fmt" }, { task = "check:lint" }, { task = "test:changed" }]
|
||||
|
||||
[tasks."pre:push"]
|
||||
description = "before push"
|
||||
run = [{ task = "tidy" }, { task = "fix:fmt" }, { task = "check" }]
|
||||
|
||||
[tasks.gen]
|
||||
description = "assets & mocks"
|
||||
run = "make generate"
|
||||
|
||||
[tasks."gen:protobuf"]
|
||||
description = "protobuf"
|
||||
run = "make protobuf"
|
||||
|
||||
[tasks.tidy]
|
||||
description = "tidy + vendor"
|
||||
run = ["GOWORK=off go mod tidy", "GOWORK=off go mod vendor"]
|
||||
+2
-5
@@ -17,10 +17,7 @@ include ../.make/docs.mk
|
||||
|
||||
.PHONY: dev-docker
|
||||
dev-docker:
|
||||
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev ..
|
||||
|
||||
dev-docker-with-workspace:
|
||||
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev --build-arg SRCDIR=opencloud ../..
|
||||
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev ../..
|
||||
|
||||
.PHONY: dev-docker-multiarch
|
||||
dev-docker-multiarch:
|
||||
@@ -31,7 +28,7 @@ dev-docker-multiarch:
|
||||
docker buildx rm opencloudbuilder || true
|
||||
docker buildx create --platform linux/arm64,linux/amd64 --name opencloudbuilder
|
||||
docker buildx use opencloudbuilder
|
||||
docker buildx build --platform linux/arm64,linux/amd64 --output type=docker --file docker/Dockerfile.multiarch --tag opencloudeu/opencloud:dev-multiarch ..
|
||||
docker buildx build --platform linux/arm64,linux/amd64 --output type=docker --file docker/Dockerfile.multiarch --tag opencloudeu/opencloud:dev-multiarch ../..
|
||||
docker buildx rm opencloudbuilder
|
||||
|
||||
.PHONY: debug-docker
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
FROM quay.io/opencloudeu/golang-ci:1.25 AS build
|
||||
FROM golang:alpine3.21 AS build
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG VERSION
|
||||
ARG STRING
|
||||
ARG EDITION="dev"
|
||||
ARG SRCDIR
|
||||
|
||||
RUN apk add bash make git curl gcc musl-dev libc-dev binutils-gold inotify-tools vips-dev
|
||||
|
||||
WORKDIR /build
|
||||
RUN --mount=type=bind,target=/build,rw \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
GOOS="${TARGETOS:-linux}" GOARCH="${TARGETARCH:-amd64}" ; \
|
||||
make -C ${SRCDIR:-.}/opencloud release-linux-docker-${TARGETARCH} \
|
||||
ENABLE_VIPS=true DIST=/dist \
|
||||
VERSION="${VERSION}" EDITION="${EDITION}" \
|
||||
${STRING:+STRING="${STRING}"}
|
||||
make -C opencloud/opencloud release-linux-docker-${TARGETARCH} ENABLE_VIPS=true DIST=/dist
|
||||
|
||||
FROM alpine:3.24
|
||||
FROM alpine:3.21
|
||||
ARG VERSION
|
||||
ARG REVISION
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN apk add --no-cache attr bash ca-certificates curl imagemagick \
|
||||
inotify-tools libc6-compat mailcap tree vips \
|
||||
vips-magick patch && \
|
||||
echo 'hosts: files dns' >| /etc/nsswitch.conf
|
||||
RUN apk add --no-cache attr bash ca-certificates curl inotify-tools libc6-compat mailcap tree vips patch && \
|
||||
echo 'hosts: files dns' >| /etc/nsswitch.conf
|
||||
|
||||
LABEL maintainer="openCloud GmbH <devops@opencloud.eu>" \
|
||||
org.opencontainers.image.title="OpenCloud" \
|
||||
|
||||
@@ -75,7 +75,7 @@ func CheckProviderConsistency(storagepath string, lbs ListBlobstore, fail bool)
|
||||
}
|
||||
|
||||
// GatherData gathers and evaluates data produced by the DataProvider
|
||||
func (c *Consistency) GatherData(events <-chan any) {
|
||||
func (c *Consistency) GatherData(events <-chan interface{}) {
|
||||
for ev := range events {
|
||||
switch d := ev.(type) {
|
||||
case NodeData:
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
func TestGatherData(t *testing.T) {
|
||||
testcases := []struct {
|
||||
Name string
|
||||
Events []any
|
||||
Events []interface{}
|
||||
Expected *backup.Consistency
|
||||
}{
|
||||
{
|
||||
Name: "no symlinks - no blobs",
|
||||
Events: []any{
|
||||
Events: []interface{}{
|
||||
nodeData("nodepath", "blobpath", true),
|
||||
},
|
||||
Expected: consistency(func(c *backup.Consistency) {
|
||||
@@ -25,7 +25,7 @@ func TestGatherData(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "symlink not required - no blobs",
|
||||
Events: []any{
|
||||
Events: []interface{}{
|
||||
nodeData("nodepath", "blobpath", false),
|
||||
},
|
||||
Expected: consistency(func(c *backup.Consistency) {
|
||||
@@ -34,7 +34,7 @@ func TestGatherData(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "no inconsistencies",
|
||||
Events: []any{
|
||||
Events: []interface{}{
|
||||
nodeData("nodepath", "blobpath", true),
|
||||
linkData("linkpath", "nodepath"),
|
||||
blobData("blobpath"),
|
||||
@@ -44,7 +44,7 @@ func TestGatherData(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "orphaned blob",
|
||||
Events: []any{
|
||||
Events: []interface{}{
|
||||
nodeData("nodepath", "blobpath", true),
|
||||
linkData("linkpath", "nodepath"),
|
||||
blobData("blobpath"),
|
||||
@@ -56,7 +56,7 @@ func TestGatherData(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "missing node",
|
||||
Events: []any{
|
||||
Events: []interface{}{
|
||||
linkData("linkpath", "nodepath"),
|
||||
blobData("blobpath"),
|
||||
},
|
||||
@@ -67,7 +67,7 @@ func TestGatherData(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "corrupt metadata",
|
||||
Events: []any{
|
||||
Events: []interface{}{
|
||||
nodeData("nodepath", "blobpath", true, backup.InconsistencyMetadataMissing),
|
||||
linkData("linkpath", "nodepath"),
|
||||
blobData("blobpath"),
|
||||
@@ -78,7 +78,7 @@ func TestGatherData(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "corrupt metadata, no blob",
|
||||
Events: []any{
|
||||
Events: []interface{}{
|
||||
nodeData("nodepath", "blobpath", true, backup.InconsistencyMetadataMissing),
|
||||
linkData("linkpath", "nodepath"),
|
||||
},
|
||||
@@ -90,7 +90,7 @@ func TestGatherData(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
events := make(chan any)
|
||||
events := make(chan interface{})
|
||||
|
||||
go func() {
|
||||
for _, ev := range tc.Events {
|
||||
|
||||
@@ -21,7 +21,7 @@ type ListBlobstore interface {
|
||||
|
||||
// DataProvider provides data for the consistency check
|
||||
type DataProvider struct {
|
||||
Events chan any
|
||||
Events chan interface{}
|
||||
|
||||
fsys fs.FS
|
||||
discpath string
|
||||
@@ -51,7 +51,7 @@ type BlobData struct {
|
||||
// NewProvider creates a new DataProvider object
|
||||
func NewProvider(fsys fs.FS, discpath string, lbs ListBlobstore) *DataProvider {
|
||||
return &DataProvider{
|
||||
Events: make(chan any),
|
||||
Events: make(chan interface{}),
|
||||
|
||||
fsys: fsys,
|
||||
discpath: discpath,
|
||||
@@ -75,20 +75,25 @@ func (dp *DataProvider) ProduceData() error {
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// crawl spaces
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for _, d := range dirs {
|
||||
dp.evaluateNodeDir(d)
|
||||
}
|
||||
})
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// crawl trash
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
dp.evaluateTrashDir()
|
||||
})
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// crawl blobstore
|
||||
if !dp.skipBlobs {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
bs, err := dp.lbs.List()
|
||||
if err != nil {
|
||||
fmt.Println("error listing blobs", err)
|
||||
@@ -97,7 +102,8 @@ func (dp *DataProvider) ProduceData() error {
|
||||
for _, bn := range bs {
|
||||
dp.Events <- BlobData{BlobPath: dp.lbs.Path(bn)}
|
||||
}
|
||||
})
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// wait for all crawlers to finish
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/backup"
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
@@ -13,34 +11,62 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/parser"
|
||||
decomposedbs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore"
|
||||
decomposeds3bs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// BackupCommand is the entrypoint for the backup command
|
||||
func BackupCommand(cfg *config.Config) *cobra.Command {
|
||||
bckCmd := &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "OpenCloud backup functionality",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
func BackupCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "backup",
|
||||
Usage: "OpenCloud backup functionality",
|
||||
Subcommands: []*cli.Command{
|
||||
ConsistencyCommand(cfg),
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg, true))
|
||||
},
|
||||
Action: func(_ *cli.Context) error {
|
||||
fmt.Println("Read the docs")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
bckCmd.AddCommand(ConsistencyCommand(cfg))
|
||||
return bckCmd
|
||||
}
|
||||
|
||||
// ConsistencyCommand is the entrypoint for the consistency Command
|
||||
func ConsistencyCommand(cfg *config.Config) *cobra.Command {
|
||||
consCmd := &cobra.Command{
|
||||
Use: "consistency",
|
||||
Short: "check backup consistency",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
func ConsistencyCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "consistency",
|
||||
Usage: "check backup consistency",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "basepath",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "blobstore",
|
||||
Aliases: []string{"b"},
|
||||
Usage: "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed",
|
||||
Value: "decomposed",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "fail",
|
||||
Usage: "exit with non-zero status if consistency check fails",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
basePath := c.String("basepath")
|
||||
if basePath == "" {
|
||||
fmt.Println("basepath is required")
|
||||
return cli.ShowCommandHelp(c, "consistency")
|
||||
}
|
||||
|
||||
var (
|
||||
bs backup.ListBlobstore
|
||||
err error
|
||||
)
|
||||
basePath, _ := cmd.Flags().GetString("basepath")
|
||||
blobstoreFlag, _ := cmd.Flags().GetString("blobstore")
|
||||
switch blobstoreFlag {
|
||||
switch c.String("blobstore") {
|
||||
case "decomposeds3":
|
||||
bs, err = decomposeds3bs.New(
|
||||
cfg.StorageUsers.Drivers.DecomposedS3.Endpoint,
|
||||
@@ -61,8 +87,7 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
fail, _ := cmd.Flags().GetBool("fail")
|
||||
if err := backup.CheckProviderConsistency(basePath, bs, fail); err != nil {
|
||||
if err := backup.CheckProviderConsistency(basePath, bs, c.Bool("fail")); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
@@ -70,11 +95,6 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
consCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)")
|
||||
_ = consCmd.MarkFlagRequired("basepath")
|
||||
consCmd.Flags().StringP("blobstore", "b", "decomposed", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed")
|
||||
consCmd.Flags().Bool("fail", false, "exit with non-zero status if consistency check fails")
|
||||
return consCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
+117
-189
@@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
@@ -11,10 +10,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
@@ -24,118 +21,105 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
"github.com/pkg/xattr"
|
||||
"github.com/rogpeppe/go-internal/lockedfile"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// BenchmarkCommand is the entrypoint for the benchmark commands.
|
||||
func BenchmarkCommand(cfg *config.Config) *cobra.Command {
|
||||
benchCmd := &cobra.Command{
|
||||
Use: "benchmark",
|
||||
Short: "cli tools to test low and high level performance",
|
||||
func BenchmarkCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "benchmark",
|
||||
Usage: "cli tools to test low and high level performance",
|
||||
Category: "benchmark",
|
||||
Subcommands: []*cli.Command{BenchmarkClientCommand(cfg), BenchmarkSyscallsCommand(cfg)},
|
||||
}
|
||||
benchCmd.AddCommand(BenchmarkClientCommand(cfg), BenchmarkSyscallsCommand(cfg))
|
||||
return benchCmd
|
||||
}
|
||||
|
||||
// BenchmarkClientCommand is the entrypoint for the benchmark client command.
|
||||
func BenchmarkClientCommand(cfg *config.Config) *cobra.Command {
|
||||
benchClientCmd := &cobra.Command{
|
||||
Use: "client",
|
||||
Short: "Start a client that continuously makes web requests and prints stats. The options mimic curl, but we default to PROPFIND requests.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
jobs, err := cmd.Flags().GetInt("jobs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
insecure, _ := cmd.Flags().GetBool("insecure")
|
||||
func BenchmarkClientCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "client",
|
||||
|
||||
Usage: "Start a client that continuously makes web requests and prints stats. The options mimic curl, but URL must be at the end.",
|
||||
Flags: []cli.Flag{
|
||||
|
||||
// TODO with v3 'flag.Persistent: true' can be set to make the order of flags no longer relevant \o/
|
||||
// flags mimicing curl
|
||||
&cli.StringFlag{
|
||||
Name: "request",
|
||||
Aliases: []string{"X"},
|
||||
Value: "PROPFIND",
|
||||
Usage: "Specifies a custom request method to use when communicating with the HTTP server.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "user",
|
||||
Aliases: []string{"u"},
|
||||
Value: "admin:admin",
|
||||
Usage: "Specify the user name and password to use for server authentication.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "insecure",
|
||||
Aliases: []string{"k"},
|
||||
Usage: "Skip the TLS verification step and proceed without checking.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "data",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Sends the specified data in a request to the HTTP server.",
|
||||
// TODE support multiple data flags, support data-binary, data-raw
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "header",
|
||||
Aliases: []string{"H"},
|
||||
Usage: "Extra header to include in information sent.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "rate",
|
||||
Usage: `Specify the maximum transfer frequency you allow a client to use - in number of transfer starts per time unit (sometimes called request rate).
|
||||
The request rate is provided as "N/U" where N is an integer number and U is a time unit. Supported units are 's' (second), 'm' (minute), 'h' (hour) and 'd' /(day, as in a 24 hour unit). The default time unit, if no "/U" is provided, is number of transfers per hour.`,
|
||||
},
|
||||
/*
|
||||
&cli.StringFlag{
|
||||
Name: "oauth2-bearer",
|
||||
Usage: "Specify the Bearer Token for OAUTH 2.0 server authentication.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "user-agent",
|
||||
Aliases: []string{"A"},
|
||||
Value: "admin:admin",
|
||||
Usage: "Specify the User-Agent string to send to the HTTP server.",
|
||||
},
|
||||
*/
|
||||
// other flags
|
||||
&cli.StringFlag{
|
||||
Name: "bearer-token-command",
|
||||
Usage: "Command to execute for a bearer token, e.g. 'oidc-token opencloud'. When set, disables basic auth.",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "every",
|
||||
Usage: "Aggregate stats every time this amount of seconds has passed.",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "jobs",
|
||||
Aliases: []string{"j"},
|
||||
Value: 1,
|
||||
Usage: "Number of parallel clients to start.",
|
||||
},
|
||||
},
|
||||
Category: "benchmark",
|
||||
Action: func(c *cli.Context) error {
|
||||
opt := clientOptions{
|
||||
url: args[0],
|
||||
insecure: insecure,
|
||||
jobs: jobs,
|
||||
request: c.String("request"),
|
||||
url: c.Args().First(),
|
||||
insecure: c.Bool("insecure"),
|
||||
jobs: c.Int("jobs"),
|
||||
headers: make(map[string]string),
|
||||
data: []byte(c.String("data")),
|
||||
}
|
||||
|
||||
if d, _ := cmd.Flags().GetString("data-raw"); d != "" {
|
||||
opt.request = "POST"
|
||||
opt.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
opt.data = []byte(d)
|
||||
}
|
||||
|
||||
if d, _ := cmd.Flags().GetString("data"); d != "" {
|
||||
opt.request = "POST"
|
||||
opt.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
if strings.HasPrefix(d, "@") {
|
||||
filePath := strings.TrimPrefix(d, "@")
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
// read from file or stdin and trim trailing newlines
|
||||
if filePath == "-" {
|
||||
data, err = os.ReadFile("/dev/stdin")
|
||||
} else {
|
||||
data, err = os.ReadFile(filePath)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(errors.New("could not read data from file '" + filePath + "': " + err.Error()))
|
||||
}
|
||||
|
||||
// clean byte array similar to curl's --data parameter
|
||||
// It removes leading/trailing whitespace and converts line breaks to spaces
|
||||
|
||||
// Trim leading and trailing whitespace
|
||||
data = bytes.TrimSpace(data)
|
||||
|
||||
// Replace newlines and carriage returns with spaces
|
||||
data = bytes.ReplaceAll(data, []byte("\r\n"), []byte(" "))
|
||||
data = bytes.ReplaceAll(data, []byte("\n"), []byte(" "))
|
||||
data = bytes.ReplaceAll(data, []byte("\r"), []byte(" "))
|
||||
|
||||
// Replace multiple spaces with single space
|
||||
for bytes.Contains(data, []byte(" ")) {
|
||||
data = bytes.ReplaceAll(data, []byte(" "), []byte(" "))
|
||||
}
|
||||
|
||||
opt.data = data
|
||||
} else {
|
||||
opt.data = []byte(d)
|
||||
}
|
||||
}
|
||||
|
||||
if d, _ := cmd.Flags().GetString("data-binary"); d != "" {
|
||||
opt.request = "POST"
|
||||
opt.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
if strings.HasPrefix(d, "@") {
|
||||
filePath := strings.TrimPrefix(d, "@")
|
||||
var data []byte
|
||||
var err error
|
||||
if filePath == "-" {
|
||||
data, err = os.ReadFile("/dev/stdin")
|
||||
} else {
|
||||
data, err = os.ReadFile(filePath)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(errors.New("could not read data from file '" + filePath + "': " + err.Error()))
|
||||
}
|
||||
opt.data = data
|
||||
} else {
|
||||
opt.data = []byte(d)
|
||||
}
|
||||
}
|
||||
|
||||
// override method if specified
|
||||
if request, _ := cmd.Flags().GetString("request"); request != "" {
|
||||
opt.request = request
|
||||
}
|
||||
|
||||
if opt.url == "" {
|
||||
log.Fatal(errors.New("no URL specified"))
|
||||
}
|
||||
|
||||
headersSlice, err := cmd.Flags().GetStringSlice("header")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, h := range headersSlice {
|
||||
for _, h := range c.StringSlice("headers") {
|
||||
parts := strings.SplitN(h, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
log.Fatal(errors.New("invalid header '" + h + "'"))
|
||||
@@ -143,7 +127,7 @@ func BenchmarkClientCommand(cfg *config.Config) *cobra.Command {
|
||||
opt.headers[parts[0]] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
rate, _ := cmd.Flags().GetString("rate")
|
||||
rate := c.String("rate")
|
||||
if rate != "" {
|
||||
parts := strings.SplitN(rate, "/", 2)
|
||||
num, err := strconv.Atoi(parts[0])
|
||||
@@ -166,12 +150,12 @@ func BenchmarkClientCommand(cfg *config.Config) *cobra.Command {
|
||||
opt.rateDelay = unit / time.Duration(num)
|
||||
}
|
||||
|
||||
user, _ := cmd.Flags().GetString("user")
|
||||
user := c.String("user")
|
||||
opt.auth = func() string {
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(user))
|
||||
}
|
||||
|
||||
btc, _ := cmd.Flags().GetString("bearer-token-command")
|
||||
btc := c.String("bearer-token-command")
|
||||
if btc != "" {
|
||||
parts := strings.SplitN(btc, " ", 2)
|
||||
var cmd *exec.Cmd
|
||||
@@ -189,47 +173,16 @@ func BenchmarkClientCommand(cfg *config.Config) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
every, err := cmd.Flags().GetInt("every")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
every := c.Int("every")
|
||||
if every != 0 {
|
||||
opt.ticker = time.NewTicker(time.Second * time.Duration(every))
|
||||
defer opt.ticker.Stop()
|
||||
}
|
||||
|
||||
// Set up signal handling for Ctrl+C
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
defer cancel()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigChan
|
||||
fmt.Println("\nReceived interrupt signal, shutting down...")
|
||||
cancel()
|
||||
}()
|
||||
return client(ctx, opt)
|
||||
return client(opt)
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
// flags mimicing curl
|
||||
benchClientCmd.Flags().StringP("request", "X", "PROPFIND", "Specifies a custom request method to use when communicating with the HTTP server.")
|
||||
benchClientCmd.Flags().StringP("user", "u", "admin:admin", "Specify the user name and password to use for server authentication.")
|
||||
benchClientCmd.Flags().BoolP("insecure", "k", false, "Skip the TLS verification step and proceed without checking.")
|
||||
benchClientCmd.Flags().StringP("data", "d", "", "Sends the specified data in a POST request to the HTTP server, in the same way that a browser does when a user has filled in an HTML form and presses the submit button. If you start the data with the letter @, the rest should be a file name to read the data from, or - if you want to read the data from stdin. When -d, --data is told to read from a file like that, carriage returns and newlines are stripped out. If you do not want the @ character to have a special interpretation use --data-raw instead.")
|
||||
benchClientCmd.Flags().StringP("data-raw", "", "", "Sends the specified data in a request to the HTTP server.")
|
||||
benchClientCmd.Flags().StringP("data-binary", "", "", "This posts data exactly as specified with no extra processing whatsoever. If you start the data with the letter @, the rest should be a file name to read the data from, or - if you want to read the data from stdin.")
|
||||
benchClientCmd.Flags().StringSliceP("headers", "H", []string{}, "Extra header to include in information sent.")
|
||||
benchClientCmd.Flags().String("rate", "", "Specify the maximum transfer frequency you allow a client to use - in number of transfer starts per time unit (sometimes called request rate). The request rate is provided as \"N/U\" where N is an integer number and U is a time unit. Supported units are 's' (second), 'm' (minute), 'h' (hour) and 'd' /(day, as in a 24 hour unit). The default time unit, if no \"/U\" is provided, is number of transfers per hour.")
|
||||
|
||||
// other flags
|
||||
benchClientCmd.Flags().IntP("jobs", "j", 1, "Number of parallel clients to start. Defaults to 1.")
|
||||
benchClientCmd.Flags().Int("every", 0, "Aggregate stats every time this amount of seconds has passed.")
|
||||
benchClientCmd.Flags().String("bearer-token-command", "", "Command to execute for a bearer token, e.g. 'oidc-token opencloud'. When set, disables basic auth.")
|
||||
|
||||
return benchClientCmd
|
||||
}
|
||||
|
||||
type clientOptions struct {
|
||||
@@ -244,7 +197,8 @@ type clientOptions struct {
|
||||
jobs int
|
||||
}
|
||||
|
||||
func client(ctx context.Context, o clientOptions) error {
|
||||
func client(o clientOptions) error {
|
||||
|
||||
type stat struct {
|
||||
job int
|
||||
duration time.Duration
|
||||
@@ -263,13 +217,6 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
|
||||
cookies := map[string]*http.Cookie{}
|
||||
for {
|
||||
// Check if context is cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(o.request, o.url, bytes.NewReader(o.data))
|
||||
if err != nil {
|
||||
log.Printf("client %d: could not create request: %s\n", i, err)
|
||||
@@ -287,35 +234,20 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
res, err := client.Do(req)
|
||||
duration := -time.Until(start)
|
||||
if err != nil {
|
||||
// Check if error is due to context cancellation
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("client %d: could not create request: %s\n", i, err)
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
res.Body.Close()
|
||||
select {
|
||||
case stats <- stat{
|
||||
stats <- stat{
|
||||
job: i,
|
||||
duration: duration,
|
||||
status: res.StatusCode,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
for _, c := range res.Cookies() {
|
||||
cookies[c.Name] = c
|
||||
}
|
||||
}
|
||||
// Sleep with context awareness
|
||||
if o.rateDelay > duration {
|
||||
select {
|
||||
case <-time.After(o.rateDelay - duration):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(o.rateDelay - duration)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
@@ -324,14 +256,9 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
if o.ticker == nil {
|
||||
// no ticker, just write every request
|
||||
for {
|
||||
select {
|
||||
case stat := <-stats:
|
||||
numRequests++
|
||||
fmt.Printf("req %d took %v and returned status %d\n", numRequests, stat.duration, stat.status)
|
||||
case <-ctx.Done():
|
||||
fmt.Println("\nShutting down...")
|
||||
return nil
|
||||
}
|
||||
stat := <-stats
|
||||
numRequests++
|
||||
fmt.Printf("req %d took %v and returned status %d\n", numRequests, stat.duration, stat.status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,25 +274,31 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
numRequests = 0
|
||||
duration = 0
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if numRequests > 0 {
|
||||
fmt.Printf("\n%d req at %v/req\n", numRequests, duration/time.Duration(numRequests))
|
||||
}
|
||||
fmt.Println("Shutting down...")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// BenchmarkSyscallsCommand is the entrypoint for the benchmark syscalls command.
|
||||
func BenchmarkSyscallsCommand(cfg *config.Config) *cobra.Command {
|
||||
benchSysCallCmd := &cobra.Command{
|
||||
Use: "syscalls",
|
||||
Short: "test the performance of syscalls",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
func BenchmarkSyscallsCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "syscalls",
|
||||
Usage: "test the performance of syscalls",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Usage: "Path to test",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "iterations",
|
||||
Value: "100",
|
||||
Usage: "Number of iterations to execute",
|
||||
},
|
||||
},
|
||||
Category: "benchmark",
|
||||
Action: func(c *cli.Context) error {
|
||||
|
||||
path, _ := cmd.Flags().GetString("path")
|
||||
path := c.String("path")
|
||||
if path == "" {
|
||||
f, err := os.CreateTemp("", "opencloud-bench-temp-")
|
||||
if err != nil {
|
||||
@@ -376,16 +309,11 @@ func BenchmarkSyscallsCommand(cfg *config.Config) *cobra.Command {
|
||||
defer os.Remove(path)
|
||||
}
|
||||
|
||||
iterations, err := cmd.Flags().GetInt("iterations")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iterations := c.Int("iterations")
|
||||
|
||||
return benchmark(iterations, path)
|
||||
},
|
||||
}
|
||||
benchSysCallCmd.Flags().String("path", "", "Path to test")
|
||||
benchSysCallCmd.Flags().Int("iterations", 100, "Number of iterations to execute")
|
||||
return benchSysCallCmd
|
||||
}
|
||||
|
||||
func benchmark(iterations int, path string) error {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package command
|
||||
|
||||
const (
|
||||
CommandGroupServer = "Server"
|
||||
CommandGroupServices = "Service"
|
||||
CommandGroupStorage = "Storage"
|
||||
)
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
@@ -23,57 +25,69 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// DecomposedfsCommand is the entrypoint for the groups command.
|
||||
func DecomposedfsCommand(cfg *config.Config) *cobra.Command {
|
||||
decomposedCmd := &cobra.Command{
|
||||
Use: "decomposedfs",
|
||||
Short: `cli tools to inspect and manipulate a decomposedfs storage.`,
|
||||
GroupID: CommandGroupStorage,
|
||||
func DecomposedfsCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "decomposedfs",
|
||||
Usage: `cli tools to inspect and manipulate a decomposedfs storage.`,
|
||||
Category: "maintenance",
|
||||
Subcommands: []*cli.Command{
|
||||
metadataCmd(cfg),
|
||||
checkCmd(cfg),
|
||||
},
|
||||
}
|
||||
decomposedCmd.AddCommand(metadataCmd(cfg), checkCmd(cfg))
|
||||
return decomposedCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
register.AddCommand(DecomposedfsCommand)
|
||||
}
|
||||
|
||||
func checkCmd(_ *config.Config) *cobra.Command {
|
||||
cCmd := &cobra.Command{
|
||||
Use: "check-treesize",
|
||||
Short: `cli tool to check the treesize metadata of a Space`,
|
||||
RunE: check,
|
||||
func checkCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "check-treesize",
|
||||
Usage: `cli tool to check the treesize metadata of a Space`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "root",
|
||||
Aliases: []string{"r"},
|
||||
Required: true,
|
||||
Usage: "Path to the root directory of the decomposedfs",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "node",
|
||||
Required: true,
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Space ID of the Space to inspect",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "repair",
|
||||
Usage: "Try to repair nodes with incorrect treesize metadata. IMPORTANT: Only use this while OpenCloud is not running.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "force",
|
||||
Usage: "Do not prompt for confirmation when running in repair mode.",
|
||||
},
|
||||
},
|
||||
Action: check,
|
||||
}
|
||||
cCmd.Flags().StringP("root", "r", "", "Path to the root directory of the decomposedfs")
|
||||
_ = cCmd.MarkFlagRequired("root")
|
||||
cCmd.Flags().StringP("node", "n", "", "Space ID of the Space to inspect")
|
||||
_ = cCmd.MarkFlagRequired("node")
|
||||
cCmd.Flags().Bool("repair", false, "Try to repair nodes with incorrect treesize metadata. IMPORTANT: Only use this while OpenCloud is not running.")
|
||||
cCmd.Flags().Bool("force", false, "Do not prompt for confirmation when running in repair mode.")
|
||||
|
||||
return cCmd
|
||||
}
|
||||
|
||||
func check(cmd *cobra.Command, args []string) error {
|
||||
rootFlag, _ := cmd.Flags().GetString("root")
|
||||
repairFlag, _ := cmd.Flags().GetBool("repair")
|
||||
forceFlag, _ := cmd.Flags().GetBool("force")
|
||||
func check(c *cli.Context) error {
|
||||
rootFlag := c.String("root")
|
||||
repairFlag := c.Bool("repair")
|
||||
|
||||
if repairFlag && !forceFlag {
|
||||
if repairFlag && !c.Bool("force") {
|
||||
answer := strings.ToLower(stringPrompt("IMPORTANT: Only use '--repair' when OpenCloud is not running. Do you want to continue? [yes | no = default]"))
|
||||
if answer != "yes" && answer != "y" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
lu, backend := getBackend(cmd)
|
||||
lu, backend := getBackend(c)
|
||||
o := &options.Options{
|
||||
MetadataBackend: backend.Name(),
|
||||
MaxConcurrency: 100,
|
||||
@@ -86,7 +100,7 @@ func check(cmd *cobra.Command, args []string) error {
|
||||
|
||||
tree := tree.New(lu, bs, o, permissions.Permissions{}, store.Create(), &zerolog.Logger{})
|
||||
|
||||
nId, _ := cmd.Flags().GetString("node")
|
||||
nId := c.String("node")
|
||||
n, err := lu.NodeFromSpaceID(context.Background(), nId)
|
||||
if err != nil || !n.Exists {
|
||||
fmt.Println("Can not find node '" + nId + "'")
|
||||
@@ -102,10 +116,7 @@ func check(cmd *cobra.Command, args []string) error {
|
||||
})
|
||||
|
||||
treeSize, err := walkTree(ctx, tree, lu, n, repairFlag)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to walk tree of node %s: %s\n", n.ID, err)
|
||||
}
|
||||
treesizeFromMetadata, err := n.GetTreeSize(cmd.Context())
|
||||
treesizeFromMetadata, err := n.GetTreeSize(c.Context)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to read treesize of node: %s: %s\n", n.ID, err)
|
||||
}
|
||||
@@ -115,7 +126,7 @@ func check(cmd *cobra.Command, args []string) error {
|
||||
if repairFlag {
|
||||
fmt.Printf("Fixing tree size for node: %s. Calculated treesize: %d\n",
|
||||
n.ID, treeSize)
|
||||
n.SetTreeSize(cmd.Context(), treeSize)
|
||||
n.SetTreeSize(c.Context, treeSize)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -174,79 +185,105 @@ func walkTree(ctx context.Context, tree *tree.Tree, lu *lookup.Lookup, root *nod
|
||||
return treesize, nil
|
||||
}
|
||||
|
||||
func metadataCmd(cfg *config.Config) *cobra.Command {
|
||||
metaCmd := &cobra.Command{
|
||||
Use: "metadata",
|
||||
Short: `cli tools to inspect and manipulate node metadata`,
|
||||
func metadataCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "metadata",
|
||||
Usage: `cli tools to inspect and manipulate node metadata`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "root",
|
||||
Aliases: []string{"r"},
|
||||
Required: true,
|
||||
Usage: "Path to the decomposedfs",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "node",
|
||||
Required: true,
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Path to or ID of the node to inspect",
|
||||
},
|
||||
},
|
||||
Subcommands: []*cli.Command{dumpCmd(cfg), getCmd(cfg), setCmd(cfg)},
|
||||
}
|
||||
metaCmd.AddCommand(dumpCmd(cfg), getCmd(cfg), setCmd(cfg))
|
||||
metaCmd.Flags().StringP("root", "r", "", "Path to the root directory of the decomposedfs")
|
||||
_ = metaCmd.MarkFlagRequired("root")
|
||||
metaCmd.Flags().StringP("node", "n", "", "Path to or ID of the node to inspect")
|
||||
_ = metaCmd.MarkFlagRequired("node")
|
||||
return metaCmd
|
||||
}
|
||||
|
||||
func dumpCmd(_ *config.Config) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "dump",
|
||||
Short: `print the metadata of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
lu, backend := getBackend(cmd)
|
||||
path, err := getNode(cmd, lu)
|
||||
func dumpCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "dump",
|
||||
Usage: `print the metadata of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`,
|
||||
Action: func(c *cli.Context) error {
|
||||
lu, backend := getBackend(c)
|
||||
path, err := getNode(c, lu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attribs, err := backend.All(cmd.Context(), path)
|
||||
attribs, err := backend.All(c.Context, path)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading attributes")
|
||||
return err
|
||||
}
|
||||
attributeFlag, _ := cmd.Flags().GetString("attribute")
|
||||
printAttribs(attribs, attributeFlag)
|
||||
printAttribs(attribs, c.String("attribute"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getCmd(_ *config.Config) *cobra.Command {
|
||||
gCmd := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: `print a specific attribute of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
lu, backend := getBackend(cmd)
|
||||
path, err := getNode(cmd, lu)
|
||||
func getCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "get",
|
||||
Usage: `print a specific attribute of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "attribute",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "attribute to inspect",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
lu, backend := getBackend(c)
|
||||
path, err := getNode(c, lu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attribs, err := backend.All(cmd.Context(), path)
|
||||
attribs, err := backend.All(c.Context, path)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading attributes")
|
||||
return err
|
||||
}
|
||||
attributeFlag, _ := cmd.Flags().GetString("attribute")
|
||||
printAttribs(attribs, attributeFlag)
|
||||
printAttribs(attribs, c.String("attribute"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
gCmd.Flags().StringP("attribute", "a", "", "attribute to inspect, can be a glob pattern (e.g. 'user.*' will match all attributes starting with 'user.').")
|
||||
return gCmd
|
||||
}
|
||||
|
||||
func setCmd(_ *config.Config) *cobra.Command {
|
||||
sCmd := &cobra.Command{
|
||||
Use: "set",
|
||||
Short: `manipulate metadata of the given node. Binary attributes can be given hex encoded (prefix by '0x') or base64 encoded (prefix by '0s').`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
lu, backend := getBackend(cmd)
|
||||
n, err := getNode(cmd, lu)
|
||||
func setCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "set",
|
||||
Usage: `manipulate metadata of the given node. Binary attributes can be given hex encoded (prefix by '0x') or base64 encoded (prefix by '0s').`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "attribute",
|
||||
Required: true,
|
||||
Aliases: []string{"a"},
|
||||
Usage: "attribute to inspect",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "value",
|
||||
Required: true,
|
||||
Aliases: []string{"v"},
|
||||
Usage: "value to set",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
lu, backend := getBackend(c)
|
||||
n, err := getNode(c, lu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, _ := cmd.Flags().GetString("value")
|
||||
v := c.String("value")
|
||||
if strings.HasPrefix(v, "0s") {
|
||||
b64, err := base64.StdEncoding.DecodeString(v[2:])
|
||||
if err == nil {
|
||||
@@ -263,8 +300,7 @@ func setCmd(_ *config.Config) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
attributeFlag, _ := cmd.Flags().GetString("attribute")
|
||||
err = backend.Set(cmd.Context(), n, attributeFlag, []byte(v))
|
||||
err = backend.Set(c.Context, n, c.String("attribute"), []byte(v))
|
||||
if err != nil {
|
||||
fmt.Println("Error setting attribute")
|
||||
return err
|
||||
@@ -272,16 +308,9 @@ func setCmd(_ *config.Config) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
sCmd.Flags().StringP("attribute", "a", "", "attribute to inspect, can be a glob pattern (e.g. 'user.*' will match all attributes starting with 'user.').")
|
||||
_ = sCmd.MarkFlagRequired("attribute")
|
||||
|
||||
sCmd.Flags().StringP("value", "v", "", "value to set")
|
||||
_ = sCmd.MarkFlagRequired("value")
|
||||
|
||||
return sCmd
|
||||
}
|
||||
|
||||
func backend(backend string) metadata.Backend {
|
||||
func backend(root, backend string) metadata.Backend {
|
||||
switch backend {
|
||||
case "xattrs":
|
||||
return metadata.NewXattrsBackend(cache.Config{})
|
||||
@@ -291,11 +320,11 @@ func backend(backend string) metadata.Backend {
|
||||
return metadata.NullBackend{}
|
||||
}
|
||||
|
||||
func getBackend(cmd *cobra.Command) (*lookup.Lookup, metadata.Backend) {
|
||||
rootFlag, _ := cmd.Flags().GetString("root")
|
||||
func getBackend(c *cli.Context) (*lookup.Lookup, metadata.Backend) {
|
||||
rootFlag := c.String("root")
|
||||
|
||||
bod := lookup.DetectBackendOnDisk(rootFlag)
|
||||
backend := backend(bod)
|
||||
backend := backend(rootFlag, bod)
|
||||
lu := lookup.New(backend, &options.Options{
|
||||
Root: rootFlag,
|
||||
MetadataBackend: bod,
|
||||
@@ -303,8 +332,8 @@ func getBackend(cmd *cobra.Command) (*lookup.Lookup, metadata.Backend) {
|
||||
return lu, backend
|
||||
}
|
||||
|
||||
func getNode(cmd *cobra.Command, lu *lookup.Lookup) (*node.Node, error) {
|
||||
nodeFlag, _ := cmd.Flags().GetString("node")
|
||||
func getNode(c *cli.Context, lu *lookup.Lookup) (*node.Node, error) {
|
||||
nodeFlag := c.String("node")
|
||||
|
||||
id, err := storagespace.ParseID(nodeFlag)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func SubcommandDescription(serviceName string) string {
|
||||
return fmt.Sprintf("%s service commands", serviceName)
|
||||
}
|
||||
@@ -11,19 +11,49 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cli "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// InitCommand is the entrypoint for the init command
|
||||
func InitCommand(_ *config.Config) *cobra.Command {
|
||||
initCmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "initialise an OpenCloud config",
|
||||
GroupID: CommandGroupServer,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
insecureFlag := viper.GetString("insecure")
|
||||
func InitCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "init",
|
||||
Usage: "initialise an OpenCloud config",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "insecure",
|
||||
EnvVars: []string{"OC_INSECURE"},
|
||||
Value: "ask",
|
||||
Usage: "Allow insecure OpenCloud config",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "diff",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Show the difference between the current config and the new one",
|
||||
Value: false,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "force-overwrite",
|
||||
Aliases: []string{"f"},
|
||||
EnvVars: []string{"OC_FORCE_CONFIG_OVERWRITE"},
|
||||
Value: false,
|
||||
Usage: "Force overwrite existing config file",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config-path",
|
||||
Value: defaults.BaseConfigPath(),
|
||||
Usage: "Config path for the OpenCloud runtime",
|
||||
EnvVars: []string{"OC_CONFIG_DIR", "OC_BASE_DATA_PATH"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "admin-password",
|
||||
Aliases: []string{"ap"},
|
||||
EnvVars: []string{"ADMIN_PASSWORD", "IDM_ADMIN_PASSWORD"},
|
||||
Usage: "Set admin password instead of using a random generated one",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
insecureFlag := c.String("insecure")
|
||||
insecure := false
|
||||
if insecureFlag == "ask" {
|
||||
answer := strings.ToLower(stringPrompt("Do you want to configure OpenCloud with certificate checking disabled?\n This is not recommended for public instances! [yes | no = default]"))
|
||||
@@ -33,39 +63,13 @@ func InitCommand(_ *config.Config) *cobra.Command {
|
||||
} else if insecureFlag == strings.ToLower("true") || insecureFlag == strings.ToLower("yes") || insecureFlag == strings.ToLower("y") {
|
||||
insecure = true
|
||||
}
|
||||
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, quietFlag)
|
||||
err := ocinit.CreateConfig(insecure, c.Bool("force-overwrite"), c.Bool("diff"), c.String("config-path"), c.String("admin-password"))
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create config: %s", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
initCmd.Flags().String("insecure", "ask", "Allow insecure OpenCloud config")
|
||||
_ = viper.BindEnv("insecure", "OC_INSECURE")
|
||||
_ = 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")
|
||||
_ = viper.BindPFlag("force-overwrite", initCmd.Flags().Lookup("force-overwrite"))
|
||||
|
||||
initCmd.Flags().String("config-path", defaults.BaseConfigPath(), "Config path for the OpenCloud runtime")
|
||||
_ = viper.BindEnv("config-path", "OC_CONFIG_DIR")
|
||||
_ = viper.BindEnv("config-path", "OC_BASE_DATA_PATH")
|
||||
_ = viper.BindPFlag("config-path", initCmd.Flags().Lookup("config-path"))
|
||||
|
||||
initCmd.Flags().String("admin-password", "", "Set admin password instead of using a random generated one")
|
||||
_ = viper.BindEnv("admin-password", "ADMIN_PASSWORD")
|
||||
_ = viper.BindEnv("admin-password", "IDM_ADMIN_PASSWORD")
|
||||
_ = viper.BindPFlag("admin-password", initCmd.Flags().Lookup("admin-password"))
|
||||
return initCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -76,7 +80,7 @@ func stringPrompt(label string) string {
|
||||
input := ""
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
_, _ = fmt.Fprint(os.Stderr, label+" ")
|
||||
fmt.Fprint(os.Stderr, label+" ")
|
||||
input, _ = reader.ReadString('\n')
|
||||
if input != "" {
|
||||
break
|
||||
|
||||
@@ -8,26 +8,31 @@ 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"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// ListCommand is the entrypoint for the list command.
|
||||
func ListCommand(cfg *config.Config) *cobra.Command {
|
||||
listCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "list OpenCloud services running in the runtime (supervised mode)",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg, true))
|
||||
func ListCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "list OpenCloud services running in the runtime (supervised mode)",
|
||||
Category: "runtime",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Value: "localhost",
|
||||
EnvVars: []string{"OC_RUNTIME_HOST"},
|
||||
Destination: &cfg.Runtime.Host,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "port",
|
||||
Value: "9250",
|
||||
EnvVars: []string{"OC_RUNTIME_PORT"},
|
||||
Destination: &cfg.Runtime.Port,
|
||||
},
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
host := viper.GetString("hostname")
|
||||
port := viper.GetString("port")
|
||||
|
||||
client, err := rpc.DialHTTP("tcp", net.JoinHostPort(host, port))
|
||||
Action: func(c *cli.Context) error {
|
||||
client, err := rpc.DialHTTP("tcp", net.JoinHostPort(cfg.Runtime.Host, cfg.Runtime.Port))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to the runtime. Has the runtime been started and did you configure the right runtime address (\"%s\")", cfg.Runtime.Host+":"+cfg.Runtime.Port)
|
||||
}
|
||||
@@ -43,15 +48,6 @@ func ListCommand(cfg *config.Config) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
listCmd.Flags().String("hostname", "localhost", "hostname of the runtime")
|
||||
_ = viper.BindEnv("hostname", "OC_RUNTIME_HOST")
|
||||
_ = viper.BindPFlag("hostname", listCmd.Flags().Lookup("hostname"))
|
||||
|
||||
listCmd.Flags().String("port", "9250", "port of the runtime")
|
||||
_ = viper.BindEnv("port", "OC_RUNTIME_PORT")
|
||||
_ = viper.BindPFlag("port", listCmd.Flags().Lookup("port"))
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
+340
-326
@@ -1,32 +1,33 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/opencloud-eu/opencloud/pkg/x/path/filepathx"
|
||||
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/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
|
||||
|
||||
"github.com/pkg/xattr"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/theckman/yacspin"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
type IDCacher interface {
|
||||
WarmupIDCache(root string, assimilate, onlyDirty bool) error
|
||||
}
|
||||
// Define the names of the extended attributes we are working with.
|
||||
const (
|
||||
parentIDAttrName = "user.oc.parentid"
|
||||
idAttrName = "user.oc.id"
|
||||
spaceIDAttrName = "user.oc.space.id"
|
||||
ownerIDAttrName = "user.oc.owner.id"
|
||||
)
|
||||
|
||||
var (
|
||||
spinner *yacspin.Spinner
|
||||
restartRequired = false
|
||||
)
|
||||
|
||||
// EntryInfo holds information about a directory entry.
|
||||
type EntryInfo struct {
|
||||
@@ -36,333 +37,346 @@ type EntryInfo struct {
|
||||
}
|
||||
|
||||
// PosixfsCommand is the entrypoint for the posixfs command.
|
||||
func PosixfsCommand(cfg *config.Config) *cobra.Command {
|
||||
posixCmd := &cobra.Command{
|
||||
Use: "posixfs",
|
||||
Short: `cli tools to inspect and manipulate a posixfs storage.`,
|
||||
GroupID: CommandGroupStorage,
|
||||
func PosixfsCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "posixfs",
|
||||
Usage: `cli tools to inspect and manipulate a posixfs storage.`,
|
||||
Category: "maintenance",
|
||||
Subcommands: []*cli.Command{
|
||||
consistencyCmd(cfg),
|
||||
},
|
||||
}
|
||||
|
||||
posixCmd.AddCommand(consistencyCmd(cfg))
|
||||
posixCmd.AddCommand(scanCmd(cfg))
|
||||
|
||||
return posixCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
register.AddCommand(PosixfsCommand)
|
||||
}
|
||||
|
||||
// scanCmd performs a posixfs id cache warmup scan
|
||||
func scanCmd(ocCfg *config.Config) *cobra.Command {
|
||||
scanCmd := &cobra.Command{
|
||||
Use: "scan [path ...]",
|
||||
Short: "Perform a filesystem scan and update the ID and filemetadata cache",
|
||||
Long: `Perform a filesystem scan and update the ID and filemetadata cache.
|
||||
|
||||
You can specify one or more paths to limit the scope of the scan.
|
||||
If no path is provided, the whole storage is checked, starting at the storage root directory.
|
||||
|
||||
The provided arguments determines the scope of the check:
|
||||
- a storage root: the whole storage (all personal and project spaces) is scanned
|
||||
- a space root: only that space is scanned
|
||||
- a file or directory: only that single resource is scanned (and its children, if it is a directory)
|
||||
|
||||
Any specified file or directory must be underneath the storage root directory and if that is not the case,
|
||||
the command is aborted with an error before performing any scanning.`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := parser.ParseConfig(ocCfg, true); err != nil {
|
||||
return configlog.ReturnError(err)
|
||||
}
|
||||
|
||||
// Parse storage users config
|
||||
ocCfg.StorageUsers.Commons = ocCfg.Commons
|
||||
|
||||
return configlog.ReturnFatal(storageUsersParser.ParseConfig(ocCfg.StorageUsers))
|
||||
// consistencyCmd returns a command to check the consistency of the posixfs storage.
|
||||
func consistencyCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "consistency",
|
||||
Usage: "check the consistency of the posixfs storage",
|
||||
Action: func(c *cli.Context) error {
|
||||
return checkPosixfsConsistency(c, cfg)
|
||||
},
|
||||
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)
|
||||
}
|
||||
|
||||
haltOnError, err := cmd.Flags().GetBool("halt-on-error")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storageRoot := cfg.Drivers.Posix.Root
|
||||
paths := []string{storageRoot}
|
||||
if len(args) > 0 {
|
||||
paths = []string{}
|
||||
for _, v := range args {
|
||||
path := v
|
||||
if !filepath.IsAbs(path) {
|
||||
if v, err := filepath.Abs(path); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to make the specified path %q absolute: %v\n", v, err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
path = v
|
||||
}
|
||||
}
|
||||
// not ensuring whether the path is under the storage root here, will be done when iterating over them
|
||||
path = filepath.Clean(path)
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
|
||||
var scan func(path string) error = nil
|
||||
{
|
||||
// We want to initialize the driver but disable scanfs on boot, so we can trigger it manually afterwards
|
||||
drivers := revaconfig.StorageProviderDrivers(cfg)
|
||||
drivers["posix"] = revaconfig.Posix(cfg, false, false)
|
||||
|
||||
var fsStream events.Stream
|
||||
var err error
|
||||
fsStream, err = event.NewStream(cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create event stream for posix driver: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log := logger("posixfs")
|
||||
|
||||
f, ok := registry.NewFuncs["posix"]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "posix driver not found in registry\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fs, err := f(drivers["posix"].(map[string]any), fsStream, &log)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to initialize filesystem driver '%s': %v\n", cfg.Driver, err)
|
||||
return err
|
||||
}
|
||||
|
||||
cacher, ok := fs.(IDCacher)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "The posix driver does not expose WarmupIDCache.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
scan = func(path string) error {
|
||||
err := cacher.WarmupIDCache(path, true, false)
|
||||
if err != nil {
|
||||
logFailure("Error scanning path '%s': %v", path, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
errors := processPosixFsResources(paths, !haltOnError,
|
||||
func(path string) error {
|
||||
fmt.Println("Scanning personal spaces...")
|
||||
return scan(path)
|
||||
},
|
||||
func(path string) error {
|
||||
fmt.Println("Scanning project spaces...")
|
||||
return scan(path)
|
||||
},
|
||||
func(path string) error {
|
||||
fmt.Printf("Scanning space '%s'...\n", path)
|
||||
return scan(path)
|
||||
},
|
||||
func(path string) error {
|
||||
fmt.Printf("Scanning '%s'...\n", path)
|
||||
return scan(path)
|
||||
},
|
||||
)
|
||||
|
||||
if len(errors) == 0 {
|
||||
fmt.Println("Scan completed successfully.")
|
||||
return nil
|
||||
} else {
|
||||
plural := "s"
|
||||
if len(errors) == 1 {
|
||||
plural = ""
|
||||
}
|
||||
verb := "completed"
|
||||
if haltOnError {
|
||||
verb = "aborted"
|
||||
}
|
||||
return fmt.Errorf("scan %s with %d error%s", verb, len(errors), plural)
|
||||
}
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "root",
|
||||
Aliases: []string{"r"},
|
||||
Required: true,
|
||||
Usage: "Path to the root directory of the posixfs storage",
|
||||
},
|
||||
},
|
||||
}
|
||||
scanCmd.Flags().BoolP("halt-on-error", "E", false, "Halt at once when an error occurs when processing one of the paths (default behaviour is to keep going and attempt to process all paths).")
|
||||
return scanCmd
|
||||
}
|
||||
|
||||
// consistencyCmd returns a command to check the consistency of the posixfs storage.
|
||||
func consistencyCmd(ocCfg *config.Config) *cobra.Command {
|
||||
consCmd := &cobra.Command{
|
||||
Use: "consistency [path ...]",
|
||||
Short: "Check the consistency of the posixfs storage",
|
||||
Long: `Check the consistency of the posixfs storage.
|
||||
// checkPosixfsConsistency checks the consistency of the posixfs storage.
|
||||
func checkPosixfsConsistency(c *cli.Context, cfg *config.Config) error {
|
||||
rootPath := c.String("root")
|
||||
indexesPath := filepath.Join(rootPath, "indexes")
|
||||
|
||||
You can specify one or more paths to limit the scope of the check.
|
||||
If no path is provided, the whole storage is checked.
|
||||
|
||||
The provided arguments determines the scope of the check:
|
||||
- a storage root: the whole storage (all personal and project spaces) is checked
|
||||
- a space root: only that space is checked
|
||||
- a file or directory: only that single entity is checked (and its children, if it is a directory)`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
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 len(args) == 0 {
|
||||
args = []string{cfg.Drivers.Posix.Root}
|
||||
}
|
||||
log := logger("posixfs")
|
||||
recalculateChecksums, err := cmd.Flags().GetBool("fix-checksums")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
drivers := revaconfig.StorageProviderDrivers(cfg)
|
||||
drivers["posix"] = revaconfig.Posix(cfg, false, false)
|
||||
opts, err := options.New(drivers["posix"].(map[string]any))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ignorer := ignore.NewIgnorer(opts, &log)
|
||||
|
||||
checker := &consistencyChecker{
|
||||
cfg: cfg,
|
||||
ignorer: ignorer,
|
||||
recalculateChecksums: recalculateChecksums,
|
||||
}
|
||||
|
||||
return checker.Check(args)
|
||||
},
|
||||
_, err := os.Stat(indexesPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("consistency check failed: '%s' is not a posixfs root", rootPath)
|
||||
}
|
||||
return fmt.Errorf("error accessing '%s': %w", indexesPath, err)
|
||||
}
|
||||
consCmd.Flags().Bool("fix-checksums", false, "Recalculate and fix the file checksums. This reads every file and can be slow on large storages.")
|
||||
return consCmd
|
||||
|
||||
spinnerCfg := yacspin.Config{
|
||||
Frequency: 100 * time.Millisecond,
|
||||
CharSet: yacspin.CharSets[11],
|
||||
StopCharacter: "✓",
|
||||
StopColors: []string{"fgGreen"},
|
||||
StopFailCharacter: "✗",
|
||||
StopFailColors: []string{"fgRed"},
|
||||
}
|
||||
|
||||
spinner, err = yacspin.New(spinnerCfg)
|
||||
err = spinner.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating spinner: %w", err)
|
||||
}
|
||||
|
||||
checkSpaces(filepath.Join(rootPath, "users"))
|
||||
spinner.Suffix(" Personal spaces check ")
|
||||
spinner.StopMessage("completed\n")
|
||||
spinner.Stop()
|
||||
|
||||
checkSpaces(filepath.Join(rootPath, "projects"))
|
||||
spinner.Suffix(" Project spaces check ")
|
||||
spinner.StopMessage("completed")
|
||||
spinner.Stop()
|
||||
|
||||
if restartRequired {
|
||||
fmt.Println("\n\n ⚠️ Please restart your openCloud instance to apply changes.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSpaces(basePath string) {
|
||||
dirEntries, err := os.ReadDir(basePath)
|
||||
if err != nil {
|
||||
spinner.Message(fmt.Sprintf("Error reading spaces directory '%s'\n", basePath))
|
||||
spinner.StopFail()
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
fullPath := filepath.Join(basePath, entry.Name())
|
||||
checkSpace(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkSpace(spacePath string) {
|
||||
spinner.Suffix(fmt.Sprintf(" Checking space '%s'", spacePath))
|
||||
|
||||
info, err := os.Stat(spacePath)
|
||||
if err != nil {
|
||||
logFailure("Error accessing path '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
if !info.IsDir() {
|
||||
logFailure("Error: The provided path '%s' is not a directory\n", spacePath)
|
||||
return
|
||||
}
|
||||
|
||||
spaceID, err := xattr.Get(spacePath, spaceIDAttrName)
|
||||
if err != nil || len(spaceID) == 0 {
|
||||
logFailure("Error: The directory '%s' does not seem to be a space root, it's missing the '%s' attribute\n", spacePath, spaceIDAttrName)
|
||||
return
|
||||
}
|
||||
|
||||
checkSpaceID(spacePath)
|
||||
}
|
||||
|
||||
func checkSpaceID(spacePath string) {
|
||||
spinner.Message("checking space ID uniqueness")
|
||||
|
||||
entries, uniqueIDs, oldestEntry, err := gatherAttributes(spacePath)
|
||||
if err != nil {
|
||||
logFailure("Failed to gather attributes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
logSuccess("(empty space)")
|
||||
return
|
||||
}
|
||||
|
||||
if len(uniqueIDs) > 1 {
|
||||
spinner.Pause()
|
||||
fmt.Println("\n ⚠ Multiple space IDs found:")
|
||||
for id := range uniqueIDs {
|
||||
fmt.Printf(" - %s\n", id)
|
||||
}
|
||||
|
||||
fmt.Printf("\n ⏳ Oldest entry is '%s' (modified on %s).\n",
|
||||
filepath.Base(oldestEntry.Path), oldestEntry.ModTime.Format(time.RFC1123))
|
||||
|
||||
targetID := oldestEntry.ParentID
|
||||
fmt.Printf(" ✅ Proposed target Parent ID: %s\n", targetID)
|
||||
|
||||
fmt.Printf("\n Do you want to unify all parent IDs to '%s'? This will modify %d entries, the directory, and the user index. (y/N): ", targetID, len(entries))
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
|
||||
if input != "y" {
|
||||
spinner.Unpause()
|
||||
logFailure("Operation cancelled by user.")
|
||||
return
|
||||
}
|
||||
restartRequired = true
|
||||
|
||||
obsoleteIDs := []string{}
|
||||
for id := range uniqueIDs {
|
||||
if id != targetID {
|
||||
obsoleteIDs = append(obsoleteIDs, id)
|
||||
}
|
||||
}
|
||||
fixSpaceID(spacePath, obsoleteIDs, targetID, entries)
|
||||
spinner.Unpause()
|
||||
} else {
|
||||
logSuccess("")
|
||||
}
|
||||
}
|
||||
|
||||
func fixSpaceID(spacePath string, obsoleteIDs []string, targetID string, entries []EntryInfo) {
|
||||
// Set all parentid attributes to the proper space ID
|
||||
err := setAllParentIDAttributes(entries, targetID)
|
||||
if err != nil {
|
||||
logFailure("an error occurred during file attribute update: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update space ID itself
|
||||
fmt.Printf(" Updating directory '%s' with attribute '%s' -> %s\n", filepath.Base(spacePath), idAttrName, targetID)
|
||||
err = xattr.Set(spacePath, idAttrName, []byte(targetID))
|
||||
if err != nil {
|
||||
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
err = xattr.Set(spacePath, spaceIDAttrName, []byte(targetID))
|
||||
if err != nil {
|
||||
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// update the index
|
||||
err = updateOwnerIndexFile(spacePath, obsoleteIDs)
|
||||
if err != nil {
|
||||
logFailure("Could not update the owner index file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func gatherAttributes(path string) ([]EntryInfo, map[string]struct{}, EntryInfo, error) {
|
||||
dirEntries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, nil, EntryInfo{}, fmt.Errorf("failed to read directory: %w", err)
|
||||
}
|
||||
|
||||
var allEntries []EntryInfo
|
||||
uniqueIDs := make(map[string]struct{})
|
||||
var oldestEntry EntryInfo
|
||||
oldestTime := time.Now().Add(100 * 365 * 24 * time.Hour) // Set to a future date to find the oldest entry
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
fullPath := filepath.Join(path, entry.Name())
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
fmt.Printf(" - Warning: could not stat %s: %v\n", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
parentID, err := xattr.Get(fullPath, parentIDAttrName)
|
||||
if err != nil {
|
||||
continue // Skip if attribute doesn't exist or can't be read
|
||||
}
|
||||
|
||||
entryInfo := EntryInfo{
|
||||
Path: fullPath,
|
||||
ModTime: info.ModTime(),
|
||||
ParentID: string(parentID),
|
||||
}
|
||||
|
||||
allEntries = append(allEntries, entryInfo)
|
||||
uniqueIDs[string(parentID)] = struct{}{}
|
||||
|
||||
if entryInfo.ModTime.Before(oldestTime) {
|
||||
oldestTime = entryInfo.ModTime
|
||||
oldestEntry = entryInfo
|
||||
}
|
||||
}
|
||||
|
||||
return allEntries, uniqueIDs, oldestEntry, nil
|
||||
}
|
||||
|
||||
func setAllParentIDAttributes(entries []EntryInfo, targetID string) error {
|
||||
fmt.Printf(" Setting all parent IDs to '%s':\n", targetID)
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.ParentID == targetID {
|
||||
fmt.Printf(" - Skipping '%s' (already has target ID).\n", filepath.Base(entry.Path))
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" - Removing all attributes from '%s'. It will be re-assimilated\n", filepath.Base(entry.Path))
|
||||
filepath.WalkDir(entry.Path, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking path '%s': %w", path, err)
|
||||
}
|
||||
|
||||
// Remove all attributes from the file.
|
||||
if err := removeAttributes(path); err != nil {
|
||||
fmt.Printf("failed to remove attributes from '%s': %v", path, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateOwnerIndexFile handles the logic of reading, modifying, and writing the MessagePack index file.
|
||||
func updateOwnerIndexFile(basePath string, obsoleteIDs []string) error {
|
||||
fmt.Printf(" Rewriting index file '%s'\n", basePath)
|
||||
|
||||
ownerID, err := xattr.Get(basePath, ownerIDAttrName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get owner ID from oldest entry '%s' to find index: %w", basePath, err)
|
||||
}
|
||||
|
||||
indexPath := filepath.Join(basePath, "../../indexes/by-user-id", string(ownerID)+".mpk")
|
||||
indexPath = filepath.Clean(indexPath)
|
||||
|
||||
// Read the MessagePack file
|
||||
fileData, err := os.ReadFile(indexPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("index file does not exist, skipping update")
|
||||
}
|
||||
return fmt.Errorf("could not read index file: %w", err)
|
||||
}
|
||||
var indexMap map[string]string
|
||||
if err := msgpack.Unmarshal(fileData, &indexMap); err != nil {
|
||||
return fmt.Errorf("failed to parse MessagePack index file (is it corrupt?): %w", err)
|
||||
}
|
||||
|
||||
// Remove obsolete IDs from the map
|
||||
itemsRemoved := 0
|
||||
for _, id := range obsoleteIDs {
|
||||
if _, exists := indexMap[id]; exists {
|
||||
fmt.Printf(" - Removing obsolete ID '%s' from index.\n", id)
|
||||
delete(indexMap, id)
|
||||
itemsRemoved++
|
||||
} else {
|
||||
fmt.Printf(" - Obsolete ID '%s' not found in index\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
if itemsRemoved == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write the data back to the file
|
||||
updatedData, err := msgpack.Marshal(&indexMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal updated index map: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(indexPath, updatedData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write updated index file: %w", err)
|
||||
}
|
||||
|
||||
logSuccess("Successfully removed %d item(s) and saved index file.\n", itemsRemoved)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAttributes(path string) error {
|
||||
attrNames, err := xattr.List(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list attributes for '%s': %w", path, err)
|
||||
}
|
||||
|
||||
for _, attrName := range attrNames {
|
||||
if err := xattr.Remove(path, attrName); err != nil {
|
||||
return fmt.Errorf("failed to remove attribute '%s' from '%s': %w", attrName, path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logFailure(message string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, message+"\n", args...)
|
||||
spinner.StopFailMessage(fmt.Sprintf(message, args...))
|
||||
spinner.StopFail()
|
||||
spinner.Start()
|
||||
}
|
||||
|
||||
// findStorageRoot walks up the directory tree starting at path until it finds a
|
||||
// directory that contains an "indexes" subdirectory which marks the root of a
|
||||
// posixfs storage. A user directory inside a space might also be named "indexes",
|
||||
// so to disambiguate we require that the "indexes" directory is an internal
|
||||
// directory: the storage's own indexes directory is skipped during assimilation
|
||||
// and therefore never receives a node ID attribute, whereas a regular user
|
||||
// directory would have one.
|
||||
func findStorageRoot(path string) (string, error) {
|
||||
current := path
|
||||
for {
|
||||
indexesPath := filepath.Join(current, "indexes")
|
||||
if info, err := os.Stat(indexesPath); err == nil && info.IsDir() {
|
||||
if id, err := xattr.Get(indexesPath, prefixes.IDAttr); err != nil || len(id) == 0 {
|
||||
return current, nil
|
||||
}
|
||||
}
|
||||
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
return "", fmt.Errorf("'%s' does not appear to be inside a posixfs storage (no 'indexes' directory found)", path)
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
|
||||
// isSpaceRoot reports whether the given path is a space root, which is
|
||||
// identified by the presence of the space ID attribute.
|
||||
func isSpaceRoot(path string) bool {
|
||||
spaceID, err := xattr.Get(path, prefixes.SpaceIDAttr)
|
||||
return err == nil && len(spaceID) > 0
|
||||
}
|
||||
|
||||
// iterates over a list of paths and processes them all, using the appropriate function
|
||||
// depending on the type of resource
|
||||
//
|
||||
// note that whenever an error occurs, it collects that error and continues processing
|
||||
// subsequent paths, and then returns a slice of errors at the end (or an empty slice
|
||||
// if no errors occured)
|
||||
func processPosixFsResources(paths []string,
|
||||
keepGoing bool,
|
||||
personalSpaceDir func(string) error,
|
||||
projectSpaceDir func(string) error,
|
||||
spaceRoot func(string) error,
|
||||
entity func(string) error,
|
||||
) []error {
|
||||
// no need to guard this with a mutex for now, since the implementation is not parallelized
|
||||
errors := []error{}
|
||||
|
||||
for _, path := range paths {
|
||||
rootPath, err := findStorageRoot(path)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
logFailure("error: %s", err)
|
||||
if keepGoing {
|
||||
continue
|
||||
} else {
|
||||
return errors
|
||||
}
|
||||
}
|
||||
path = filepath.Clean(path)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
errors = append(errors, err)
|
||||
logFailure("error accessing '%s': %w", path, err)
|
||||
if keepGoing {
|
||||
continue
|
||||
} else {
|
||||
return errors
|
||||
}
|
||||
}
|
||||
contained, _ := filepathx.IsSameOrContainedBy(rootPath, path)
|
||||
|
||||
switch {
|
||||
case path == rootPath:
|
||||
if err := personalSpaceDir(filepath.Join(path, "users")); err != nil {
|
||||
errors = append(errors, err)
|
||||
if !keepGoing {
|
||||
return errors
|
||||
}
|
||||
}
|
||||
if err := projectSpaceDir(filepath.Join(path, "projects")); err != nil {
|
||||
errors = append(errors, err)
|
||||
if !keepGoing {
|
||||
return errors
|
||||
}
|
||||
}
|
||||
case isSpaceRoot(path):
|
||||
if err := spaceRoot(path); err != nil {
|
||||
errors = append(errors, err)
|
||||
if !keepGoing {
|
||||
return errors
|
||||
}
|
||||
}
|
||||
case contained:
|
||||
if err := entity(path); err != nil {
|
||||
errors = append(errors, err)
|
||||
if !keepGoing {
|
||||
return errors
|
||||
}
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("error: the provided path '%s' is neither a space root nor contained by the storage root '%s'", path, rootPath)
|
||||
errors = append(errors, err)
|
||||
logFailure(err.Error())
|
||||
if !keepGoing {
|
||||
return errors
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
func logSuccess(message string, args ...any) {
|
||||
spinner.StopMessage(fmt.Sprintf(message, args...))
|
||||
spinner.Stop()
|
||||
spinner.Start()
|
||||
}
|
||||
@@ -1,489 +0,0 @@
|
||||
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
storageUsersConfig "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node"
|
||||
"github.com/pkg/xattr"
|
||||
"github.com/shamaton/msgpack/v2"
|
||||
)
|
||||
|
||||
type consistencyChecker struct {
|
||||
cfg *storageUsersConfig.Config
|
||||
ignorer *ignore.Ignorer
|
||||
recalculateChecksums bool
|
||||
|
||||
restartRequired bool
|
||||
}
|
||||
|
||||
// checkPosixfsConsistency checks the consistency of the posixfs storage. The
|
||||
// given path determines the scope of the check: the whole storage, a single
|
||||
// space or a single entity within a space.
|
||||
func (c *consistencyChecker) Check(paths []string) error {
|
||||
_ = processPosixFsResources(paths, true,
|
||||
func(path string) error {
|
||||
fmt.Println("Checking personal spaces...")
|
||||
c.checkSpaces(path)
|
||||
return nil
|
||||
},
|
||||
func(path string) error {
|
||||
fmt.Println("Checking project spaces...")
|
||||
c.checkSpaces(path)
|
||||
return nil
|
||||
},
|
||||
func(path string) error {
|
||||
fmt.Printf("Checking space '%s'...\n", path)
|
||||
c.checkSpace(path)
|
||||
return nil
|
||||
},
|
||||
func(path string) error {
|
||||
if c.ignorer.IsIgnored(path) {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Checking '%s'...\n", path)
|
||||
c.checkEntity(path)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if c.restartRequired {
|
||||
fmt.Println("\n\n ⚠️ Please restart your openCloud instance to apply changes.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *consistencyChecker) checkSpaces(basePath string) {
|
||||
dirEntries, err := os.ReadDir(basePath)
|
||||
if err != nil {
|
||||
logFailure("Error reading spaces directory '%s': %v", basePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
if entry.IsDir() {
|
||||
fullPath := filepath.Join(basePath, entry.Name())
|
||||
c.checkSpace(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *consistencyChecker) checkSpace(spacePath string) {
|
||||
info, err := os.Stat(spacePath)
|
||||
if err != nil {
|
||||
logFailure("Error accessing path '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
if !info.IsDir() {
|
||||
logFailure("Error: The provided path '%s' is not a directory\n", spacePath)
|
||||
return
|
||||
}
|
||||
|
||||
spaceID, err := xattr.Get(spacePath, prefixes.SpaceIDAttr)
|
||||
if err != nil || len(spaceID) == 0 {
|
||||
logFailure("Error: The directory '%s' does not seem to be a space root, it's missing the '%s' attribute\n", spacePath, prefixes.SpaceIDAttr)
|
||||
return
|
||||
}
|
||||
|
||||
c.checkSpaceID(spacePath)
|
||||
c.checkNodes(spacePath)
|
||||
}
|
||||
|
||||
func (c *consistencyChecker) checkSpaceID(spacePath string) {
|
||||
entries, uniqueIDs, oldestEntry, err := c.gatherAttributes(spacePath)
|
||||
if err != nil {
|
||||
logFailure("Failed to gather attributes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(uniqueIDs) > 1 {
|
||||
fmt.Println("\n ⚠ Multiple space IDs found:")
|
||||
for id := range uniqueIDs {
|
||||
fmt.Printf(" - %s\n", id)
|
||||
}
|
||||
|
||||
fmt.Printf("\n ⏳ Oldest entry is '%s' (modified on %s).\n",
|
||||
filepath.Base(oldestEntry.Path), oldestEntry.ModTime.Format(time.RFC1123))
|
||||
|
||||
targetID := oldestEntry.ParentID
|
||||
fmt.Printf(" ✅ Proposed target Parent ID: %s\n", targetID)
|
||||
|
||||
fmt.Printf("\n Do you want to unify all parent IDs to '%s'? This will modify %d entries, the directory, and the user index. (y/N): ", targetID, len(entries))
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
|
||||
if input != "y" {
|
||||
logFailure("Operation cancelled by user.")
|
||||
return
|
||||
}
|
||||
c.restartRequired = true
|
||||
|
||||
obsoleteIDs := []string{}
|
||||
for id := range uniqueIDs {
|
||||
if id != targetID {
|
||||
obsoleteIDs = append(obsoleteIDs, id)
|
||||
}
|
||||
}
|
||||
c.fixSpaceID(spacePath, obsoleteIDs, targetID, entries)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *consistencyChecker) walkNodes(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 c.ignorer.IsIgnored(fullPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
fixes += c.checkNodeAttributes(fullPath, entry.Name(), parentID, entry.IsDir())
|
||||
|
||||
if entry.IsDir() {
|
||||
nodeID, err := xattr.Get(fullPath, prefixes.IDAttr)
|
||||
if err != nil || len(nodeID) == 0 {
|
||||
logFailure("Directory '%s' missing '%s', skipping its children", fullPath, prefixes.IDAttr)
|
||||
continue
|
||||
}
|
||||
fixes += c.walkNodes(fullPath, string(nodeID))
|
||||
}
|
||||
}
|
||||
return fixes
|
||||
}
|
||||
|
||||
// checkNodeAttributes checks and fixes the parent ID and name attributes of a
|
||||
// single node. For files it additionally checks the blobsize and, when
|
||||
// requested, the checksums. It returns the number of fixes applied.
|
||||
func (c *consistencyChecker) checkNodeAttributes(path, name, parentID string, isDir bool) int {
|
||||
fixes := 0
|
||||
|
||||
// Check if the parent ID attribute matches the expected parent ID, if not, fix it.
|
||||
actualParentID, err := xattr.Get(path, prefixes.ParentidAttr)
|
||||
if err != nil || string(actualParentID) != parentID {
|
||||
if err := xattr.Set(path, prefixes.ParentidAttr, []byte(parentID)); err != nil {
|
||||
logFailure("Failed to fix parent ID for '%s': %v", path, err)
|
||||
} else {
|
||||
fmt.Printf(" + Fixed parent ID for '%s'\n", path)
|
||||
fixes++
|
||||
c.restartRequired = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the name attribute matches the actual name of the file/directory, if not, fix it.
|
||||
nameAttr, err := xattr.Get(path, prefixes.NameAttr)
|
||||
if err != nil || string(nameAttr) != name {
|
||||
if err := xattr.Set(path, prefixes.NameAttr, []byte(name)); err != nil {
|
||||
logFailure("Failed to fix name attribute for '%s': %v", path, err)
|
||||
} else {
|
||||
fmt.Printf(" + Fixed name attribute for '%s'\n", path)
|
||||
fixes++
|
||||
c.restartRequired = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isDir {
|
||||
fixes += c.checkBlobsize(path)
|
||||
if c.recalculateChecksums {
|
||||
fixes += c.fixChecksums(path)
|
||||
}
|
||||
}
|
||||
|
||||
return fixes
|
||||
}
|
||||
|
||||
// checkEntity checks a single file or directory within a space, including its own
|
||||
// parent ID, name and (for files) blobsize/checksums. If the entity is a directory
|
||||
// its children are checked recursively.
|
||||
func (c *consistencyChecker) checkEntity(path string) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
logFailure("Error accessing path '%s': %v", path, err)
|
||||
return
|
||||
}
|
||||
|
||||
// The expected parent ID is the ID attribute of the containing directory.
|
||||
parentDir := filepath.Dir(path)
|
||||
parentID, err := xattr.Get(parentDir, prefixes.IDAttr)
|
||||
if err != nil || len(parentID) == 0 {
|
||||
logFailure("Parent directory '%s' is missing the '%s' attribute", parentDir, prefixes.IDAttr)
|
||||
return
|
||||
}
|
||||
|
||||
fixes := c.checkNodeAttributes(path, info.Name(), string(parentID), info.IsDir())
|
||||
|
||||
if info.IsDir() {
|
||||
nodeID, err := xattr.Get(path, prefixes.IDAttr)
|
||||
if err != nil || len(nodeID) == 0 {
|
||||
logFailure("Directory '%s' missing '%s' attribute", path, prefixes.IDAttr)
|
||||
} else {
|
||||
fixes += c.walkNodes(path, string(nodeID))
|
||||
}
|
||||
}
|
||||
|
||||
if fixes > 0 {
|
||||
fmt.Printf(" ✓ Fixed %d incorrect node attributes for %s\n", fixes, filepath.Base(path))
|
||||
}
|
||||
}
|
||||
|
||||
// checkBlobsize verifies that the stored blobsize attribute matches the actual
|
||||
// file size and fixes it if it doesn't. It returns the number of fixes applied.
|
||||
func (c *consistencyChecker) checkBlobsize(path string) int {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
logFailure("Error accessing file '%s': %v", path, err)
|
||||
return 0
|
||||
}
|
||||
|
||||
expectedSize := strconv.FormatInt(info.Size(), 10)
|
||||
blobsize, err := xattr.Get(path, prefixes.BlobsizeAttr)
|
||||
if err == nil && string(blobsize) == expectedSize {
|
||||
return 0
|
||||
}
|
||||
|
||||
if err := xattr.Set(path, prefixes.BlobsizeAttr, []byte(expectedSize)); err != nil {
|
||||
logFailure("Failed to fix blobsize for '%s': %v", path, err)
|
||||
return 0
|
||||
}
|
||||
|
||||
fmt.Printf(" + Fixed blobsize for '%s'\n", path)
|
||||
c.restartRequired = true
|
||||
return 1
|
||||
}
|
||||
|
||||
// fixChecksums recalculates the sha1, md5 and adler32 checksums of the file and
|
||||
// updates the stored attributes if they differ. It returns the number of fixes applied.
|
||||
func (c *consistencyChecker) fixChecksums(path string) int {
|
||||
sha1h, md5h, adler32h, err := node.CalculateChecksums(context.Background(), path)
|
||||
if err != nil {
|
||||
logFailure("Failed to calculate checksums for '%s': %v", path, err)
|
||||
return 0
|
||||
}
|
||||
|
||||
checksums := map[string][]byte{
|
||||
prefixes.ChecksumPrefix + "sha1": sha1h.Sum(nil),
|
||||
prefixes.ChecksumPrefix + "md5": md5h.Sum(nil),
|
||||
prefixes.ChecksumPrefix + "adler32": adler32h.Sum(nil),
|
||||
}
|
||||
|
||||
fixes := 0
|
||||
for attrName, sum := range checksums {
|
||||
current, err := xattr.Get(path, attrName)
|
||||
if err == nil && bytes.Equal(current, sum) {
|
||||
continue
|
||||
}
|
||||
if err := xattr.Set(path, attrName, sum); err != nil {
|
||||
logFailure("Failed to fix checksum '%s' for '%s': %v", attrName, path, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" + Fixed checksum '%s' for '%s'\n", attrName, path)
|
||||
c.restartRequired = true
|
||||
fixes++
|
||||
}
|
||||
return fixes
|
||||
}
|
||||
|
||||
func (c *consistencyChecker) checkNodes(spacePath string) {
|
||||
rootID, err := xattr.Get(spacePath, prefixes.IDAttr)
|
||||
if err != nil || len(rootID) == 0 {
|
||||
logFailure("Space root '%s' missing '%s' attribute", spacePath, prefixes.IDAttr)
|
||||
return
|
||||
}
|
||||
|
||||
fixes := c.walkNodes(spacePath, string(rootID))
|
||||
|
||||
if fixes > 0 {
|
||||
fmt.Printf(" ✓ Fixed %d incorrect node attributes in %s\n", fixes, filepath.Base(spacePath))
|
||||
}
|
||||
}
|
||||
|
||||
// fixSpaceID updates the parentid attributes of all entries in a space to a new target ID,
|
||||
// updates the space's own ID attributes, and removes obsolete IDs from the user index file.
|
||||
func (c *consistencyChecker) fixSpaceID(spacePath string, obsoleteIDs []string, targetID string, entries []EntryInfo) {
|
||||
// Set all parentid attributes to the proper space ID
|
||||
err := setAllParentIDAttributes(entries, targetID)
|
||||
if err != nil {
|
||||
logFailure("an error occurred during file attribute update: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update space ID itself
|
||||
fmt.Printf(" Updating directory '%s' with attribute '%s' -> %s\n", filepath.Base(spacePath), prefixes.IDAttr, targetID)
|
||||
err = xattr.Set(spacePath, prefixes.IDAttr, []byte(targetID))
|
||||
if err != nil {
|
||||
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
err = xattr.Set(spacePath, prefixes.SpaceIDAttr, []byte(targetID))
|
||||
if err != nil {
|
||||
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// update the index
|
||||
err = c.updateOwnerIndexFile(spacePath, obsoleteIDs)
|
||||
if err != nil {
|
||||
logFailure("Could not update the owner index file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *consistencyChecker) gatherAttributes(path string) ([]EntryInfo, map[string]struct{}, EntryInfo, error) {
|
||||
dirEntries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, nil, EntryInfo{}, fmt.Errorf("failed to read directory: %w", err)
|
||||
}
|
||||
|
||||
var allEntries []EntryInfo
|
||||
uniqueIDs := make(map[string]struct{})
|
||||
var oldestEntry EntryInfo
|
||||
oldestTime := time.Now().Add(100 * 365 * 24 * time.Hour) // Set to a future date to find the oldest entry
|
||||
|
||||
for _, entry := range dirEntries {
|
||||
fullPath := filepath.Join(path, entry.Name())
|
||||
if c.ignorer.IsIgnored(fullPath) {
|
||||
continue
|
||||
}
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
fmt.Printf(" - Warning: could not stat %s: %v\n", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
parentID, err := xattr.Get(fullPath, prefixes.ParentidAttr)
|
||||
if err != nil {
|
||||
continue // Skip if attribute doesn't exist or can't be read
|
||||
}
|
||||
|
||||
entryInfo := EntryInfo{
|
||||
Path: fullPath,
|
||||
ModTime: info.ModTime(),
|
||||
ParentID: string(parentID),
|
||||
}
|
||||
|
||||
allEntries = append(allEntries, entryInfo)
|
||||
uniqueIDs[string(parentID)] = struct{}{}
|
||||
|
||||
if entryInfo.ModTime.Before(oldestTime) {
|
||||
oldestTime = entryInfo.ModTime
|
||||
oldestEntry = entryInfo
|
||||
}
|
||||
}
|
||||
|
||||
return allEntries, uniqueIDs, oldestEntry, nil
|
||||
}
|
||||
|
||||
func setAllParentIDAttributes(entries []EntryInfo, targetID string) error {
|
||||
fmt.Printf(" Setting all parent IDs to '%s':\n", targetID)
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.ParentID == targetID {
|
||||
fmt.Printf(" - Skipping '%s' (already has target ID).\n", filepath.Base(entry.Path))
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" - Removing all attributes from '%s'. It will be re-assimilated\n", filepath.Base(entry.Path))
|
||||
filepath.WalkDir(entry.Path, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking path '%s': %w", path, err)
|
||||
}
|
||||
|
||||
// Remove all attributes from the file.
|
||||
if err := removeAttributes(path); err != nil {
|
||||
fmt.Printf("failed to remove attributes from '%s': %v", path, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateOwnerIndexFile handles the logic of reading, modifying, and writing the MessagePack index file.
|
||||
func (c *consistencyChecker) updateOwnerIndexFile(basePath string, obsoleteIDs []string) error {
|
||||
fmt.Printf(" Rewriting index file '%s'\n", basePath)
|
||||
|
||||
ownerID, err := xattr.Get(basePath, prefixes.OwnerIDAttr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get owner ID from oldest entry '%s' to find index: %w", basePath, err)
|
||||
}
|
||||
|
||||
indexPath := filepath.Join(c.cfg.Drivers.Posix.Root, "indexes", "by-user-id", string(ownerID)+".mpk")
|
||||
indexPath = filepath.Clean(indexPath)
|
||||
|
||||
// Read the MessagePack file
|
||||
fileData, err := os.ReadFile(indexPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("index file does not exist, skipping update")
|
||||
}
|
||||
return fmt.Errorf("could not read index file: %w", err)
|
||||
}
|
||||
var indexMap map[string]string
|
||||
if err := msgpack.Unmarshal(fileData, &indexMap); err != nil {
|
||||
return fmt.Errorf("failed to parse MessagePack index file (is it corrupt?): %w", err)
|
||||
}
|
||||
|
||||
// Remove obsolete IDs from the map
|
||||
itemsRemoved := 0
|
||||
for _, id := range obsoleteIDs {
|
||||
if _, exists := indexMap[id]; exists {
|
||||
fmt.Printf(" - Removing obsolete ID '%s' from index.\n", id)
|
||||
delete(indexMap, id)
|
||||
itemsRemoved++
|
||||
} else {
|
||||
fmt.Printf(" - Obsolete ID '%s' not found in index\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
if itemsRemoved == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write the data back to the file
|
||||
updatedData, err := msgpack.Marshal(&indexMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal updated index map: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(indexPath, updatedData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write updated index file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf(" ✓ Successfully removed %d item(s) and saved index file.\n", itemsRemoved)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAttributes(path string) error {
|
||||
attrNames, err := xattr.List(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list attributes for '%s': %w", path, err)
|
||||
}
|
||||
|
||||
for _, attrName := range attrNames {
|
||||
if err := xattr.Remove(path, attrName); err != nil {
|
||||
return fmt.Errorf("failed to remove attribute '%s' from '%s': %w", attrName, path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/revisions"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
@@ -16,8 +15,7 @@ import (
|
||||
decomposeds3bs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -25,36 +23,76 @@ var (
|
||||
_nodesGlobPattern = "spaces/*/*/nodes/"
|
||||
)
|
||||
|
||||
const posixDriver = "posix"
|
||||
|
||||
// RevisionsCommand is the entrypoint for the revisions command.
|
||||
func RevisionsCommand(cfg *config.Config) *cobra.Command {
|
||||
revCmd := &cobra.Command{
|
||||
Use: "revisions",
|
||||
Short: "OpenCloud revisions functionality",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
func RevisionsCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "revisions",
|
||||
Usage: "OpenCloud revisions functionality",
|
||||
Subcommands: []*cli.Command{
|
||||
PurgeRevisionsCommand(cfg),
|
||||
},
|
||||
Before: func(_ *cli.Context) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg, true))
|
||||
},
|
||||
Action: func(_ *cli.Context) error {
|
||||
fmt.Println("Read the docs")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
revCmd.AddCommand(PurgeRevisionsCommand(cfg))
|
||||
|
||||
return revCmd
|
||||
}
|
||||
|
||||
// PurgeRevisionsCommand allows removing all revisions from a storage provider.
|
||||
func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command {
|
||||
revCmd := &cobra.Command{
|
||||
Use: "purge",
|
||||
Short: "purge revisions",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
basePath, _ := cmd.Flags().GetString("basepath")
|
||||
func PurgeRevisionsCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "purge",
|
||||
Usage: "purge revisions",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "basepath",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/metadata)",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "blobstore",
|
||||
Aliases: []string{"b"},
|
||||
Usage: "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed. Note: When using decomposeds3 this needs same configuration as the storage-users service",
|
||||
Value: "decomposed",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "do not delete anything, just print what would be deleted",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "print verbose output",
|
||||
Value: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "resource-id",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "purge all revisions of this file/space. If not set, all revisions will be purged",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "glob-mechanism",
|
||||
Usage: "the glob mechanism to find all nodes. Can be 'glob', 'list' or 'workers'. 'glob' uses globbing with a single worker. 'workers' spawns multiple go routines, accelatering the command drastically but causing high cpu and ram usage. 'list' looks for references by listing directories with multiple workers. Default is 'glob'",
|
||||
Value: "glob",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
basePath := c.String("basepath")
|
||||
if basePath == "" {
|
||||
fmt.Println("basepath is required")
|
||||
return cli.ShowCommandHelp(c, "revisions")
|
||||
}
|
||||
|
||||
var (
|
||||
bs revisions.DelBlobstore
|
||||
err error
|
||||
)
|
||||
blobstoreFlag, _ := cmd.Flags().GetString("blobstore")
|
||||
switch blobstoreFlag {
|
||||
switch c.String("blobstore") {
|
||||
case "decomposeds3":
|
||||
bs, err = decomposeds3bs.New(
|
||||
cfg.StorageUsers.Drivers.DecomposedS3.Endpoint,
|
||||
@@ -77,67 +115,42 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command {
|
||||
}
|
||||
|
||||
var rid *provider.ResourceId
|
||||
resourceIDFlag, _ := cmd.Flags().GetString("resource-id")
|
||||
resid, err := storagespace.ParseID(resourceIDFlag)
|
||||
resid, err := storagespace.ParseID(c.String("resource-id"))
|
||||
if err == nil {
|
||||
rid = &resid
|
||||
}
|
||||
|
||||
mechanism, _ := cmd.Flags().GetString("glob-mechanism")
|
||||
mechanism := c.String("glob-mechanism")
|
||||
if rid.GetOpaqueId() != "" {
|
||||
mechanism = "glob"
|
||||
}
|
||||
|
||||
var posix = cfg.StorageUsers.Driver == posixDriver
|
||||
|
||||
var ch <-chan string
|
||||
switch mechanism {
|
||||
default:
|
||||
fallthrough
|
||||
case "glob":
|
||||
p := generatePath(basePath, rid, posix)
|
||||
p := generatePath(basePath, rid)
|
||||
if rid.GetOpaqueId() == "" {
|
||||
p = filepath.Join(p, "*/*/*/*/*")
|
||||
}
|
||||
ch = revisions.Glob(p)
|
||||
case "workers":
|
||||
p := generatePath(basePath, rid, posix)
|
||||
p := generatePath(basePath, rid)
|
||||
ch = revisions.GlobWorkers(p, "/*", "/*/*/*/*")
|
||||
case "list":
|
||||
p := basePath
|
||||
if !posix {
|
||||
p = filepath.Join(basePath, "spaces")
|
||||
}
|
||||
p := filepath.Join(basePath, "spaces")
|
||||
if rid != nil {
|
||||
p = generatePath(basePath, rid, posix)
|
||||
p = generatePath(basePath, rid)
|
||||
}
|
||||
ch = revisions.List(p, 10)
|
||||
}
|
||||
|
||||
flagDryRun, err := cmd.Flags().GetBool("dry-run")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flagVerbose, err := cmd.Flags().GetBool("verbose")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, blobs, revisionResults := revisions.PurgeRevisions(ch, bs, flagDryRun, flagVerbose)
|
||||
printResults(files, blobs, revisionResults, flagDryRun)
|
||||
files, blobs, revisions := revisions.PurgeRevisions(ch, bs, c.Bool("dry-run"), c.Bool("verbose"))
|
||||
printResults(files, blobs, revisions, c.Bool("dry-run"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
revCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/metadata)")
|
||||
_ = revCmd.MarkFlagRequired("basepath")
|
||||
revCmd.Flags().StringP("blobstore", "b", "decomposed", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed")
|
||||
revCmd.Flags().Bool("dry-run", true, "do not delete anything, just print what would be deleted")
|
||||
revCmd.Flags().BoolP("verbose", "v", false, "print verbose output")
|
||||
revCmd.Flags().StringP("resource-id", "r", "", "purge all revisions of this file/space. If not set, all revisions will be purged")
|
||||
revCmd.Flags().String("glob-mechanism", "glob", "the glob mechanism to find all nodes. Can be 'glob', 'list' or 'workers'. 'glob' uses globbing with a single worker. 'workers' spawns multiple go routines, accelatering the command drastically but causing high cpu and ram usage. 'list' looks for references by listing directories with multiple workers. Default is 'glob'")
|
||||
|
||||
return revCmd
|
||||
}
|
||||
|
||||
func printResults(countFiles, countBlobs, countRevisions int, dryRun bool) {
|
||||
@@ -151,44 +164,22 @@ func printResults(countFiles, countBlobs, countRevisions int, dryRun bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func generatePath(basePath string, rid *provider.ResourceId, posix bool) string {
|
||||
// decomposedfs and posix store the revisions of a node at different
|
||||
// locations on disk, so the path has to be built per driver:
|
||||
// - decomposedfs: <basePath>/spaces/<pathified spaceID>/nodes/<pathified nodeID>.REV.<ts>
|
||||
// - posix: <basePath>/<users|projects>/<spaceID>/.oc-nodes/<pathified nodeID>.REV.<ts>
|
||||
if posix {
|
||||
if rid == nil {
|
||||
return filepath.Join(basePath, "*", "*", ".oc-nodes")
|
||||
}
|
||||
|
||||
nid := lookup.Pathify(rid.GetOpaqueId(), 4, 2)
|
||||
if nid != "" {
|
||||
return filepath.Join(basePath, "*", "*", ".oc-nodes", nid+"*")
|
||||
}
|
||||
|
||||
if rid.GetSpaceId() == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(basePath, "*", rid.GetSpaceId(), ".oc-nodes")
|
||||
} else {
|
||||
// decomposedfs
|
||||
if rid == nil {
|
||||
|
||||
return filepath.Join(basePath, _nodesGlobPattern)
|
||||
}
|
||||
|
||||
sid := lookup.Pathify(rid.GetSpaceId(), 1, 2)
|
||||
if sid == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
nid := lookup.Pathify(rid.GetOpaqueId(), 4, 2)
|
||||
if nid == "" {
|
||||
return filepath.Join(basePath, "spaces", sid, "nodes")
|
||||
}
|
||||
|
||||
return filepath.Join(basePath, "spaces", sid, "nodes", nid+"*")
|
||||
func generatePath(basePath string, rid *provider.ResourceId) string {
|
||||
if rid == nil {
|
||||
return filepath.Join(basePath, _nodesGlobPattern)
|
||||
}
|
||||
|
||||
sid := lookup.Pathify(rid.GetSpaceId(), 1, 2)
|
||||
if sid == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
nid := lookup.Pathify(rid.GetOpaqueId(), 4, 2)
|
||||
if nid == "" {
|
||||
return filepath.Join(basePath, "spaces", sid, "nodes")
|
||||
}
|
||||
|
||||
return filepath.Join(basePath, "spaces", sid, "nodes", nid+"*")
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -6,45 +6,28 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/pkg/clihelper"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
oclog "github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Execute is the entry point for the opencloud command.
|
||||
func Execute() error {
|
||||
cfg := config.DefaultConfig()
|
||||
|
||||
app := clihelper.DefaultApp(&cobra.Command{
|
||||
Use: "opencloud",
|
||||
Short: "opencloud",
|
||||
app := clihelper.DefaultApp(&cli.App{
|
||||
Name: "opencloud",
|
||||
Usage: "opencloud",
|
||||
})
|
||||
|
||||
for _, commandFactory := range register.Commands {
|
||||
command := commandFactory(cfg)
|
||||
|
||||
if command.GroupID != "" && !app.ContainsGroup(command.GroupID) {
|
||||
app.AddGroup(&cobra.Group{
|
||||
ID: command.GroupID,
|
||||
Title: command.GroupID,
|
||||
})
|
||||
}
|
||||
|
||||
app.AddCommand(command)
|
||||
for _, fn := range register.Commands {
|
||||
app.Commands = append(
|
||||
app.Commands,
|
||||
fn(cfg),
|
||||
)
|
||||
}
|
||||
app.SetArgs(os.Args[1:])
|
||||
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
|
||||
return app.ExecuteContext(ctx)
|
||||
}
|
||||
|
||||
func logger(name string) zerolog.Logger {
|
||||
return oclog.NewLogger(
|
||||
oclog.Name(name),
|
||||
oclog.Level("info"),
|
||||
oclog.Pretty(true),
|
||||
oclog.Color(true)).Logger
|
||||
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
|
||||
return app.RunContext(ctx, os.Args)
|
||||
}
|
||||
@@ -6,23 +6,22 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/parser"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
func Server(cfg *config.Config) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "start a fullstack server (runtime and all services in supervised mode)",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
func Server(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "server",
|
||||
Usage: "start a fullstack server (runtime and all services in supervised mode)",
|
||||
Category: "fullstack",
|
||||
Before: func(c *cli.Context) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg, false))
|
||||
},
|
||||
GroupID: CommandGroupServer,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
Action: func(c *cli.Context) error {
|
||||
// Prefer the in-memory registry as the default when running in single-binary mode
|
||||
r := runtime.New(cfg)
|
||||
return r.Start(cmd.Context())
|
||||
return r.Start(c.Context)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/command/helper"
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
invitations "github.com/opencloud-eu/opencloud/services/invitations/pkg/command"
|
||||
nats "github.com/opencloud-eu/opencloud/services/nats/pkg/command"
|
||||
notifications "github.com/opencloud-eu/opencloud/services/notifications/pkg/command"
|
||||
ocdav "github.com/opencloud-eu/opencloud/services/ocdav/pkg/command"
|
||||
ocm "github.com/opencloud-eu/opencloud/services/ocm/pkg/command"
|
||||
ocs "github.com/opencloud-eu/opencloud/services/ocs/pkg/command"
|
||||
policies "github.com/opencloud-eu/opencloud/services/policies/pkg/command"
|
||||
@@ -48,238 +50,238 @@ import (
|
||||
web "github.com/opencloud-eu/opencloud/services/web/pkg/command"
|
||||
webdav "github.com/opencloud-eu/opencloud/services/webdav/pkg/command"
|
||||
webfinger "github.com/opencloud-eu/opencloud/services/webfinger/pkg/command"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var serviceCommands = []register.Command{
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
var svccmds = []register.Command{
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Activitylog.Service.Name, activitylog.GetCommands(cfg.Activitylog), func(c *config.Config) {
|
||||
cfg.Activitylog.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Antivirus.Service.Name, antivirus.GetCommands(cfg.Antivirus), func(c *config.Config) {
|
||||
cfg.Antivirus.Commons = cfg.Commons
|
||||
// cfg.Antivirus.Commons = cfg.Commons // antivirus needs no commons atm
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AppProvider.Service.Name, appprovider.GetCommands(cfg.AppProvider), func(c *config.Config) {
|
||||
cfg.AppProvider.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AppRegistry.Service.Name, appregistry.GetCommands(cfg.AppRegistry), func(c *config.Config) {
|
||||
cfg.AppRegistry.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Audit.Service.Name, audit.GetCommands(cfg.Audit), func(c *config.Config) {
|
||||
cfg.Audit.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AuthApp.Service.Name, authapp.GetCommands(cfg.AuthApp), func(_ *config.Config) {
|
||||
cfg.AuthApp.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AuthBasic.Service.Name, authbasic.GetCommands(cfg.AuthBasic), func(c *config.Config) {
|
||||
cfg.AuthBasic.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AuthBearer.Service.Name, authbearer.GetCommands(cfg.AuthBearer), func(c *config.Config) {
|
||||
cfg.AuthBearer.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AuthMachine.Service.Name, authmachine.GetCommands(cfg.AuthMachine), func(c *config.Config) {
|
||||
cfg.AuthMachine.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AuthService.Service.Name, authservice.GetCommands(cfg.AuthService), func(c *config.Config) {
|
||||
cfg.AuthService.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Clientlog.Service.Name, clientlog.GetCommands(cfg.Clientlog), func(c *config.Config) {
|
||||
cfg.Clientlog.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Collaboration.Service.Name, collaboration.GetCommands(cfg.Collaboration), func(c *config.Config) {
|
||||
cfg.Collaboration.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.EventHistory.Service.Name, eventhistory.GetCommands(cfg.EventHistory), func(c *config.Config) {
|
||||
cfg.EventHistory.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Frontend.Service.Name, frontend.GetCommands(cfg.Frontend), func(c *config.Config) {
|
||||
cfg.Frontend.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Gateway.Service.Name, gateway.GetCommands(cfg.Gateway), func(c *config.Config) {
|
||||
cfg.Gateway.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Graph.Service.Name, graph.GetCommands(cfg.Graph), func(c *config.Config) {
|
||||
cfg.Graph.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Groups.Service.Name, groups.GetCommands(cfg.Groups), func(c *config.Config) {
|
||||
cfg.Groups.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.IDM.Service.Name, idm.GetCommands(cfg.IDM), func(c *config.Config) {
|
||||
cfg.IDM.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.IDP.Service.Name, idp.GetCommands(cfg.IDP), func(c *config.Config) {
|
||||
cfg.IDP.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Invitations.Service.Name, invitations.GetCommands(cfg.Invitations), func(c *config.Config) {
|
||||
cfg.Invitations.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Nats.Service.Name, nats.GetCommands(cfg.Nats), func(c *config.Config) {
|
||||
cfg.Nats.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Notifications.Service.Name, notifications.GetCommands(cfg.Notifications), func(c *config.Config) {
|
||||
cfg.Notifications.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.OCDav.Service.Name, ocdav.GetCommands(cfg.OCDav), func(c *config.Config) {
|
||||
cfg.OCDav.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.OCM.Service.Name, ocm.GetCommands(cfg.OCM), func(c *config.Config) {
|
||||
cfg.OCM.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.OCS.Service.Name, ocs.GetCommands(cfg.OCS), func(c *config.Config) {
|
||||
cfg.OCS.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Policies.Service.Name, policies.GetCommands(cfg.Policies), func(c *config.Config) {
|
||||
cfg.Policies.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Postprocessing.Service.Name, postprocessing.GetCommands(cfg.Postprocessing), func(c *config.Config) {
|
||||
cfg.Postprocessing.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Proxy.Service.Name, proxy.GetCommands(cfg.Proxy), func(c *config.Config) {
|
||||
cfg.Proxy.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Search.Service.Name, search.GetCommands(cfg.Search), func(c *config.Config) {
|
||||
cfg.Search.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Settings.Service.Name, settings.GetCommands(cfg.Settings), func(c *config.Config) {
|
||||
cfg.Settings.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Sharing.Service.Name, sharing.GetCommands(cfg.Sharing), func(c *config.Config) {
|
||||
cfg.Sharing.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.SSE.Service.Name, sse.GetCommands(cfg.SSE), func(c *config.Config) {
|
||||
cfg.SSE.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.StoragePublicLink.Service.Name, storagepubliclink.GetCommands(cfg.StoragePublicLink), func(c *config.Config) {
|
||||
cfg.StoragePublicLink.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.StorageShares.Service.Name, storageshares.GetCommands(cfg.StorageShares), func(c *config.Config) {
|
||||
cfg.StorageShares.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.StorageSystem.Service.Name, storagesystem.GetCommands(cfg.StorageSystem), func(c *config.Config) {
|
||||
cfg.StorageSystem.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.StorageUsers.Service.Name, storageusers.GetCommands(cfg.StorageUsers), func(c *config.Config) {
|
||||
cfg.StorageUsers.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Thumbnails.Service.Name, thumbnails.GetCommands(cfg.Thumbnails), func(c *config.Config) {
|
||||
cfg.Thumbnails.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Userlog.Service.Name, userlog.GetCommands(cfg.Userlog), func(c *config.Config) {
|
||||
cfg.Userlog.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Users.Service.Name, users.GetCommands(cfg.Users), func(c *config.Config) {
|
||||
cfg.Users.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Web.Service.Name, web.GetCommands(cfg.Web), func(c *config.Config) {
|
||||
cfg.Web.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.WebDAV.Service.Name, webdav.GetCommands(cfg.WebDAV), func(c *config.Config) {
|
||||
cfg.WebDAV.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cobra.Command {
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.Webfinger.Service.Name, webfinger.GetCommands(cfg.Webfinger), func(c *config.Config) {
|
||||
cfg.Webfinger.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// ServiceCommand composes a cobra command from the given inputs.
|
||||
func ServiceCommand(cfg *config.Config, serviceName string, subCommands []*cobra.Command, f func(*config.Config)) *cobra.Command {
|
||||
command := &cobra.Command{
|
||||
Use: serviceName,
|
||||
Short: fmt.Sprintf("%s service commands", serviceName),
|
||||
GroupID: CommandGroupServices,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// ServiceCommand is the entry point for the all service commands.
|
||||
func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: serviceName,
|
||||
Usage: helper.SubcommandDescription(serviceName),
|
||||
Category: "services",
|
||||
Before: func(c *cli.Context) error {
|
||||
configlog.Error(parser.ParseConfig(cfg, true))
|
||||
f(cfg)
|
||||
return nil
|
||||
},
|
||||
Subcommands: subcommands,
|
||||
}
|
||||
|
||||
command.AddCommand(subCommands...)
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, c := range serviceCommands {
|
||||
for _, c := range svccmds {
|
||||
register.AddCommand(c)
|
||||
}
|
||||
}
|
||||
+104
-74
@@ -1,42 +1,33 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"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"
|
||||
mregistry "github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
sharing "github.com/opencloud-eu/opencloud/services/sharing/pkg/config"
|
||||
sharingparser "github.com/opencloud-eu/opencloud/services/sharing/pkg/config/parser"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/share/manager/registry"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
"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"
|
||||
oclog "github.com/opencloud-eu/opencloud/pkg/log"
|
||||
mregistry "github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
sharing "github.com/opencloud-eu/opencloud/services/sharing/pkg/config"
|
||||
sharingparser "github.com/opencloud-eu/opencloud/services/sharing/pkg/config/parser"
|
||||
)
|
||||
|
||||
// need to be discussed, for now I will let it here
|
||||
//
|
||||
// Problem:
|
||||
// in reva on CleanupStaleShares call there is migrations invokation, which at leat in tests takes some time,
|
||||
// reva code was modified to wait until migrations are done, to prevent cases when migrations are stuck and the
|
||||
// this executions is not returned this timeout is needed
|
||||
const cleanupTimeout = 1 * time.Minute
|
||||
|
||||
// SharesCommand is the entrypoint for the groups command.
|
||||
func SharesCommand(cfg *config.Config) *cobra.Command {
|
||||
sharesCmd := &cobra.Command{
|
||||
Use: "shares",
|
||||
Short: `cli tools to manage entries in the share manager.`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
func SharesCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "shares",
|
||||
Usage: `cli tools to manage entries in the share manager.`,
|
||||
Category: "maintenance",
|
||||
Before: func(c *cli.Context) error {
|
||||
// Parse base config
|
||||
if err := parser.ParseConfig(cfg, true); err != nil {
|
||||
return configlog.ReturnError(err)
|
||||
@@ -46,21 +37,37 @@ func SharesCommand(cfg *config.Config) *cobra.Command {
|
||||
cfg.Sharing.Commons = cfg.Commons
|
||||
return configlog.ReturnError(sharingparser.ParseConfig(cfg.Sharing))
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
cleanupCmd(cfg),
|
||||
},
|
||||
}
|
||||
sharesCmd.AddCommand(cleanupCmd(cfg))
|
||||
|
||||
return sharesCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
register.AddCommand(SharesCommand)
|
||||
}
|
||||
|
||||
func cleanupCmd(cfg *config.Config) *cobra.Command {
|
||||
cleanCmd := &cobra.Command{
|
||||
Use: "cleanup",
|
||||
Short: `clean up stale entries in the share manager.`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
func cleanupCmd(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "cleanup",
|
||||
Usage: `clean up stale entries in the share manager.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "service-account-id",
|
||||
Value: "",
|
||||
Usage: "Name of the service account to use for the cleanup",
|
||||
EnvVars: []string{"OC_SERVICE_ACCOUNT_ID"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-account-secret",
|
||||
Value: "",
|
||||
Usage: "Secret for the service account",
|
||||
EnvVars: []string{"OC_SERVICE_ACCOUNT_SECRET"},
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
// Parse base config
|
||||
if err := parser.ParseConfig(cfg, true); err != nil {
|
||||
return configlog.ReturnError(err)
|
||||
@@ -70,40 +77,25 @@ func cleanupCmd(cfg *config.Config) *cobra.Command {
|
||||
cfg.Sharing.Commons = cfg.Commons
|
||||
return configlog.ReturnError(sharingparser.ParseConfig(cfg.Sharing))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cleanup(cmd, cfg)
|
||||
Action: func(c *cli.Context) error {
|
||||
return cleanup(c, cfg)
|
||||
},
|
||||
}
|
||||
cleanCmd.Flags().String("service-account-id", "", "Name of the service account to use for the cleanup")
|
||||
_ = cleanCmd.MarkFlagRequired("service-account-id")
|
||||
_ = viper.BindEnv("service-account-id", "OC_SERVICE_ACCOUNT_ID")
|
||||
_ = viper.BindPFlag("service-account-id", cleanCmd.Flags().Lookup("service-account-id"))
|
||||
|
||||
cleanCmd.Flags().String("service-account-secret", "", "Secret for the service account")
|
||||
_ = cleanCmd.MarkFlagRequired("service-account-secret")
|
||||
_ = viper.BindEnv("service-account-secret", "OC_SERVICE_ACCOUNT_SECRET")
|
||||
_ = viper.BindPFlag("service-account-secret", cleanCmd.Flags().Lookup("service-account-secret"))
|
||||
|
||||
return cleanCmd
|
||||
}
|
||||
|
||||
func cleanup(_ *cobra.Command, cfg *config.Config) error {
|
||||
func cleanup(c *cli.Context, cfg *config.Config) error {
|
||||
driver := cfg.Sharing.UserSharingDriver
|
||||
// cleanup is only implemented for the jsoncs3 share manager
|
||||
if driver != "jsoncs3" {
|
||||
return configlog.ReturnError(errors.New("cleanup is only implemented for the jsoncs3 share manager"))
|
||||
}
|
||||
|
||||
l := logger("migrate")
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
|
||||
rcfg := revaShareConfig(cfg.Sharing)
|
||||
f, ok := registry.NewFuncs[driver]
|
||||
if !ok {
|
||||
return configlog.ReturnError(errors.New("Unknown share manager type '" + driver + "'"))
|
||||
}
|
||||
mgr, err := f(rcfg[driver].(map[string]any), &l)
|
||||
mgr, err := f(rcfg[driver].(map[string]interface{}))
|
||||
if err != nil {
|
||||
return configlog.ReturnError(err)
|
||||
}
|
||||
@@ -122,30 +114,28 @@ func cleanup(_ *cobra.Command, cfg *config.Config) error {
|
||||
return configlog.ReturnError(err)
|
||||
}
|
||||
|
||||
serviceAccountIDFlag := viper.GetString("service-account-id")
|
||||
serviceAccountSecretFlag := viper.GetString("service-account-secret")
|
||||
serviceUserCtx, err := utils.GetServiceUserContext(serviceAccountIDFlag, client, serviceAccountSecretFlag)
|
||||
serviceUserCtx, err := utils.GetServiceUserContext(c.String("service-account-id"), client, c.String("service-account-secret"))
|
||||
if err != nil {
|
||||
return configlog.ReturnError(err)
|
||||
}
|
||||
|
||||
l := logger()
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
serviceUserCtx = l.WithContext(serviceUserCtx)
|
||||
|
||||
cleanupCtx, cancel := context.WithTimeout(serviceUserCtx, cleanupTimeout)
|
||||
defer cancel()
|
||||
if err := mgr.(*jsoncs3.Manager).CleanupStaleShares(cleanupCtx); err != nil {
|
||||
return configlog.ReturnError(err)
|
||||
}
|
||||
mgr.(*jsoncs3.Manager).CleanupStaleShares(serviceUserCtx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func revaShareConfig(cfg *sharing.Config) map[string]any {
|
||||
return map[string]any{
|
||||
"json": map[string]any{
|
||||
func revaShareConfig(cfg *sharing.Config) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"json": map[string]interface{}{
|
||||
"file": cfg.UserSharingDrivers.JSON.File,
|
||||
"gateway_addr": cfg.Reva.Address,
|
||||
},
|
||||
"sql": map[string]any{ // cernbox sql
|
||||
"sql": map[string]interface{}{ // cernbox sql
|
||||
"db_username": cfg.UserSharingDrivers.SQL.DBUsername,
|
||||
"db_password": cfg.UserSharingDrivers.SQL.DBPassword,
|
||||
"db_host": cfg.UserSharingDrivers.SQL.DBHost,
|
||||
@@ -155,7 +145,7 @@ func revaShareConfig(cfg *sharing.Config) map[string]any {
|
||||
"enable_expired_shares_cleanup": cfg.UserSharingDrivers.SQL.EnableExpiredSharesCleanup,
|
||||
"janitor_run_interval": cfg.UserSharingDrivers.SQL.JanitorRunInterval,
|
||||
},
|
||||
"owncloudsql": map[string]any{
|
||||
"owncloudsql": map[string]interface{}{
|
||||
"gateway_addr": cfg.Reva.Address,
|
||||
"storage_mount_id": cfg.UserSharingDrivers.OwnCloudSQL.UserStorageMountID,
|
||||
"db_username": cfg.UserSharingDrivers.OwnCloudSQL.DBUsername,
|
||||
@@ -164,21 +154,61 @@ func revaShareConfig(cfg *sharing.Config) map[string]any {
|
||||
"db_port": cfg.UserSharingDrivers.OwnCloudSQL.DBPort,
|
||||
"db_name": cfg.UserSharingDrivers.OwnCloudSQL.DBName,
|
||||
},
|
||||
"cs3": map[string]any{
|
||||
"cs3": map[string]interface{}{
|
||||
"gateway_addr": cfg.UserSharingDrivers.CS3.ProviderAddr,
|
||||
"provider_addr": cfg.UserSharingDrivers.CS3.ProviderAddr,
|
||||
"service_user_id": cfg.UserSharingDrivers.CS3.SystemUserID,
|
||||
"service_user_idp": cfg.UserSharingDrivers.CS3.SystemUserIDP,
|
||||
"machine_auth_apikey": cfg.UserSharingDrivers.CS3.SystemUserAPIKey,
|
||||
},
|
||||
"jsoncs3": map[string]any{
|
||||
"gateway_addr": cfg.Reva.Address,
|
||||
"provider_addr": cfg.UserSharingDrivers.JSONCS3.ProviderAddr,
|
||||
"system_user_id": cfg.UserSharingDrivers.JSONCS3.SystemUserID,
|
||||
"system_user_idp": cfg.UserSharingDrivers.JSONCS3.SystemUserIDP,
|
||||
"machine_auth_apikey": cfg.UserSharingDrivers.JSONCS3.SystemUserAPIKey,
|
||||
"service_account_id": cfg.ServiceAccount.ServiceAccountID,
|
||||
"service_account_secret": cfg.ServiceAccount.ServiceAccountSecret,
|
||||
"jsoncs3": map[string]interface{}{
|
||||
"gateway_addr": cfg.Reva.Address,
|
||||
"provider_addr": cfg.UserSharingDrivers.JSONCS3.ProviderAddr,
|
||||
"service_user_id": cfg.UserSharingDrivers.JSONCS3.SystemUserID,
|
||||
"service_user_idp": cfg.UserSharingDrivers.JSONCS3.SystemUserIDP,
|
||||
"machine_auth_apikey": cfg.UserSharingDrivers.JSONCS3.SystemUserAPIKey,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func revaPublicShareConfig(cfg *sharing.Config) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"json": map[string]interface{}{
|
||||
"file": cfg.PublicSharingDrivers.JSON.File,
|
||||
"gateway_addr": cfg.Reva.Address,
|
||||
},
|
||||
"jsoncs3": map[string]interface{}{
|
||||
"gateway_addr": cfg.Reva.Address,
|
||||
"provider_addr": cfg.PublicSharingDrivers.JSONCS3.ProviderAddr,
|
||||
"service_user_id": cfg.PublicSharingDrivers.JSONCS3.SystemUserID,
|
||||
"service_user_idp": cfg.PublicSharingDrivers.JSONCS3.SystemUserIDP,
|
||||
"machine_auth_apikey": cfg.PublicSharingDrivers.JSONCS3.SystemUserAPIKey,
|
||||
},
|
||||
"sql": map[string]interface{}{
|
||||
"db_username": cfg.PublicSharingDrivers.SQL.DBUsername,
|
||||
"db_password": cfg.PublicSharingDrivers.SQL.DBPassword,
|
||||
"db_host": cfg.PublicSharingDrivers.SQL.DBHost,
|
||||
"db_port": cfg.PublicSharingDrivers.SQL.DBPort,
|
||||
"db_name": cfg.PublicSharingDrivers.SQL.DBName,
|
||||
"password_hash_cost": cfg.PublicSharingDrivers.SQL.PasswordHashCost,
|
||||
"enable_expired_shares_cleanup": cfg.PublicSharingDrivers.SQL.EnableExpiredSharesCleanup,
|
||||
"janitor_run_interval": cfg.PublicSharingDrivers.SQL.JanitorRunInterval,
|
||||
},
|
||||
"cs3": map[string]interface{}{
|
||||
"gateway_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr,
|
||||
"provider_addr": cfg.PublicSharingDrivers.CS3.ProviderAddr,
|
||||
"service_user_id": cfg.PublicSharingDrivers.CS3.SystemUserID,
|
||||
"service_user_idp": cfg.PublicSharingDrivers.CS3.SystemUserIDP,
|
||||
"machine_auth_apikey": cfg.PublicSharingDrivers.CS3.SystemUserAPIKey,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func logger() *zerolog.Logger {
|
||||
log := oclog.NewLogger(
|
||||
oclog.Name("migrate"),
|
||||
oclog.Level("info"),
|
||||
oclog.Pretty(true),
|
||||
oclog.Color(true)).Logger
|
||||
return &log
|
||||
}
|
||||
@@ -3,41 +3,57 @@ package command
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/trash"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TrashCommand(cfg *config.Config) *cobra.Command {
|
||||
trashCmd := &cobra.Command{
|
||||
Use: "trash",
|
||||
Short: "OpenCloud trash functionality",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
func TrashCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "trash",
|
||||
Usage: "OpenCloud trash functionality",
|
||||
Subcommands: []*cli.Command{
|
||||
TrashPurgeEmptyDirsCommand(cfg),
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg, true))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
Action: func(_ *cli.Context) error {
|
||||
fmt.Println("Read the docs")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
trashCmd.AddCommand(TrashPurgeEmptyDirsCommand(cfg))
|
||||
|
||||
return trashCmd
|
||||
}
|
||||
|
||||
func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command {
|
||||
trashPurgeCmd := &cobra.Command{
|
||||
Use: "purge-empty-dirs",
|
||||
Short: "purge empty directories",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
basePath, _ := cmd.Flags().GetString("basepath")
|
||||
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
||||
posix := cfg.StorageUsers.Driver == posixDriver
|
||||
if err := trash.PurgeTrashEmptyPaths(basePath, dryRun, posix); err != nil {
|
||||
func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "purge-empty-dirs",
|
||||
Usage: "purge empty directories",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "basepath",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)",
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "do not delete anything, just print what would be deleted",
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
basePath := c.String("basepath")
|
||||
if basePath == "" {
|
||||
fmt.Println("basepath is required")
|
||||
return cli.ShowCommandHelp(c, "trash")
|
||||
}
|
||||
|
||||
if err := trash.PurgeTrashEmptyPaths(basePath, c.Bool("dry-run")); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
@@ -45,12 +61,6 @@ func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
trashPurgeCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)")
|
||||
_ = trashPurgeCmd.MarkFlagRequired("basepath")
|
||||
|
||||
trashPurgeCmd.Flags().Bool("dry-run", true, "do not delete anything, just print what would be deleted")
|
||||
|
||||
return trashPurgeCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -4,16 +4,15 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"github.com/urfave/cli/v2"
|
||||
mreg "go-micro.dev/v4/registry"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
mreg "go-micro.dev/v4/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,18 +20,22 @@ const (
|
||||
)
|
||||
|
||||
// VersionCommand is the entrypoint for the version command.
|
||||
func VersionCommand(cfg *config.Config) *cobra.Command {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "print the version of this binary and all running service instances",
|
||||
GroupID: CommandGroupServer,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
func VersionCommand(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "version",
|
||||
Usage: "print the version of this binary and all running service instances",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: _skipServiceListingFlagName,
|
||||
Usage: "skip service listing",
|
||||
},
|
||||
},
|
||||
Category: "info",
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("Version: " + version.GetString())
|
||||
fmt.Printf("Edition: %s\n", version.Edition)
|
||||
fmt.Printf("Compiled: %s\n", version.Compiled())
|
||||
|
||||
skipServiceListing, _ := cmd.Flags().GetBool(_skipServiceListingFlagName)
|
||||
if skipServiceListing {
|
||||
if c.Bool(_skipServiceListingFlagName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -71,8 +74,6 @@ func VersionCommand(cfg *config.Config) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
versionCmd.Flags().Bool(_skipServiceListingFlagName, false, "skip service listing")
|
||||
return versionCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -47,22 +47,15 @@ func backupOpenCloudConfigFile(configPath string) (string, error) {
|
||||
}
|
||||
|
||||
// printBanner prints the generated opencloud config banner.
|
||||
func printBanner(targetPath, ocAdminServicePassword string, adminPWgenerated bool, targetBackupConfig string) {
|
||||
func printBanner(targetPath, ocAdminServicePassword, targetBackupConfig string) {
|
||||
fmt.Printf(
|
||||
"\n=========================================\n"+
|
||||
" generated OpenCloud Config\n"+
|
||||
"=========================================\n"+
|
||||
" configpath : %s\n",
|
||||
targetPath,
|
||||
)
|
||||
if adminPWgenerated {
|
||||
fmt.Printf(" user : admin\n"+
|
||||
" password : %s\n",
|
||||
ocAdminServicePassword,
|
||||
)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
" configpath : %s\n"+
|
||||
" user : admin\n"+
|
||||
" password : %s\n\n",
|
||||
targetPath, ocAdminServicePassword)
|
||||
if targetBackupConfig != "" {
|
||||
fmt.Printf("\n=========================================\n"+
|
||||
"An older config file has been backed up to\n %s\n\n",
|
||||
@@ -71,15 +64,13 @@ func printBanner(targetPath, ocAdminServicePassword string, adminPWgenerated boo
|
||||
}
|
||||
|
||||
// writeConfig writes the config to the target path and prints a banner
|
||||
func writeConfig(configPath, ocAdminServicePassword, targetBackupConfig string, yamlOutput []byte, adminPWgenerated, quiet bool) error {
|
||||
func writeConfig(configPath, ocAdminServicePassword, targetBackupConfig string, yamlOutput []byte) error {
|
||||
targetPath := path.Join(configPath, configFilename)
|
||||
err := os.WriteFile(targetPath, yamlOutput, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !quiet {
|
||||
printBanner(targetPath, ocAdminServicePassword, adminPWgenerated, targetBackupConfig)
|
||||
}
|
||||
printBanner(targetPath, ocAdminServicePassword, targetBackupConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ var (
|
||||
)
|
||||
|
||||
// CreateConfig creates a config file with random passwords at configPath
|
||||
func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword string, quiet bool) error {
|
||||
func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword string) error {
|
||||
if diff && forceOverwrite {
|
||||
return fmt.Errorf("diff and force-overwrite flags are mutually exclusive")
|
||||
}
|
||||
@@ -69,7 +69,6 @@ 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 {
|
||||
@@ -124,7 +123,6 @@ 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)
|
||||
@@ -274,9 +272,6 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword
|
||||
Activitylog: Activitylog{
|
||||
ServiceAccount: serviceAccount,
|
||||
},
|
||||
Sharing: Sharing{
|
||||
ServiceAccount: serviceAccount,
|
||||
},
|
||||
}
|
||||
|
||||
if insecure {
|
||||
@@ -286,7 +281,6 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword
|
||||
cfg.Collaboration.App.Insecure = true
|
||||
cfg.Frontend.AppHandler = _insecureService
|
||||
cfg.Frontend.Archiver = _insecureService
|
||||
cfg.Frontend.OCDav = _insecureService
|
||||
cfg.Graph.Spaces = _insecureService
|
||||
cfg.Graph.Events = _insecureEvents
|
||||
cfg.Notifications.Notifications.Events = _insecureEvents
|
||||
@@ -295,6 +289,7 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword
|
||||
cfg.Sharing.Events = _insecureEvents
|
||||
cfg.StorageUsers.Events = _insecureEvents
|
||||
cfg.Nats.Nats.TLSSkipVerifyClientCert = true
|
||||
cfg.Ocdav = _insecureService
|
||||
cfg.Proxy = ProxyService{
|
||||
InsecureBackends: true,
|
||||
OIDC: InsecureProxyOIDC{
|
||||
@@ -313,5 +308,5 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword
|
||||
if diff {
|
||||
return writePatch(configPath, yamlOutput)
|
||||
}
|
||||
return writeConfig(configPath, ocAdminServicePassword, targetBackupConfig, yamlOutput, adminPasswdwordGenerated, quiet)
|
||||
return writeConfig(configPath, ocAdminServicePassword, targetBackupConfig, yamlOutput)
|
||||
}
|
||||
@@ -32,6 +32,7 @@ type OpenCloudConfig struct {
|
||||
AuthBearer AuthbearerService `yaml:"auth_bearer"`
|
||||
Users UsersAndGroupsService `yaml:"users"`
|
||||
Groups UsersAndGroupsService `yaml:"groups"`
|
||||
Ocdav InsecureService `yaml:"ocdav"`
|
||||
Ocm OcmService `yaml:"ocm"`
|
||||
Thumbnails ThumbnailService `yaml:"thumbnails"`
|
||||
Search Search `yaml:"search"`
|
||||
@@ -104,7 +105,6 @@ type FrontendService struct {
|
||||
AppHandler InsecureService `yaml:"app_handler"`
|
||||
Archiver InsecureService
|
||||
ServiceAccount ServiceAccount `yaml:"service_account"`
|
||||
OCDav InsecureService
|
||||
}
|
||||
|
||||
// Gateway is the configuration for the gateway
|
||||
@@ -204,8 +204,7 @@ type SettingsService struct {
|
||||
|
||||
// Sharing is the configuration for the sharing service
|
||||
type Sharing struct {
|
||||
Events Events
|
||||
ServiceAccount ServiceAccount `yaml:"service_account"`
|
||||
Events Events
|
||||
}
|
||||
|
||||
// StorageRegistry is the configuration for the storage registry
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package register
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// Commands define the slice of commands.
|
||||
Commands []Command
|
||||
// Commands defines the slice of commands.
|
||||
Commands = []Command{}
|
||||
)
|
||||
|
||||
// Command defines the register command.
|
||||
type Command func(*config.Config) *cobra.Command
|
||||
type Command func(*config.Config) *cli.Command
|
||||
|
||||
// AddCommand appends a command to Commands.
|
||||
func AddCommand(cmd Command) {
|
||||
|
||||
@@ -227,11 +227,13 @@ func listFolder(path string, ch chan<- string, workers chan struct{}) error {
|
||||
|
||||
for _, child := range children {
|
||||
if child.IsDir() {
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := listFolder(filepath.Join(path, child.Name()), ch, workers); err != nil {
|
||||
fmt.Println("error listing", path, err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
if _versionRegex.MatchString(child.Name()) {
|
||||
|
||||
@@ -2,12 +2,14 @@ package runtime
|
||||
|
||||
import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Options is a runtime option
|
||||
type Options struct {
|
||||
Services []string
|
||||
Logger log.Logger
|
||||
Context *cli.Context
|
||||
}
|
||||
|
||||
// Option undocumented
|
||||
@@ -19,3 +21,10 @@ func Services(s []string) Option {
|
||||
o.Services = append(o.Services, s...)
|
||||
}
|
||||
}
|
||||
|
||||
// Context option
|
||||
func Context(c *cli.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = c
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
invitations "github.com/opencloud-eu/opencloud/services/invitations/pkg/command"
|
||||
nats "github.com/opencloud-eu/opencloud/services/nats/pkg/command"
|
||||
notifications "github.com/opencloud-eu/opencloud/services/notifications/pkg/command"
|
||||
ocdav "github.com/opencloud-eu/opencloud/services/ocdav/pkg/command"
|
||||
ocm "github.com/opencloud-eu/opencloud/services/ocm/pkg/command"
|
||||
ocs "github.com/opencloud-eu/opencloud/services/ocs/pkg/command"
|
||||
policies "github.com/opencloud-eu/opencloud/services/policies/pkg/command"
|
||||
@@ -123,7 +124,7 @@ func NewService(ctx context.Context, options ...Option) (*Service, error) {
|
||||
if s.Services[priority] == nil {
|
||||
s.Services[priority] = make(serviceFuncMap)
|
||||
}
|
||||
s.Services[priority][name] = NewSutureServiceBuilder(name, exec)
|
||||
s.Services[priority][name] = NewSutureServiceBuilder(exec)
|
||||
}
|
||||
|
||||
// nats is in priority group 0. It needs to start before all other services
|
||||
@@ -203,6 +204,11 @@ func NewService(ctx context.Context, options ...Option) (*Service, error) {
|
||||
cfg.IDM.Commons = cfg.Commons
|
||||
return idm.Execute(cfg.IDM)
|
||||
})
|
||||
reg(3, opts.Config.OCDav.Service.Name, func(ctx context.Context, cfg *occfg.Config) error {
|
||||
cfg.OCDav.Context = ctx
|
||||
cfg.OCDav.Commons = cfg.Commons
|
||||
return ocdav.Execute(cfg.OCDav)
|
||||
})
|
||||
reg(3, opts.Config.OCS.Service.Name, func(ctx context.Context, cfg *occfg.Config) error {
|
||||
cfg.OCS.Context = ctx
|
||||
cfg.OCS.Commons = cfg.Commons
|
||||
@@ -310,11 +316,11 @@ func NewService(ctx context.Context, options ...Option) (*Service, error) {
|
||||
|
||||
// populate optional services
|
||||
areg := func(name string, exec func(context.Context, *occfg.Config) error) {
|
||||
s.Additional[name] = NewSutureServiceBuilder(name, exec)
|
||||
s.Additional[name] = NewSutureServiceBuilder(exec)
|
||||
}
|
||||
areg(opts.Config.Antivirus.Service.Name, func(ctx context.Context, cfg *occfg.Config) error {
|
||||
cfg.Antivirus.Context = ctx
|
||||
cfg.Antivirus.Commons = cfg.Commons
|
||||
// cfg.Antivirus.Commons = cfg.Commons // antivirus holds no Commons atm
|
||||
return antivirus.Execute(cfg.Antivirus)
|
||||
})
|
||||
areg(opts.Config.Audit.Service.Name, func(ctx context.Context, cfg *occfg.Config) error {
|
||||
@@ -389,16 +395,9 @@ func Start(ctx context.Context, o ...Option) error {
|
||||
if ev.Restarting {
|
||||
l = s.Log.Error()
|
||||
}
|
||||
l = l.Str("event", e.String()).Str("service", ev.ServiceName).Str("supervisor", ev.SupervisorName).
|
||||
Bool("restarting", ev.Restarting).Float64("failures", ev.CurrentFailures).Float64("threshold", ev.FailureThreshold)
|
||||
// ev.Err is an interface{}: marshaling an error yields {} because
|
||||
// its fields are unexported, so the message has to go through Err
|
||||
if err, ok := ev.Err.(error); ok {
|
||||
l = l.Err(err)
|
||||
} else {
|
||||
l = l.Interface("error", ev.Err)
|
||||
}
|
||||
l.Msg("service terminated")
|
||||
l.Str("event", e.String()).Str("service", ev.ServiceName).Str("supervisor", ev.SupervisorName).
|
||||
Bool("restarting", ev.Restarting).Float64("failures", ev.CurrentFailures).Float64("threshold", ev.FailureThreshold).
|
||||
Interface("error", ev.Err).Msg("service terminated")
|
||||
case suture.EventBackoff:
|
||||
s.Log.Warn().Str("event", e.String()).Str("supervisor", ev.SupervisorName).Msg("service backoff")
|
||||
case suture.EventResume:
|
||||
@@ -435,9 +434,12 @@ func Start(ctx context.Context, o ...Option) error {
|
||||
// prepare the set of services to run
|
||||
s.generateRunSet(s.cfg)
|
||||
|
||||
// We need to control the order in which services are started and shut down,
|
||||
// so we need a backgroud context that will outlive the service execution.
|
||||
go s.Supervisor.ServeBackground(context.Background())
|
||||
// There are reasons not to do this, but we have race conditions ourselves. Until we resolve them, mind the following disclaimer:
|
||||
// Calling ServeBackground will CORRECTLY start the supervisor running in a new goroutine. It is risky to directly run
|
||||
// go supervisor.Serve()
|
||||
// because that will briefly create a race condition as it starts up, if you try to .Add() services immediately afterward.
|
||||
// https://pkg.go.dev/github.com/thejerf/suture/v4@v4.0.0#Supervisor
|
||||
go s.Supervisor.ServeBackground(ctx)
|
||||
|
||||
for i, service := range s.Services {
|
||||
scheduleServiceTokens(s, service)
|
||||
@@ -530,7 +532,9 @@ func trapShutdownCtx(s *Service, srv *http.Server, ctx context.Context) error {
|
||||
s.Log.Info().Msg("starting graceful shutdown")
|
||||
start := time.Now()
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), _defaultShutdownTimeoutDuration)
|
||||
defer cancel()
|
||||
s.Log.Debug().Msg("starting runtime listener shutdown")
|
||||
@@ -539,7 +543,7 @@ func trapShutdownCtx(s *Service, srv *http.Server, ctx context.Context) error {
|
||||
return
|
||||
}
|
||||
s.Log.Debug().Msg("runtime listener shutdown done")
|
||||
})
|
||||
}()
|
||||
|
||||
// shutdown services in the order defined in the config
|
||||
// any services not listed will be shutdown in parallel afterwards
|
||||
|
||||
@@ -10,17 +10,15 @@ import (
|
||||
// SutureService allows for the settings command to be embedded and supervised by a suture supervisor tree.
|
||||
type SutureService struct {
|
||||
exec func(ctx context.Context) error
|
||||
name string
|
||||
}
|
||||
|
||||
// NewSutureServiceBuilder creates a new suture service
|
||||
func NewSutureServiceBuilder(name string, f func(context.Context, *occfg.Config) error) func(*occfg.Config) suture.Service {
|
||||
func NewSutureServiceBuilder(f func(context.Context, *occfg.Config) error) func(*occfg.Config) suture.Service {
|
||||
return func(cfg *occfg.Config) suture.Service {
|
||||
return SutureService{
|
||||
exec: func(ctx context.Context) error {
|
||||
return f(ctx, cfg)
|
||||
},
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,8 +27,3 @@ func NewSutureServiceBuilder(name string, f func(context.Context, *occfg.Config)
|
||||
func (s SutureService) Serve(ctx context.Context) error {
|
||||
return s.exec(ctx)
|
||||
}
|
||||
|
||||
// String to fullfil fmt.Stringer interface, used to log the service name
|
||||
func (s SutureService) String() string {
|
||||
return s.name
|
||||
}
|
||||
@@ -10,23 +10,12 @@ import (
|
||||
const (
|
||||
// _trashGlobPattern is the glob pattern to find all trash items
|
||||
_trashGlobPattern = "spaces/*/*/trash/*/*/*/*"
|
||||
// _trashRootPattern is the glob pattern of the trash container root
|
||||
_trashRootPattern = "spaces/*/*/trash"
|
||||
// _posixTrashGlobPattern is the glob pattern to find all trash items on posix
|
||||
_posixTrashGlobPattern = "*/*/.Trash/files/*"
|
||||
// _posixTrashRootPattern is the glob pattern of the trash container root on posix
|
||||
_posixTrashRootPattern = "*/*/.Trash/files"
|
||||
)
|
||||
|
||||
// PurgeTrashEmptyPaths purges empty paths in the trash
|
||||
func PurgeTrashEmptyPaths(p string, dryRun bool, posix bool) error {
|
||||
pattern := _trashGlobPattern
|
||||
if posix {
|
||||
pattern = _posixTrashGlobPattern
|
||||
}
|
||||
|
||||
func PurgeTrashEmptyPaths(p string, dryRun bool) error {
|
||||
// we have all trash nodes in all spaces now
|
||||
dirs, err := filepath.Glob(filepath.Join(p, pattern))
|
||||
dirs, err := filepath.Glob(filepath.Join(p, _trashGlobPattern))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -36,26 +25,15 @@ func PurgeTrashEmptyPaths(p string, dryRun bool, posix bool) error {
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
if err := removeEmptyFolder(d, dryRun, posix, p); err != nil {
|
||||
if err := removeEmptyFolder(d, dryRun); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeEmptyFolder(path string, dryRun bool, posix bool, basePath string) error {
|
||||
|
||||
func removeEmptyFolder(path string, dryRun bool) error {
|
||||
if dryRun {
|
||||
if posix {
|
||||
// on posix the ".trashitem" entries are the actual data and can be
|
||||
// files, which are skipped, so we need to check here if the path
|
||||
// is the actual dir, the same check for real removal part
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil || !fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -65,14 +43,6 @@ func removeEmptyFolder(path string, dryRun bool, posix bool, basePath string) er
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if posix {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil || !fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Remove(path); err != nil {
|
||||
// we do not really care about the error here
|
||||
// if the folder is not empty we will get an error,
|
||||
@@ -80,17 +50,8 @@ func removeEmptyFolder(path string, dryRun bool, posix bool, basePath string) er
|
||||
return nil
|
||||
}
|
||||
nd := filepath.Dir(path)
|
||||
if isTrashRoot(nd, basePath, posix) {
|
||||
if filepath.Base(nd) == "trash" {
|
||||
return nil
|
||||
}
|
||||
return removeEmptyFolder(nd, dryRun, posix, basePath)
|
||||
}
|
||||
|
||||
func isTrashRoot(path, basePath string, posix bool) bool {
|
||||
rootPattern := _trashRootPattern
|
||||
if posix {
|
||||
rootPattern = _posixTrashRootPattern
|
||||
}
|
||||
matched, _ := filepath.Match(filepath.Join(basePath, rootPattern), path)
|
||||
return matched
|
||||
return removeEmptyFolder(nd, dryRun)
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package trash
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/test-go/testify/require"
|
||||
)
|
||||
|
||||
func TestIsTrashRootDecomposed(t *testing.T) {
|
||||
storageRoot := "test_temp_" + t.Name()
|
||||
defer os.RemoveAll(storageRoot)
|
||||
|
||||
require.True(t, isTrashRoot(storageRoot+"/spaces/id/id/trash", storageRoot, false))
|
||||
require.False(t, isTrashRoot(storageRoot+"/spaces/id/id/trash/s1/s2/s3/node", storageRoot, false))
|
||||
require.False(t, isTrashRoot(storageRoot+"/spaces/id/id/trash/s1/s2/trash/node", storageRoot, false))
|
||||
}
|
||||
|
||||
func TestIsTrashRootPosix(t *testing.T) {
|
||||
storageRoot := "test_temp_" + t.Name()
|
||||
defer os.RemoveAll(storageRoot)
|
||||
|
||||
require.True(t, isTrashRoot(storageRoot+"/users/alice/.Trash/files", storageRoot, true))
|
||||
require.False(t, isTrashRoot(storageRoot+"/users/alice/.Trash/files/item.trashitem", storageRoot, true))
|
||||
require.False(t, isTrashRoot(storageRoot+"/users/alice/.Trash/files/item.trashitem/files", storageRoot, true))
|
||||
}
|
||||
|
||||
func TestRemoveEmptyFolderPosix(t *testing.T) {
|
||||
storageRoot := "test_temp_" + t.Name()
|
||||
base := storageRoot + "/users/alice/.Trash/files"
|
||||
defer os.RemoveAll(storageRoot)
|
||||
|
||||
emptyChain := base + "/empty.trashitem/sub/subsub"
|
||||
require.NoError(t, os.MkdirAll(emptyChain, os.ModePerm))
|
||||
|
||||
nonEmpty := base + "/keep.trashitem"
|
||||
require.NoError(t, os.MkdirAll(nonEmpty, os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(nonEmpty+"/file.txt", []byte("some text"), os.ModePerm))
|
||||
|
||||
require.NoError(t, removeEmptyFolder(emptyChain, false, true, storageRoot))
|
||||
|
||||
assertNoDirExists(t, emptyChain)
|
||||
assertNoDirExists(t, base+"/empty.trashitem")
|
||||
assertDirExists(t, base)
|
||||
assertDirExists(t, nonEmpty)
|
||||
}
|
||||
|
||||
func TestRemoveEmptyFolderPosixUserDirNamedFiles(t *testing.T) {
|
||||
storageRoot := "test_temp_" + t.Name()
|
||||
base := storageRoot + "/users/alice/.Trash/files"
|
||||
defer os.RemoveAll(storageRoot)
|
||||
|
||||
nestedFilesDir := base + "/folder.trashitem/files/files"
|
||||
require.NoError(t, os.MkdirAll(nestedFilesDir, os.ModePerm))
|
||||
|
||||
require.NoError(t, removeEmptyFolder(nestedFilesDir, false, true, storageRoot))
|
||||
|
||||
assertNoDirExists(t, nestedFilesDir)
|
||||
assertNoDirExists(t, base+"/folder.trashitem/files")
|
||||
assertNoDirExists(t, base+"/folder.trashitem")
|
||||
assertDirExists(t, base)
|
||||
}
|
||||
|
||||
func TestRemoveEmptyFolderDecomposed(t *testing.T) {
|
||||
storageRoot := "test_temp_" + t.Name()
|
||||
base := storageRoot + "/spaces/id/id/trash"
|
||||
defer os.RemoveAll(storageRoot)
|
||||
|
||||
emptyChain := base + "/s1/s2/s3/node"
|
||||
require.NoError(t, os.MkdirAll(emptyChain, os.ModePerm))
|
||||
|
||||
require.NoError(t, removeEmptyFolder(emptyChain, false, false, storageRoot))
|
||||
|
||||
assertNoDirExists(t, emptyChain)
|
||||
assertNoDirExists(t, base+"/s1/s2/s3")
|
||||
assertNoDirExists(t, base+"/s1/s2")
|
||||
assertNoDirExists(t, base+"/s1")
|
||||
assertDirExists(t, base)
|
||||
}
|
||||
|
||||
func TestRemoveEmptyFolderDecomposedUserDirNamedTrash(t *testing.T) {
|
||||
storageRoot := "test_temp_" + t.Name()
|
||||
base := storageRoot + "/spaces/id/id/trash"
|
||||
defer os.RemoveAll(storageRoot)
|
||||
|
||||
nestedTrashDir := base + "/s1/trash/s2/node"
|
||||
require.NoError(t, os.MkdirAll(nestedTrashDir, os.ModePerm))
|
||||
|
||||
require.NoError(t, removeEmptyFolder(nestedTrashDir, false, false, storageRoot))
|
||||
|
||||
assertNoDirExists(t, nestedTrashDir)
|
||||
assertNoDirExists(t, base+"/s1/trash/s2")
|
||||
assertNoDirExists(t, base+"/s1/trash")
|
||||
assertNoDirExists(t, base+"/s1")
|
||||
assertDirExists(t, base)
|
||||
}
|
||||
|
||||
func assertNoDirExists(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
_, err := os.Stat(path)
|
||||
require.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func assertDirExists(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
fi, err := os.Stat(path)
|
||||
require.NoError(t, err)
|
||||
require.True(t, fi.IsDir())
|
||||
}
|
||||
@@ -86,6 +86,5 @@
|
||||
<property name="lineLimit" value="120" />
|
||||
<property name="absoluteLineLimit" value="120" />
|
||||
</properties>
|
||||
<exclude-pattern>*/tests/acceptance/bootstrap/*</exclude-pattern>
|
||||
</rule>
|
||||
</ruleset>
|
||||
+1
-13
@@ -43,10 +43,6 @@ type StringNode struct {
|
||||
*Base
|
||||
Key string
|
||||
Value string
|
||||
Exact bool
|
||||
// CaseInsensitive marks a case-insensitive restriction; set by the search
|
||||
// lowering pass, a backend routes it to the field's lowercased form.
|
||||
CaseInsensitive bool
|
||||
}
|
||||
|
||||
// BooleanNode represents a bool value
|
||||
@@ -64,14 +60,6 @@ type DateTimeNode struct {
|
||||
Value time.Time
|
||||
}
|
||||
|
||||
// NumberNode represents a numeric value
|
||||
type NumberNode struct {
|
||||
*Base
|
||||
Key string
|
||||
Operator *OperatorNode
|
||||
Value float64
|
||||
}
|
||||
|
||||
// OperatorNode represents an operator value like
|
||||
// AND, OR, NOT, =, <= ... and so on
|
||||
type OperatorNode struct {
|
||||
@@ -103,7 +91,7 @@ func NodeKey(n Node) string {
|
||||
}
|
||||
|
||||
// NodeValue tries to return the node key
|
||||
func NodeValue(n Node) any {
|
||||
func NodeValue(n Node) interface{} {
|
||||
switch node := n.(type) {
|
||||
case *StringNode:
|
||||
return node.Value
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// DiffAst returns a human-readable report of the differences between two values
|
||||
// by default it ignores every ast node Base field.
|
||||
func DiffAst(x, y any, opts ...cmp.Option) string {
|
||||
func DiffAst(x, y interface{}, opts ...cmp.Option) string {
|
||||
return cmp.Diff(
|
||||
x,
|
||||
y,
|
||||
@@ -21,7 +21,6 @@ func DiffAst(x, y any, opts ...cmp.Option) string {
|
||||
cmpopts.IgnoreFields(ast.GroupNode{}, "Base"),
|
||||
cmpopts.IgnoreFields(ast.BooleanNode{}, "Base"),
|
||||
cmpopts.IgnoreFields(ast.DateTimeNode{}, "Base"),
|
||||
cmpopts.IgnoreFields(ast.NumberNode{}, "Base"),
|
||||
)...,
|
||||
)
|
||||
}
|
||||
+14
-18
@@ -1,30 +1,26 @@
|
||||
package clihelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// DefaultApp is a wrapper for DefaultApp that adds Cobra specific settings
|
||||
func DefaultApp(app *cobra.Command) *cobra.Command {
|
||||
// TODO: when migration is done this has to become DefaultApp
|
||||
func DefaultApp(app *cli.App) *cli.App {
|
||||
// version info
|
||||
app.Version = fmt.Sprintf("%s (%s <%s>) (%s)", version.String, "OpenCloud GmbH", "support@opencloud.eu", version.Compiled())
|
||||
app.Version = version.String
|
||||
app.Compiled = version.Compiled()
|
||||
|
||||
// cobra would print the error on top of what main() already prints
|
||||
app.SilenceErrors = true
|
||||
|
||||
// keep the usage block for flag parse errors, drop it once RunE runs.
|
||||
// Traversing runs the hook even below a subcommand that brings its own,
|
||||
// e.g. every service below ServiceCommand.
|
||||
cobra.EnableTraverseRunHooks = true
|
||||
app.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
|
||||
cmd.SilenceUsage = true
|
||||
return nil
|
||||
// author info
|
||||
app.Authors = []*cli.Author{
|
||||
{
|
||||
Name: "OpenCloud GmbH",
|
||||
Email: "support@opencloud.eu",
|
||||
},
|
||||
}
|
||||
|
||||
// disable global version flag
|
||||
// instead we provide the version command
|
||||
app.HideVersion = true
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
invitations "github.com/opencloud-eu/opencloud/services/invitations/pkg/config"
|
||||
nats "github.com/opencloud-eu/opencloud/services/nats/pkg/config"
|
||||
notifications "github.com/opencloud-eu/opencloud/services/notifications/pkg/config"
|
||||
ocdav "github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
|
||||
ocm "github.com/opencloud-eu/opencloud/services/ocm/pkg/config"
|
||||
ocs "github.com/opencloud-eu/opencloud/services/ocs/pkg/config"
|
||||
policies "github.com/opencloud-eu/opencloud/services/policies/pkg/config"
|
||||
@@ -54,13 +55,14 @@ type Runtime struct {
|
||||
Services []string `yaml:"services" env:"OC_RUN_EXTENSIONS;OC_RUN_SERVICES" desc:"A comma-separated list of service names. Will start only the listed services." introductionVersion:"1.0.0"`
|
||||
Disabled []string `yaml:"disabled_services" env:"OC_EXCLUDE_RUN_SERVICES" desc:"A comma-separated list of service names. Will start all default services except of the ones listed. Has no effect when OC_RUN_SERVICES is set." introductionVersion:"1.0.0"`
|
||||
Additional []string `yaml:"add_services" env:"OC_ADD_RUN_SERVICES" desc:"A comma-separated list of service names. Will add the listed services to the default configuration. Has no effect when OC_RUN_SERVICES is set. Note that one can add services not started by the default list and exclude services from the default list by using both envvars at the same time." introductionVersion:"1.0.0"`
|
||||
ShutdownOrder []string `yaml:"shutdown_order" env:"OC_SHUTDOWN_ORDER" desc:"A comma-separated list of service names defining the order in which services are shut down. Services not listed will be stopped after the listed ones in random order." introductionVersion:"4.0.0"`
|
||||
ShutdownOrder []string `yaml:"shutdown_order" env:"OC_SHUTDOWN_ORDER" desc:"A comma-separated list of service names defining the order in which services are shut down. Services not listed will be stopped after the listed ones in random order." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// Config combines all available configuration parts.
|
||||
type Config struct {
|
||||
*shared.Commons `yaml:"shared"`
|
||||
|
||||
Tracing *shared.Tracing `yaml:"tracing"`
|
||||
Log *shared.Log `yaml:"log"`
|
||||
Cache *shared.Cache `yaml:"cache"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
@@ -76,7 +78,7 @@ type Config struct {
|
||||
TokenManager *shared.TokenManager `yaml:"token_manager"`
|
||||
MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OC_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary for the access to resources from other services." introductionVersion:"1.0.0"`
|
||||
TransferSecret string `yaml:"transfer_secret" env:"OC_TRANSFER_SECRET" desc:"Transfer secret for signing file up- and download requests." introductionVersion:"1.0.0"`
|
||||
URLSigningSecret string `yaml:"url_signing_secret" env:"OC_URL_SIGNING_SECRET" desc:"The shared secret used to sign URLs e.g. for image downloads by the web office suite." introductionVersion:"4.0.0"`
|
||||
URLSigningSecret string `yaml:"url_signing_secret" env:"OC_URL_SIGNING_SECRET" desc:"The shared secret used to sign URLs e.g. for image downloads by the web office suite." introductionVersion:"%%NEXT%%"`
|
||||
SystemUserID string `yaml:"system_user_id" env:"OC_SYSTEM_USER_ID" desc:"ID of the OpenCloud storage-system system user. Admins need to set the ID for the storage-system system user in this config option which is then used to reference the user. Any reasonable long string is possible, preferably this would be an UUIDv4 format." introductionVersion:"1.0.0"`
|
||||
SystemUserAPIKey string `yaml:"system_user_api_key" env:"OC_SYSTEM_USER_API_KEY" desc:"API key for the storage-system system user." introductionVersion:"1.0.0"`
|
||||
AdminUserID string `yaml:"admin_user_id" env:"OC_ADMIN_USER_ID" desc:"ID of a user, that should receive admin privileges. Consider that the UUID can be encoded in some LDAP deployment configurations like in .ldif files. These need to be decoded beforehand." introductionVersion:"1.0.0"`
|
||||
@@ -104,6 +106,7 @@ type Config struct {
|
||||
Invitations *invitations.Config `yaml:"invitations"`
|
||||
Nats *nats.Config `yaml:"nats"`
|
||||
Notifications *notifications.Config `yaml:"notifications"`
|
||||
OCDav *ocdav.Config `yaml:"ocdav"`
|
||||
OCM *ocm.Config `yaml:"ocm"`
|
||||
OCS *ocs.Config `yaml:"ocs"`
|
||||
Postprocessing *postprocessing.Config `yaml:"postprocessing"`
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
invitations "github.com/opencloud-eu/opencloud/services/invitations/pkg/config/defaults"
|
||||
nats "github.com/opencloud-eu/opencloud/services/nats/pkg/config/defaults"
|
||||
notifications "github.com/opencloud-eu/opencloud/services/notifications/pkg/config/defaults"
|
||||
ocdav "github.com/opencloud-eu/opencloud/services/ocdav/pkg/config/defaults"
|
||||
ocm "github.com/opencloud-eu/opencloud/services/ocm/pkg/config/defaults"
|
||||
ocs "github.com/opencloud-eu/opencloud/services/ocs/pkg/config/defaults"
|
||||
policies "github.com/opencloud-eu/opencloud/services/policies/pkg/config/defaults"
|
||||
@@ -79,6 +80,7 @@ func DefaultConfig() *Config {
|
||||
Invitations: invitations.DefaultConfig(),
|
||||
Nats: nats.DefaultConfig(),
|
||||
Notifications: notifications.DefaultConfig(),
|
||||
OCDav: ocdav.DefaultConfig(),
|
||||
OCM: ocm.DefaultConfig(),
|
||||
OCS: ocs.DefaultConfig(),
|
||||
Postprocessing: postprocessing.DefaultConfig(),
|
||||
|
||||
@@ -58,7 +58,7 @@ type Decoder interface {
|
||||
// time.ParseDuration() function and *url.URL is supported via the
|
||||
// url.Parse() function. Slices are supported for all above mentioned
|
||||
// primitive types. Semicolon is used as delimiter in environment variables.
|
||||
func Decode(target any) error {
|
||||
func Decode(target interface{}) error {
|
||||
nFields, err := decode(target, false)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -75,7 +75,7 @@ func Decode(target any) error {
|
||||
|
||||
// StrictDecode is similar to Decode except all fields will have an implicit
|
||||
// ",strict" on all fields.
|
||||
func StrictDecode(target any) error {
|
||||
func StrictDecode(target interface{}) error {
|
||||
nFields, err := decode(target, true)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -90,7 +90,7 @@ func StrictDecode(target any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func decode(target any, strict bool) (int, error) {
|
||||
func decode(target interface{}, strict bool) (int, error) {
|
||||
s := reflect.ValueOf(target)
|
||||
if s.Kind() != reflect.Ptr || s.IsNil() {
|
||||
return 0, ErrInvalidTarget
|
||||
@@ -296,7 +296,7 @@ func decodePrimitiveType(f *reflect.Value, env string) error {
|
||||
|
||||
// MustDecode calls Decode and terminates the process if any errors
|
||||
// are encountered.
|
||||
func MustDecode(target any) {
|
||||
func MustDecode(target interface{}) {
|
||||
if err := Decode(target); err != nil {
|
||||
FailureFunc(err)
|
||||
}
|
||||
@@ -304,7 +304,7 @@ func MustDecode(target any) {
|
||||
|
||||
// MustStrictDecode calls StrictDecode and terminates the process if any errors
|
||||
// are encountered.
|
||||
func MustStrictDecode(target any) {
|
||||
func MustStrictDecode(target interface{}) {
|
||||
if err := StrictDecode(target); err != nil {
|
||||
FailureFunc(err)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func (c ConfigInfoSlice) Swap(i, j int) {
|
||||
}
|
||||
|
||||
// Returns a list of final configuration metadata sorted by envvar name
|
||||
func Export(target any) ([]*ConfigInfo, error) {
|
||||
func Export(target interface{}) ([]*ConfigInfo, error) {
|
||||
s := reflect.ValueOf(target)
|
||||
if s.Kind() != reflect.Ptr || s.IsNil() {
|
||||
return nil, ErrInvalidTarget
|
||||
|
||||
@@ -552,7 +552,7 @@ type testConfigStrict struct {
|
||||
|
||||
func TestInvalidStrict(t *testing.T) {
|
||||
cases := []struct {
|
||||
decoder func(any) error
|
||||
decoder func(interface{}) error
|
||||
rootValue string
|
||||
nestedValue string
|
||||
rootValueImplicit string
|
||||
|
||||
@@ -19,13 +19,13 @@ var (
|
||||
)
|
||||
|
||||
// BindSourcesToStructs assigns any config value from a config file / env variable to struct `dst`.
|
||||
func BindSourcesToStructs(service string, dst any) error {
|
||||
func BindSourcesToStructs(service string, dst interface{}) error {
|
||||
fileSystem := os.DirFS("/")
|
||||
filePath := strings.TrimLeft(path.Join(defaults.BaseConfigPath(), service+".yaml"), "/")
|
||||
return bindSourcesToStructs(fileSystem, filePath, service, dst)
|
||||
}
|
||||
|
||||
func bindSourcesToStructs(fileSystem fs.FS, filePath, service string, dst any) error {
|
||||
func bindSourcesToStructs(fileSystem fs.FS, filePath, service string, dst interface{}) error {
|
||||
cnf := gofig.NewWithOptions(service)
|
||||
cnf.WithOptions(func(options *gofig.Options) {
|
||||
options.ParseEnv = true
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"gotest.tools/v3/assert"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
type TestConfig struct {
|
||||
@@ -99,8 +98,6 @@ frontend:
|
||||
service_account:
|
||||
service_account_id: c05389b2-d94c-4d01-a9b5-a2f97952cc14
|
||||
service_account_secret: GW5.x1vDM&+NPRi++eV@.P7Tms4vj!=s
|
||||
ocdav:
|
||||
insecure: true
|
||||
auth_basic:
|
||||
auth_providers:
|
||||
ldap:
|
||||
@@ -117,6 +114,8 @@ groups:
|
||||
drivers:
|
||||
ldap:
|
||||
bind_password: c68JL=V$c@0GHs!%eSb8r&Ps3rgzKnXJ
|
||||
ocdav:
|
||||
insecure: true
|
||||
ocm:
|
||||
service_account:
|
||||
service_account_id: c05389b2-d94c-4d01-a9b5-a2f97952cc14
|
||||
|
||||
@@ -40,6 +40,9 @@ func ParseConfig(cfg *config.Config, skipValidate bool) error {
|
||||
// EnsureDefaults ensures that all pointers in the
|
||||
// OpenCloud config (not the services configs) are initialized
|
||||
func EnsureDefaults(cfg *config.Config) {
|
||||
if cfg.Tracing == nil {
|
||||
cfg.Tracing = &shared.Tracing{}
|
||||
}
|
||||
if cfg.Log == nil {
|
||||
cfg.Log = &shared.Log{}
|
||||
}
|
||||
@@ -68,6 +71,7 @@ func EnsureCommons(cfg *config.Config) {
|
||||
}
|
||||
|
||||
cfg.Commons.Log = structs.CopyOrZeroValue(cfg.Log)
|
||||
cfg.Commons.Tracing = structs.CopyOrZeroValue(cfg.Tracing)
|
||||
cfg.Commons.Cache = structs.CopyOrZeroValue(cfg.Cache)
|
||||
|
||||
if cfg.GRPCClientTLS != nil {
|
||||
|
||||
@@ -63,7 +63,7 @@ func GenTempCertForAddr(addr string) (tls.Certificate, error) {
|
||||
}
|
||||
|
||||
// persistCertificate generates a certificate using pk as private key and proceeds to store it into a file named certName.
|
||||
func persistCertificate(certName string, l log.Logger, pk any) error {
|
||||
func persistCertificate(certName string, l log.Logger, pk interface{}) error {
|
||||
if err := ensureExistsDir(certName); err != nil {
|
||||
return fmt.Errorf("creating certificate destination: %s", certName)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func persistCertificate(certName string, l log.Logger, pk any) error {
|
||||
}
|
||||
|
||||
// genCert generates a self signed certificate using a random rsa key.
|
||||
func generateCertificate(pk any) ([]byte, error) {
|
||||
func generateCertificate(pk interface{}) ([]byte, error) {
|
||||
for _, h := range defaultHosts {
|
||||
if ip := net.ParseIP(h); ip != nil {
|
||||
acmeTemplate.IPAddresses = append(acmeTemplate.IPAddresses, ip)
|
||||
@@ -106,7 +106,7 @@ func generateCertificate(pk any) ([]byte, error) {
|
||||
}
|
||||
|
||||
// persistKey persists the private key used to generate the certificate at the configured location.
|
||||
func persistKey(destination string, l log.Logger, pk any) error {
|
||||
func persistKey(destination string, l log.Logger, pk interface{}) error {
|
||||
if err := ensureExistsDir(destination); err != nil {
|
||||
return fmt.Errorf("creating key destination: %s", destination)
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func persistKey(destination string, l log.Logger, pk any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func publicKey(pk any) any {
|
||||
func publicKey(pk interface{}) interface{} {
|
||||
switch k := pk.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
@@ -140,7 +140,7 @@ func publicKey(pk any) any {
|
||||
}
|
||||
}
|
||||
|
||||
func pemBlockForKey(pk any, l log.Logger) *pem.Block {
|
||||
func pemBlockForKey(pk interface{}, l log.Logger) *pem.Block {
|
||||
switch k := pk.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestPersistKey(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
keyName string
|
||||
pk any
|
||||
pk interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -104,7 +104,7 @@ func TestPersistCertificate(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
certName string
|
||||
pk any
|
||||
pk interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Loaded 100 of 6129 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user