Compare commits

..

11 Commits

Author SHA1 Message Date
Jörn Friedrich Dreyer
b4ef4af69c reduce docker build context
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2025-12-05 12:17:52 +01:00
Jörn Friedrich Dreyer
0947bf7a94 merge ocdav into frontend
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
2025-12-05 10:33:40 +01:00
Viktor Scharf
6c171e11a2 check propfing after renaming data in file system (#1809) 2025-12-05 09:11:23 +01:00
Viktor Scharf
7318fde6a0 fix-get-attribute-test (#1974) 2025-12-04 14:24:57 +01:00
Viktor Scharf
6ce0cc6b1f replace golang image (#1955) 2025-12-03 15:02:25 +01:00
dependabot[bot]
3e81d1f1d8 build(deps): bump github.com/testcontainers/testcontainers-go
Bumps [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) from 0.39.0 to 0.40.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.39.0...v0.40.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-03 12:12:51 +01:00
Jannik Stehle
d352d91210 Merge pull request #1965 from opencloud-eu/ci/chat-notification-url
ci: use secret for the chat notifications url
2025-12-03 11:53:49 +01:00
Jannik Stehle
434ba0a30a ci: use secret for the chat notifications url 2025-12-03 11:08:53 +01:00
opencloudeu
0f448f23a0 [tx] updated from transifex 2025-12-03 00:02:26 +00:00
Artur Neumann
babb97f8a6 Merge pull request #1956 from opencloud-eu/individual-it-patch-1
fix the link in quickstart script for itself
2025-12-02 21:11:25 +05:45
Artur Neumann
a6d637456d fix the link in quickstart script for itself
see also https://docs.opencloud.eu/docs/admin/intro#quick-start
2025-12-02 19:24:27 +05:45
122 changed files with 1094 additions and 2533 deletions

View File

@@ -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"

4
.vscode/launch.json vendored
View File

@@ -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,ocdav",
//"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",
/*
* Keep secrets and passwords in one block to allow easy uncommenting
@@ -129,8 +129,6 @@
"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",

View File

@@ -74,11 +74,11 @@ OC_FED_DOMAIN = "%s:10200" % FED_OC_SERVER_NAME
event = {
"base": {
"event": ["push", "manual"],
"branch": "stable-*",
"branch": "main",
},
"cron": {
"event": "cron",
"branch": "stable-*",
"branch": "main",
},
"pull_request": {
"event": "pull_request",
@@ -479,10 +479,6 @@ def main(ctx):
if ctx.build.event == "cron" and ctx.build.sender == "translation-sync":
return translation_sync(ctx)
is_release_pr = (ctx.build.event == "pull_request" and ctx.build.sender == "openclouders" and "🎉 release" in ctx.build.title.lower())
if is_release_pr:
return [licenseCheck(ctx)]
build_release_helpers = \
readyReleaseGo()
@@ -1656,7 +1652,6 @@ def dockerRelease(ctx, repo, build_type):
build_args = {
"REVISION": "%s" % ctx.build.commit,
"VERSION": "%s" % (ctx.build.ref.replace("refs/tags/", "") if ctx.build.event == "tag" else "daily"),
"EDITION": "stable" if build_type == "production" else "rolling",
}
# if no additional tag is given, the build-plugin adds latest
@@ -1820,7 +1815,6 @@ def binaryRelease(ctx, arch, depends_on = []):
"image": OC_CI_GOLANG,
"environment": {
"VERSION": (ctx.build.ref.replace("refs/tags/", "") if ctx.build.event == "tag" else "daily"),
"EDITION": "rolling",
"HTTP_PROXY": {
"from_secret": "ci_http_proxy",
},
@@ -1953,7 +1947,6 @@ def readyReleaseGo():
"image": READY_RELEASE_GO,
"settings": {
"git_email": "devops@opencloud.eu",
"release_branch": "stable-4.0",
"forge_type": "github",
"forge_token": {
"from_secret": "github_token",
@@ -2127,7 +2120,6 @@ def opencloudServer(storage = "decomposed", accounts_hash_difficulty = 4, depend
"IDP_DEBUG_ADDR": "0.0.0.0:9134",
"INVITATIONS_DEBUG_ADDR": "0.0.0.0:9269",
"NATS_DEBUG_ADDR": "0.0.0.0:9234",
"OCDAV_DEBUG_ADDR": "0.0.0.0:9163",
"OCM_DEBUG_ADDR": "0.0.0.0:9281",
"OCS_DEBUG_ADDR": "0.0.0.0:9114",
"POSTPROCESSING_DEBUG_ADDR": "0.0.0.0:9255",
@@ -2347,12 +2339,11 @@ def translation_sync(ctx):
"image": OC_CI_GOLANG,
"commands": [
"make l10n-read",
"mkdir tx && cd tx",
"curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash",
"export PATH=$PATH:$(pwd) && cd ..",
". ~/.profile",
"make l10n-push",
"make l10n-pull",
"rm -rf tx",
"rm tx",
"make l10n-clean",
],
"environment": {

View File

@@ -1,21 +1,5 @@
# Changelog
## [4.0.1](https://github.com/opencloud-eu/opencloud/releases/tag/v4.0.1) - 2025-12-15
### ❤️ Thanks to all contributors! ❤️
@ScharfViktor, @fschade, @kulmann, @micbar, @prashant-gurung899
### ✅ Tests
- [stable-4.0] Port #2011 [[#2018](https://github.com/opencloud-eu/opencloud/pull/2018)]
### 🐛 Bug Fixes
- [stable-4.0] fix: build time edition channels #2001 [[#2010](https://github.com/opencloud-eu/opencloud/pull/2010)]
- [stable-4.0] fix: enforce trailing slash for server url [[#2002](https://github.com/opencloud-eu/opencloud/pull/2002)]
- [stable-4.0] fix: enhance resource creation with detailed process information (#1978) [[#2000](https://github.com/opencloud-eu/opencloud/pull/2000)]
## [4.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v4.0.0) - 2025-12-01
### ❤️ Thanks to all contributors! ❤️

View File

@@ -44,7 +44,6 @@ OC_MODULES = \
services/invitations \
services/nats \
services/notifications \
services/ocdav \
services/ocm \
services/ocs \
services/policies \

View File

@@ -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/quickinstall.sh | /bin/bash
# curl -L https://opencloud.eu/install | /bin/bash
# This function is borrowed from openSUSEs /usr/bin/old, thanks.
function backup_file () {

4
go.mod
View File

@@ -80,7 +80,7 @@ require (
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.39.0
github.com/testcontainers/testcontainers-go v0.40.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
@@ -189,7 +189,7 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/docker v28.5.1+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

8
go.sum
View File

@@ -310,8 +310,8 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -1188,8 +1188,8 @@ github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhg
github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8=
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/testcontainers/testcontainers-go/modules/opensearch v0.39.0 h1:IkJUhR8AigQxv7qHZho/OtTU6JtiSdBGVh76o175JGo=
github.com/testcontainers/testcontainers-go/modules/opensearch v0.39.0/go.mod h1:B7AhrDmQ4QbpzA0BeWvqzaJ8vbwcdEQDzybr35sBRfw=
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=

View File

@@ -17,7 +17,7 @@ include ../.make/docs.mk
.PHONY: dev-docker
dev-docker:
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev ../..
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev ..
.PHONY: dev-docker-multiarch
dev-docker-multiarch:

View File

@@ -3,7 +3,6 @@ ARG TARGETOS
ARG TARGETARCH
ARG VERSION
ARG STRING
ARG EDITION
RUN apk add bash make git curl gcc musl-dev libc-dev binutils-gold inotify-tools vips-dev
@@ -12,7 +11,7 @@ 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 opencloud/opencloud release-linux-docker-${TARGETARCH} ENABLE_VIPS=true DIST=/dist
make -C opencloud release-linux-docker-${TARGETARCH} ENABLE_VIPS=true DIST=/dist
FROM alpine:3.22
ARG VERSION

View File

@@ -30,7 +30,6 @@ 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"
@@ -163,11 +162,6 @@ var svccmds = []register.Command{
cfg.Notifications.Commons = cfg.Commons
})
},
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

View File

@@ -281,6 +281,7 @@ 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
@@ -289,7 +290,6 @@ 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{

View File

@@ -32,7 +32,6 @@ 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"`
@@ -105,6 +104,7 @@ type FrontendService struct {
AppHandler InsecureService `yaml:"app_handler"`
Archiver InsecureService
ServiceAccount ServiceAccount `yaml:"service_account"`
OCDav InsecureService
}
// Gateway is the configuration for the gateway

View File

@@ -40,7 +40,6 @@ 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"
@@ -204,11 +203,6 @@ 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

View File

@@ -24,7 +24,6 @@ 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"
@@ -105,7 +104,6 @@ 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"`

View File

@@ -24,7 +24,6 @@ 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"
@@ -80,7 +79,6 @@ func DefaultConfig() *Config {
Invitations: invitations.DefaultConfig(),
Nats: nats.DefaultConfig(),
Notifications: notifications.DefaultConfig(),
OCDav: ocdav.DefaultConfig(),
OCM: ocm.DefaultConfig(),
OCS: ocs.DefaultConfig(),
Postprocessing: postprocessing.DefaultConfig(),

View File

@@ -1,9 +1,10 @@
package config
import (
"gotest.tools/v3/assert"
"testing"
"testing/fstest"
"gotest.tools/v3/assert"
)
type TestConfig struct {
@@ -98,6 +99,8 @@ 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:
@@ -114,8 +117,6 @@ 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

View File

@@ -105,19 +105,9 @@ func createResource(ctx context.Context, serviceName string) (*resource.Resource
return resource.New(ctx,
// Reads OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME
resource.WithFromEnv(),
// Host Information
// Host and process information
resource.WithHost(),
// Process Information
// Resource WithProcessOwner is deliberately omitted because
// inside containers where process might run as an arbitrary
// uid without a username associated this would fail.
resource.WithProcessPID(),
resource.WithProcessCommandArgs(),
resource.WithProcessExecutableName(),
resource.WithProcessExecutablePath(),
resource.WithProcessRuntimeDescription(),
resource.WithProcessRuntimeName(),
resource.WithProcessRuntimeVersion(),
resource.WithProcess(),
// Service attributes
resource.WithAttributes(
semconv.ServiceName(serviceName),

View File

@@ -1,4 +0,0 @@
package version
// InitEdition exports the private edition initialization func for testing
var InitEdition = initEdition

View File

@@ -1,27 +1,9 @@
package version
import (
"fmt"
"slices"
"strings"
"time"
"github.com/Masterminds/semver"
"github.com/opencloud-eu/reva/v2/pkg/logger"
)
const (
// Dev is used as a placeholder.
Dev = "dev"
// EditionDev indicates the development build channel was used to build the binary.
EditionDev = Dev
// EditionRolling indicates the rolling release build channel was used to build the binary.
EditionRolling = "rolling"
// EditionStable indicates the stable release build channel was used to build the binary.
EditionStable = "stable"
// EditionLTS indicates the lts release build channel was used to build the binary.
EditionLTS = "lts"
)
var (
@@ -34,61 +16,22 @@ var (
// LatestTag is the latest released version plus the dev meta version.
// Will be overwritten by the release pipeline
// Needs a manual change for every tagged release
LatestTag = "4.0.1+dev"
LatestTag = "4.0.0-rc.3+dev"
// Date indicates the build date.
// This has been removed, it looks like you can only replace static strings with recent go versions
//Date = time.Now().Format("20060102")
Date = Dev
Date = "dev"
// Legacy defines the old long 4 number OpenCloud version needed for some clients
Legacy = "0.1.0.0"
// LegacyString defines the old OpenCloud version needed for some clients
LegacyString = "0.1.0"
// Edition describes the build channel (stable, rolling, nightly, daily, dev)
Edition = Dev // default for self-compiled builds
)
func init() { //nolint:gochecknoinits
if err := initEdition(); err != nil {
logger.New().Error().Err(err).Msg("falling back to dev")
}
}
func initEdition() error {
regularEditions := []string{EditionDev, EditionRolling, EditionStable}
versionedEditions := []string{EditionLTS}
if !slices.ContainsFunc(slices.Concat(regularEditions, versionedEditions), func(s string) bool {
isRegularEdition := slices.Contains(regularEditions, Edition)
if isRegularEdition && s == Edition {
return true
}
// handle editions with a version
editionParts := strings.Split(Edition, "-")
if len(editionParts) != 2 { // a versioned edition channel must consist of exactly 2 parts.
return false
}
isVersionedEdition := slices.Contains(versionedEditions, editionParts[0])
if !isVersionedEdition { // not all channels can contain version information
return false
}
_, err := semver.NewVersion(editionParts[1])
return err == nil
}) {
Edition = Dev
return fmt.Errorf(`unknown edition channel "%s"`, Edition)
}
return nil
}
// Compiled returns the compile time of this service.
func Compiled() time.Time {
if Date == Dev {
if Date == "dev" {
return time.Now()
}
t, _ := time.Parse("20060102", Date)

View File

@@ -1,65 +0,0 @@
package version_test
import (
"fmt"
"testing"
"github.com/opencloud-eu/opencloud/pkg/version"
)
func TestChannel(t *testing.T) {
tests := map[string]struct {
got string
valid bool
}{
"no channel, defaults to dev": {
got: "",
valid: false,
},
"dev channel": {
got: version.EditionDev,
valid: true,
},
"rolling channel": {
got: version.EditionRolling,
valid: true,
},
"stable channel": {
got: version.EditionStable,
valid: true,
},
"lts channel without version": {
got: version.EditionLTS,
valid: false,
},
"lts-1.0.0 channel": {
got: fmt.Sprintf("%s-1", version.EditionLTS),
valid: true,
},
"lts-one invalid version": {
got: fmt.Sprintf("%s-one", version.EditionLTS),
valid: false,
},
"known channel with version": {
got: fmt.Sprintf("%s-1", version.EditionStable),
valid: false,
},
"unknown channel": {
got: "foo",
valid: false,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
version.Edition = test.got
switch err := version.InitEdition(); {
case err != nil && !test.valid && version.Edition != version.Dev: // if a given edition is unknown, the value is always dev
fallthrough
case test.valid != (err == nil):
t.Fatalf("invalid edition: %s", version.Edition)
}
})
}
}

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2025-11-12 00:01+0000\n"
"POT-Creation-Date: 2025-12-03 00:01+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2025-11-12 00:01+0000\n"
"POT-Creation-Date: 2025-12-03 00:01+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Lulufox, 2025\n"
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"

View File

@@ -33,7 +33,7 @@ type Config struct {
EnableFederatedSharingIncoming bool `yaml:"enable_federated_sharing_incoming" env:"OC_ENABLE_OCM;FRONTEND_ENABLE_FEDERATED_SHARING_INCOMING" desc:"Changing this value is NOT supported. Enables support for incoming federated sharing for clients. The backend behaviour is not changed." introductionVersion:"1.0.0"`
EnableFederatedSharingOutgoing bool `yaml:"enable_federated_sharing_outgoing" env:"OC_ENABLE_OCM;FRONTEND_ENABLE_FEDERATED_SHARING_OUTGOING" desc:"Changing this value is NOT supported. Enables support for outgoing federated sharing for clients. The backend behaviour is not changed." introductionVersion:"1.0.0"`
SearchMinLength int `yaml:"search_min_length" env:"FRONTEND_SEARCH_MIN_LENGTH" desc:"Minimum number of characters to enter before a client should start a search for Share receivers. This setting can be used to customize the user experience if e.g too many results are displayed." introductionVersion:"1.0.0"`
Edition string `desc:"Edition of OpenCloud. Used for branding purposes." introductionVersion:"1.0.0"`
Edition string `yaml:"edition" env:"OC_EDITION;FRONTEND_EDITION" desc:"Edition of OpenCloud. Used for branding purposes." introductionVersion:"1.0.0"`
DisableSSE bool `yaml:"disable_sse" env:"OC_DISABLE_SSE;FRONTEND_DISABLE_SSE" desc:"When set to true, clients are informed that the Server-Sent Events endpoint is not accessible." introductionVersion:"1.0.0"`
DisableRadicale bool `yaml:"disable_radicale" env:"FRONTEND_DISABLE_RADICALE" desc:"When set to true, clients are informed that the Radicale (CalDAV/CardDAV) is not accessible." introductionVersion:"4.0.0"`
DefaultLinkPermissions int `yaml:"default_link_permissions" env:"FRONTEND_DEFAULT_LINK_PERMISSIONS" desc:"Defines the default permissions a link is being created with. Possible values are 0 (= internal link, for instance members only) and 1 (= public link with viewer permissions). Defaults to 1." introductionVersion:"1.0.0"`
@@ -44,6 +44,7 @@ type Config struct {
Archiver Archiver `yaml:"archiver"`
DataGateway DataGateway `yaml:"data_gateway"`
OCS OCS `yaml:"ocs"`
OCDav OCDav `yaml:"ocdav"`
Checksums Checksums `yaml:"checksums"`
ReadOnlyUserAttributes []string `yaml:"read_only_user_attributes" env:"FRONTEND_READONLY_USER_ATTRIBUTES" desc:"A list of user attributes to indicate as read-only. Supported values: 'user.onPremisesSamAccountName' (username), 'user.displayName', 'user.mail', 'user.passwordProfile' (password), 'user.appRoleAssignments' (role), 'user.memberOf' (groups), 'user.accountEnabled' (login allowed), 'drive.quota' (quota). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
LDAPServerWriteEnabled bool `yaml:"ldap_server_write_enabled" env:"OC_LDAP_SERVER_WRITE_ENABLED;FRONTEND_LDAP_SERVER_WRITE_ENABLED" desc:"Allow creating, modifying and deleting LDAP users via the GRAPH API. This can only be set to 'true' when keeping default settings for the LDAP user and group attribute types (the 'OC_LDAP_USER_SCHEMA_* and 'OC_LDAP_GROUP_SCHEMA_* variables)." introductionVersion:"1.0.0"`
@@ -152,6 +153,41 @@ type OCS struct {
ShowUserEmailInResults bool `yaml:"show_email_in_results" env:"OC_SHOW_USER_EMAIL_IN_RESULTS" desc:"Include user email addresses in responses. If absent or set to false emails will be omitted from results. Please note that admin users can always see all email addresses." introductionVersion:"1.0.0"`
}
type OCDav struct {
Prefix string `yaml:"prefix" env:"OCDAV_HTTP_PREFIX" desc:"A URL path prefix for the handler." introductionVersion:"1.0.0"`
SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"OCDAV_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"1.0.0"`
WebdavNamespace string `yaml:"webdav_namespace" env:"OCDAV_WEBDAV_NAMESPACE" desc:"Jail requests to /dav/webdav into this CS3 namespace. Supports template layouting with CS3 User properties." introductionVersion:"1.0.0"`
FilesNamespace string `yaml:"files_namespace" env:"OCDAV_FILES_NAMESPACE" desc:"Jail requests to /dav/files/{username} into this CS3 namespace. Supports template layouting with CS3 User properties." introductionVersion:"1.0.0"`
SharesNamespace string `yaml:"shares_namespace" env:"OCDAV_SHARES_NAMESPACE" desc:"The human readable path for the share jail. Relative to a users personal space root. Upcased intentionally." introductionVersion:"1.0.0"`
OCMNamespace string `yaml:"ocm_namespace" env:"OCDAV_OCM_NAMESPACE" desc:"The human readable path prefix for the ocm shares." introductionVersion:"1.0.0"`
// PublicURL used to redirect /s/{token} URLs to
PublicURL string `yaml:"public_url" env:"OC_URL;OCDAV_PUBLIC_URL" desc:"URL where OpenCloud is reachable for users." introductionVersion:"1.0.0"`
// Insecure certificates allowed when making requests to the gateway
Insecure bool `yaml:"insecure" env:"OC_INSECURE;OCDAV_INSECURE" desc:"Allow insecure connections to the GATEWAY service." introductionVersion:"1.0.0"`
EnableHTTPTPC bool `yaml:"enable_http_tpc" env:"OCDAV_ENABLE_HTTP_TPC" desc:"Enable HTTP / WebDAV Third-Party-Copy support." introductionVersion:"%%NEXT%%"`
// Timeout in seconds when making requests to the gateway
Timeout int64 `yaml:"gateway_request_timeout" env:"OCDAV_GATEWAY_REQUEST_TIMEOUT" desc:"Request timeout in seconds for requests from the oCDAV service to the GATEWAY service." introductionVersion:"1.0.0"`
MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OC_MACHINE_AUTH_API_KEY;OCDAV_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"`
Status Status `yaml:"-"`
AllowPropfindDepthInfinity bool `yaml:"allow_propfind_depth_infinity" env:"OCDAV_ALLOW_PROPFIND_DEPTH_INFINITY" desc:"Allow the use of depth infinity in PROPFINDS. When enabled, a propfind will traverse through all subfolders. If many subfolders are expected, depth infinity can cause heavy server load and/or delayed response times." introductionVersion:"1.0.0"`
}
// Status holds the configurable values for the status.php
type Status struct {
Version string
VersionString string
Product string
ProductName string
ProductVersion string
Edition string `yaml:"edition" env:"OC_EDITION;OCDAV_EDITION" desc:"Edition of OpenCloud. Used for branding purposes." introductionVersion:"1.0.0"`
}
type CacheWarmupDrivers struct {
CBOX CBOXDriver `yaml:"cbox,omitempty"`
}

View File

@@ -88,7 +88,7 @@ func DefaultConfig() *config.Config {
DefaultUploadProtocol: "tus",
DefaultLinkPermissions: 1,
SearchMinLength: 3,
Edition: version.Edition,
Edition: "",
CheckForUpdates: true,
Checksums: config.Checksums{
SupportedTypes: []string{"sha1", "md5", "adler32"},
@@ -120,6 +120,28 @@ func DefaultConfig() *config.Config {
PublicShareMustHavePassword: true,
IncludeOCMSharees: false,
},
OCDav: config.OCDav{
Prefix: "",
SkipUserGroupsInToken: false,
WebdavNamespace: "/users/{{.Id.OpaqueId}}",
FilesNamespace: "/users/{{.Id.OpaqueId}}",
SharesNamespace: "/Shares",
OCMNamespace: "/public",
PublicURL: "https://localhost:9200",
Insecure: false,
EnableHTTPTPC: false,
Timeout: 84300,
Status: config.Status{
Version: version.Legacy,
VersionString: version.LegacyString,
ProductVersion: version.GetString(),
Product: "OpenCloud",
ProductName: "OpenCloud",
Edition: "",
},
AllowPropfindDepthInfinity: false,
},
Middleware: config.Middleware{
Auth: config.Auth{
CredentialsByUserAgent: map[string]string{},

View File

@@ -346,7 +346,7 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
},
"version": map[string]interface{}{
"product": "OpenCloud",
"edition": version.Edition,
"edition": "",
"major": version.ParsedLegacy().Major(),
"minor": version.ParsedLegacy().Minor(),
"micro": version.ParsedLegacy().Patch(),
@@ -357,6 +357,34 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
"include_ocm_sharees": cfg.OCS.IncludeOCMSharees,
"show_email_in_results": cfg.OCS.ShowUserEmailInResults,
},
"ocdav": map[string]interface{}{
"prefix": cfg.OCDav.Prefix,
"files_namespace": cfg.OCDav.FilesNamespace,
"webdav_namespace": cfg.OCDav.WebdavNamespace,
"shares_namespace": cfg.OCDav.SharesNamespace,
"ocm_namespace": cfg.OCDav.OCMNamespace,
"gatewaysvc": cfg.Reva.Address,
"timeout": cfg.OCDav.Timeout,
"insecure": cfg.OCDav.Insecure,
"enable_http_tpc": cfg.OCDav.EnableHTTPTPC,
"public_url": cfg.OCDav.PublicURL,
// still not supported
//"favorite_storage_driver": unused,
//"favorite_storage_drivers": unused,
"version": cfg.OCDav.Status.Version,
"version_string": cfg.OCDav.Status.VersionString,
"edition": cfg.OCDav.Status.Edition,
"product": cfg.OCDav.Status.Product,
"product_name": cfg.OCDav.Status.ProductName,
"product_version": cfg.OCDav.Status.ProductVersion,
"allow_depth_infinity": cfg.OCDav.AllowPropfindDepthInfinity,
"validation": map[string]interface{}{
// "invalid_chars": aka ItemNameInvalidChars option ... unused
// "max_length": aka ItemNameMaxLength option ... unused
},
"url_signing_shared_secret": cfg.Commons.URLSigningSecret,
"machine_auth_apikey": cfg.MachineAuthAPIKey,
},
},
},
}, nil

View File

@@ -1,11 +0,0 @@
SHELL := bash
NAME := ocdav
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
include ../../.bingo/Variables.mk
endif
include ../../.make/default.mk
include ../../.make/go.mk
include ../../.make/release.mk
include ../../.make/docs.mk

View File

@@ -1,3 +0,0 @@
# ocDAV
The ocdav service provides the WebDAV API which is required by OpenCloud clients. Previews (thumbnails) are provided by the [WebDAV service](../webdav).

View File

@@ -1,19 +0,0 @@
package main
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/command"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config/defaults"
)
func main() {
cfg := defaults.DefaultConfig()
cfg.Context, _ = signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
if err := command.Execute(cfg); err != nil {
os.Exit(1)
}
}

View File

@@ -1,54 +0,0 @@
package command
import (
"fmt"
"net/http"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config/parser"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/logging"
"github.com/urfave/cli/v2"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "check health status",
Category: "info",
Before: func(c *cli.Context) error {
return configlog.ReturnError(parser.ParseConfig(cfg))
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}

View File

@@ -1,34 +0,0 @@
package command
import (
"os"
"github.com/opencloud-eu/opencloud/pkg/clihelper"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
"github.com/urfave/cli/v2"
)
// GetCommands provides all commands for this service
func GetCommands(cfg *config.Config) cli.Commands {
return []*cli.Command{
// start this service
Server(cfg),
// interaction with this service
// infos about this service
Health(cfg),
Version(cfg),
}
}
// Execute is the entry point for the OpenCloud ocdav command.
func Execute(cfg *config.Config) error {
app := clihelper.DefaultApp(&cli.App{
Name: "ocdav",
Usage: "Provide a WebDav API for OpenCloud",
Commands: GetCommands(cfg),
})
return app.RunContext(cfg.Context, os.Args)
}

View File

@@ -1,134 +0,0 @@
package command
import (
"context"
"fmt"
"os/signal"
"github.com/opencloud-eu/opencloud/pkg/broker"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/registry"
"github.com/opencloud-eu/opencloud/pkg/runner"
ohttp "github.com/opencloud-eu/opencloud/pkg/service/http"
"github.com/opencloud-eu/opencloud/pkg/tracing"
"github.com/opencloud-eu/opencloud/pkg/version"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config/parser"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/logging"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/server/debug"
"github.com/opencloud-eu/reva/v2/pkg/micro/ocdav"
"github.com/opencloud-eu/reva/v2/pkg/sharedconf"
"github.com/urfave/cli/v2"
)
// Server is the entry point for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
Category: "server",
Before: func(c *cli.Context) error {
return configlog.ReturnFatal(parser.ParseConfig(cfg))
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name)
if err != nil {
return err
}
var cancel context.CancelFunc
if cfg.Context == nil {
cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...)
defer cancel()
}
ctx := cfg.Context
gr := runner.NewGroup()
// init reva shared config explicitly as the go-micro based ocdav does not use
// the reva runtime. But we need e.g. the shared client settings to be initialized
sc := map[string]interface{}{
"jwt_secret": cfg.TokenManager.JWTSecret,
"gatewaysvc": cfg.Reva.Address,
"skip_user_groups_in_token": cfg.SkipUserGroupsInToken,
"grpc_client_options": cfg.Reva.GetGRPCClientConfig(),
}
if err := sharedconf.Decode(sc); err != nil {
logger.Error().Err(err).Msg("error decoding shared config for ocdav")
}
opts := []ocdav.Option{
ocdav.Name(cfg.HTTP.Namespace + "." + cfg.Service.Name),
ocdav.Version(version.GetString()),
ocdav.Context(ctx),
ocdav.Logger(logger.Logger),
ocdav.Address(cfg.HTTP.Addr),
ocdav.AllowCredentials(cfg.HTTP.CORS.AllowCredentials),
ocdav.AllowedMethods(cfg.HTTP.CORS.AllowedMethods),
ocdav.AllowedHeaders(cfg.HTTP.CORS.AllowedHeaders),
ocdav.AllowedOrigins(cfg.HTTP.CORS.AllowedOrigins),
ocdav.FilesNamespace(cfg.FilesNamespace),
ocdav.WebdavNamespace(cfg.WebdavNamespace),
ocdav.OCMNamespace(cfg.OCMNamespace),
ocdav.AllowDepthInfinity(cfg.AllowPropfindDepthInfinity),
ocdav.SharesNamespace(cfg.SharesNamespace),
ocdav.Timeout(cfg.Timeout),
ocdav.Insecure(cfg.Insecure),
ocdav.PublicURL(cfg.PublicURL),
ocdav.Prefix(cfg.HTTP.Prefix),
ocdav.GatewaySvc(cfg.Reva.Address),
ocdav.JWTSecret(cfg.TokenManager.JWTSecret),
ocdav.ProductName(cfg.Status.ProductName),
ocdav.ProductVersion(cfg.Status.ProductVersion),
ocdav.Product(cfg.Status.Product),
ocdav.Version(cfg.Status.Version),
ocdav.VersionString(cfg.Status.VersionString),
ocdav.Edition(cfg.Status.Edition),
ocdav.MachineAuthAPIKey(cfg.MachineAuthAPIKey),
ocdav.Broker(broker.NoOp{}),
// ocdav.FavoriteManager() // FIXME needs a proper persistence implementation https://github.com/owncloud/ocis/issues/1228
// ocdav.LockSystem(), // will default to the CS3 lock system
// ocdav.TLSConfig() // tls config for the http server
ocdav.MetricsEnabled(true),
ocdav.MetricsNamespace("ocis"),
ocdav.WithTraceProvider(traceProvider),
ocdav.RegisterTTL(registry.GetRegisterTTL()),
ocdav.RegisterInterval(registry.GetRegisterInterval()),
ocdav.URLSigningSharedSecret(cfg.Commons.URLSigningSecret),
}
s, err := ocdav.Service(opts...)
if err != nil {
return err
}
// creating a runner for a go-micro service is a bit complex, so we'll
// wrap the go-micro service with an ocis service the same way as
// ocis-pkg/service/http is doing in order to reuse the factory.
gr.Add(runner.NewGoMicroHttpServerRunner(cfg.Service.Name+".http", ohttp.Service{Service: s}))
debugServer, err := debug.Server(
debug.Logger(logger),
debug.Context(ctx),
debug.Config(cfg),
)
if err != nil {
logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server")
return err
}
gr.Add(runner.NewGolangHttpServerRunner(cfg.Service.Name+".debug", debugServer))
grResults := gr.Run(ctx)
// return the first non-nil error found in the results
for _, grResult := range grResults {
if grResult.RunnerError != nil {
return grResult.RunnerError
}
}
return nil
},
}
}

View File

@@ -1,50 +0,0 @@
package command
import (
"fmt"
"os"
"github.com/opencloud-eu/opencloud/pkg/registry"
"github.com/opencloud-eu/opencloud/pkg/version"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
"github.com/urfave/cli/v2"
)
// Version prints the service versions of all running instances.
func Version(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "version",
Usage: "print the version of this binary and the running service instances",
Category: "info",
Action: func(c *cli.Context) error {
fmt.Println("Version: " + version.GetString())
fmt.Printf("Compiled: %s\n", version.Compiled())
fmt.Println("")
reg := registry.GetRegistry()
services, err := reg.GetService(cfg.HTTP.Namespace + "." + cfg.Service.Name)
if err != nil {
fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err))
return err
}
if len(services) == 0 {
fmt.Println("No running " + cfg.Service.Name + " service found.")
return nil
}
table := tablewriter.NewTable(os.Stdout, tablewriter.WithHeaderAutoFormat(tw.Off))
table.Header([]string{"Version", "Address", "Id"})
for _, s := range services {
for _, n := range s.Nodes {
table.Append([]string{s.Version, n.Address, n.Id})
}
}
table.Render()
return nil
},
}
}

View File

@@ -1,84 +0,0 @@
package config
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/shared"
)
type Config struct {
Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
Service Service `yaml:"-"`
Log *Log `yaml:"log"`
Debug Debug `yaml:"debug"`
HTTP HTTPConfig `yaml:"http"`
TokenManager *TokenManager `yaml:"token_manager"`
Reva *shared.Reva `yaml:"reva"`
SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"OCDAV_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"1.0.0"`
WebdavNamespace string `yaml:"webdav_namespace" env:"OCDAV_WEBDAV_NAMESPACE" desc:"Jail requests to /dav/webdav into this CS3 namespace. Supports template layouting with CS3 User properties." introductionVersion:"1.0.0"`
FilesNamespace string `yaml:"files_namespace" env:"OCDAV_FILES_NAMESPACE" desc:"Jail requests to /dav/files/{username} into this CS3 namespace. Supports template layouting with CS3 User properties." introductionVersion:"1.0.0"`
SharesNamespace string `yaml:"shares_namespace" env:"OCDAV_SHARES_NAMESPACE" desc:"The human readable path for the share jail. Relative to a users personal space root. Upcased intentionally." introductionVersion:"1.0.0"`
OCMNamespace string `yaml:"ocm_namespace" env:"OCDAV_OCM_NAMESPACE" desc:"The human readable path prefix for the ocm shares." introductionVersion:"1.0.0"`
// PublicURL used to redirect /s/{token} URLs to
PublicURL string `yaml:"public_url" env:"OC_URL;OCDAV_PUBLIC_URL" desc:"URL where OpenCloud is reachable for users." introductionVersion:"1.0.0"`
// Insecure certificates allowed when making requests to the gateway
Insecure bool `yaml:"insecure" env:"OC_INSECURE;OCDAV_INSECURE" desc:"Allow insecure connections to the GATEWAY service." introductionVersion:"1.0.0"`
// Timeout in seconds when making requests to the gateway
Timeout int64 `yaml:"gateway_request_timeout" env:"OCDAV_GATEWAY_REQUEST_TIMEOUT" desc:"Request timeout in seconds for requests from the oCDAV service to the GATEWAY service." introductionVersion:"1.0.0"`
MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OC_MACHINE_AUTH_API_KEY;OCDAV_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"`
Context context.Context `yaml:"-"`
Status Status `yaml:"-"`
AllowPropfindDepthInfinity bool `yaml:"allow_propfind_depth_infinity" env:"OCDAV_ALLOW_PROPFIND_DEPTH_INFINITY" desc:"Allow the use of depth infinity in PROPFINDS. When enabled, a propfind will traverse through all subfolders. If many subfolders are expected, depth infinity can cause heavy server load and/or delayed response times." introductionVersion:"1.0.0"`
}
type Log struct {
Level string `yaml:"level" env:"OC_LOG_LEVEL;OCDAV_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"1.0.0"`
Pretty bool `yaml:"pretty" env:"OC_LOG_PRETTY;OCDAV_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"1.0.0"`
Color bool `yaml:"color" env:"OC_LOG_COLOR;OCDAV_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"1.0.0"`
File string `yaml:"file" env:"OC_LOG_FILE;OCDAV_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"1.0.0"`
}
type Service struct {
Name string `yaml:"-"`
}
type Debug struct {
Addr string `yaml:"addr" env:"OCDAV_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"1.0.0"`
Token string `yaml:"token" env:"OCDAV_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"1.0.0"`
Pprof bool `yaml:"pprof" env:"OCDAV_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"1.0.0"`
Zpages bool `yaml:"zpages" env:"OCDAV_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"1.0.0"`
}
type HTTPConfig struct {
Addr string `yaml:"addr" env:"OCDAV_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"1.0.0"`
Namespace string `yaml:"-"`
Protocol string `yaml:"protocol" env:"OCDAV_HTTP_PROTOCOL" desc:"The transport protocol of the HTTP service." introductionVersion:"1.0.0"`
Prefix string `yaml:"prefix" env:"OCDAV_HTTP_PREFIX" desc:"A URL path prefix for the handler." introductionVersion:"1.0.0"`
CORS `yaml:"cors"`
}
// CORS defines the available cors configuration.
type CORS struct {
AllowedOrigins []string `yaml:"allow_origins" env:"OC_CORS_ALLOW_ORIGINS;OCDAV_CORS_ALLOW_ORIGINS" desc:"A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AllowedMethods []string `yaml:"allow_methods" env:"OC_CORS_ALLOW_METHODS;OCDAV_CORS_ALLOW_METHODS" desc:"A list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AllowedHeaders []string `yaml:"allow_headers" env:"OC_CORS_ALLOW_HEADERS;OCDAV_CORS_ALLOW_HEADERS" desc:"A list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AllowCredentials bool `yaml:"allow_credentials" env:"OC_CORS_ALLOW_CREDENTIALS;OCDAV_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials." introductionVersion:"1.0.0"`
}
// Status holds the configurable values for the status.php
type Status struct {
Version string
VersionString string
Product string
ProductName string
ProductVersion string
Edition string `desc:"Edition of OpenCloud. Used for branding purposes." introductionVersion:"1.0.0"`
}

View File

@@ -1,141 +0,0 @@
package defaults
import (
"github.com/opencloud-eu/opencloud/pkg/shared"
"github.com/opencloud-eu/opencloud/pkg/structs"
"github.com/opencloud-eu/opencloud/pkg/version"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
)
// FullDefaultConfig returns a fully initialized default configuration
func FullDefaultConfig() *config.Config {
cfg := DefaultConfig()
EnsureDefaults(cfg)
Sanitize(cfg)
return cfg
}
// DefaultConfig returns a basic default configuration
func DefaultConfig() *config.Config {
return &config.Config{
Debug: config.Debug{
Addr: "127.0.0.1:9163",
Token: "",
Pprof: false,
Zpages: false,
},
HTTP: config.HTTPConfig{
Addr: "127.0.0.1:9350",
Namespace: "eu.opencloud.web",
Protocol: "tcp",
Prefix: "",
CORS: config.CORS{
AllowedOrigins: []string{"https://localhost:9200"},
AllowedMethods: []string{
"OPTIONS",
"HEAD",
"GET",
"PUT",
"POST",
"DELETE",
"MKCOL",
"PROPFIND",
"PROPPATCH",
"MOVE",
"COPY",
"REPORT",
"SEARCH",
},
AllowedHeaders: []string{
"Origin",
"Accept",
"Content-Type",
"Depth",
"Authorization",
"Ocs-Apirequest",
"If-None-Match",
"If-Match",
"Destination",
"Overwrite",
"X-Request-Id",
"X-Requested-With",
"Tus-Resumable",
"Tus-Checksum-Algorithm",
"Upload-Concat",
"Upload-Length",
"Upload-Metadata",
"Upload-Defer-Length",
"Upload-Expires",
"Upload-Checksum",
"Upload-Offset",
"X-HTTP-Method-Override",
"Cache-Control",
},
AllowCredentials: false,
},
},
Service: config.Service{
Name: "ocdav",
},
Reva: shared.DefaultRevaConfig(),
WebdavNamespace: "/users/{{.Id.OpaqueId}}",
FilesNamespace: "/users/{{.Id.OpaqueId}}",
SharesNamespace: "/Shares",
OCMNamespace: "/public",
PublicURL: "https://localhost:9200",
Insecure: false,
Timeout: 84300,
MachineAuthAPIKey: "",
Status: config.Status{
Version: version.Legacy,
VersionString: version.LegacyString,
ProductVersion: version.GetString(),
Product: "OpenCloud",
ProductName: "OpenCloud",
Edition: version.Edition,
},
}
}
// EnsureDefaults adds default values to the configuration if they are not set yet
func EnsureDefaults(cfg *config.Config) {
// provide with defaults for shared logging, since we need a valid destination address for "envdecode".
if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil {
cfg.Log = &config.Log{
Level: cfg.Commons.Log.Level,
Pretty: cfg.Commons.Log.Pretty,
Color: cfg.Commons.Log.Color,
File: cfg.Commons.Log.File,
}
} else if cfg.Log == nil {
cfg.Log = &config.Log{}
}
if cfg.Reva == nil && cfg.Commons != nil {
cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.Reva)
}
if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil {
cfg.TokenManager = &config.TokenManager{
JWTSecret: cfg.Commons.TokenManager.JWTSecret,
}
} else if cfg.TokenManager == nil {
cfg.TokenManager = &config.TokenManager{}
}
if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" {
cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey
}
if (cfg.Commons != nil && cfg.Commons.OpenCloudURL != "") &&
(cfg.HTTP.CORS.AllowedOrigins == nil ||
len(cfg.HTTP.CORS.AllowedOrigins) == 1 &&
cfg.HTTP.CORS.AllowedOrigins[0] == "https://localhost:9200") {
cfg.HTTP.CORS.AllowedOrigins = []string{cfg.Commons.OpenCloudURL}
}
}
// Sanitize sanitizes the configuration
func Sanitize(cfg *config.Config) {
// nothing to sanitize here atm
}

View File

@@ -1,50 +0,0 @@
package parser
import (
"errors"
occfg "github.com/opencloud-eu/opencloud/pkg/config"
"github.com/opencloud-eu/opencloud/pkg/shared"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config/defaults"
"github.com/opencloud-eu/opencloud/pkg/config/envdecode"
)
// ParseConfig loads configuration from known paths.
func ParseConfig(cfg *config.Config) error {
err := occfg.BindSourcesToStructs(cfg.Service.Name, cfg)
if err != nil {
return err
}
defaults.EnsureDefaults(cfg)
// load all env variables relevant to the config in the current context.
if err := envdecode.Decode(cfg); err != nil {
// no environment variable set for this config is an expected "error"
if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
return err
}
}
defaults.Sanitize(cfg)
return Validate(cfg)
}
func Validate(cfg *config.Config) error {
if cfg.TokenManager.JWTSecret == "" {
return shared.MissingJWTTokenError(cfg.Service.Name)
}
if cfg.MachineAuthAPIKey == "" {
return shared.MissingMachineAuthApiKeyError(cfg.Service.Name)
}
if cfg.Commons.URLSigningSecret == "" {
return shared.MissingURLSigningSecret(cfg.Service.Name)
}
return nil
}

View File

@@ -1,6 +0,0 @@
package config
// TokenManager is the config for using the reva token manager
type TokenManager struct {
JWTSecret string `yaml:"jwt_secret" env:"OC_JWT_SECRET;OCDAV_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"1.0.0"`
}

View File

@@ -1,17 +0,0 @@
package logging
import (
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
)
// Configure initializes a service-specific logger instance.
func Configure(name string, cfg *config.Log) log.Logger {
return log.NewLogger(
log.Name(name),
log.Level(cfg.Level),
log.Pretty(cfg.Pretty),
log.Color(cfg.Color),
log.File(cfg.File),
)
}

View File

@@ -1,50 +0,0 @@
package debug
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/services/ocdav/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}

View File

@@ -1,27 +0,0 @@
package debug
import (
"net/http"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
return debug.NewService(
debug.Logger(options.Logger),
debug.Name(options.Config.Service.Name),
debug.Version(version.GetString()),
debug.Address(options.Config.Debug.Addr),
debug.Token(options.Config.Debug.Token),
debug.Pprof(options.Config.Debug.Pprof),
debug.Zpages(options.Config.Debug.Zpages),
//debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins),
//debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods),
//debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
//debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
), nil
}

View File

@@ -28,7 +28,7 @@ policies:
- endpoint: /
service: eu.opencloud.web.web
- endpoint: /dav/
service: eu.opencloud.web.ocdav
service: eu.opencloud.web.frontend
```
For adding _additional_ routes to the default routes use:

View File

@@ -223,33 +223,33 @@ func DefaultPolicies() []config.Policy {
},
{
Endpoint: "/remote.php/",
Service: "eu.opencloud.web.ocdav",
Service: "eu.opencloud.web.frontend",
},
{
Endpoint: "/dav/",
Service: "eu.opencloud.web.ocdav",
Service: "eu.opencloud.web.frontend",
},
{
Endpoint: "/webdav/",
Service: "eu.opencloud.web.ocdav",
Service: "eu.opencloud.web.frontend",
},
{
Endpoint: "/status",
Service: "eu.opencloud.web.ocdav",
Service: "eu.opencloud.web.frontend",
Unprotected: true,
},
{
Endpoint: "/status.php",
Service: "eu.opencloud.web.ocdav",
Service: "eu.opencloud.web.frontend",
Unprotected: true,
},
{
Endpoint: "/index.php/",
Service: "eu.opencloud.web.ocdav",
Service: "eu.opencloud.web.frontend",
},
{
Endpoint: "/apps/",
Service: "eu.opencloud.web.ocdav",
Service: "eu.opencloud.web.frontend",
},
{
Endpoint: "/data",
@@ -262,7 +262,7 @@ func DefaultPolicies() []config.Policy {
Unprotected: true,
},
{
Endpoint: "/app/", // /app or /apps? ocdav only handles /apps
Endpoint: "/app/", // /app or /apps? frontend only handles /apps
Service: "eu.opencloud.web.frontend",
},
{

View File

@@ -127,7 +127,7 @@ func TestRouter(t *testing.T) {
Name: "default",
Routes: []config.Route{
{Type: config.PrefixRoute, Endpoint: "/web/unprotected/demo/", Backend: "http://web", Unprotected: true},
{Type: config.PrefixRoute, Endpoint: "/dav", Backend: "http://ocdav"},
{Type: config.PrefixRoute, Endpoint: "/dav", Backend: "http://frontend"},
{Type: config.PrefixRoute, Method: "REPORT", Endpoint: "/dav", Backend: "http://opencloud-webdav"},
},
},
@@ -138,7 +138,7 @@ func TestRouter(t *testing.T) {
router := New(sel, policySelectorCfg, policies, log.NewLogger())
table := []matchertest{
{method: "PROPFIND", endpoint: "/dav/files/demo/", target: "ocdav"},
{method: "PROPFIND", endpoint: "/dav/files/demo/", target: "frontend"},
{method: "REPORT", endpoint: "/dav/files/demo/", target: "opencloud-webdav"},
{method: "GET", endpoint: "/web/unprotected/demo/", target: "web", unprotected: true},
}

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2025-11-12 00:01+0000\n"
"POT-Creation-Date: 2025-12-03 00:01+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"

View File

@@ -12,7 +12,7 @@ This service is part of the storage services family and is responsible for:
The storage-publiclink service integrates with:
- `sharing` service - Manages and persists public link shares
- `frontend` and `ocdav` - Provide HTTP/WebDAV access to public links
- `frontend` service - Provides HTTP/WebDAV access to public links
- Storage drivers - Accesses the actual file content
## Storage Registry

View File

@@ -13,7 +13,7 @@ This service is part of the storage services family and is responsible for:
The storage-shares service integrates with:
- `sharing` service - Manages and persists shares
- `storage-users` service - Accesses the underlying file content
- `frontend` and `ocdav` - Provide HTTP/WebDAV access to shares
- `frontend` service - Provides HTTP/WebDAV access to shares
## Virtual Shares Folder

View File

@@ -136,9 +136,6 @@ func (p Web) getPayload() (payload []byte, err error) {
p.config.Web.Config.Apps = make([]string, 0)
}
// ensure that the server url has a trailing slash
p.config.Web.Config.Server = strings.TrimRight(p.config.Web.Config.Server, "/") + "/"
return json.Marshal(p.config.Web.Config)
}

View File

@@ -1,6 +1,6 @@
# Webdav
The webdav service, like the [ocdav](../ocdav) service, provides a HTTP API following the webdav protocol. It receives HTTP calls from requestors like clients and issues gRPC calls to other services executing these requests. After the called service has finished the request, the webdav service will render their responses in `xml` and sends them back to the requestor.
The webdav service, like the [frontend](../frontend) service, provides a HTTP API following the webdav protocol. It receives HTTP calls from requestors like clients and issues gRPC calls to other services executing these requests. After the called service has finished the request, the webdav service will render their responses in `xml` and sends them back to the requestor.
## Endpoints Overview

View File

@@ -33,7 +33,6 @@ use SimpleXMLElement;
use Sabre\Xml\LibXMLException;
use Sabre\Xml\Reader;
use GuzzleHttp\Pool;
use Symfony\Component\HttpFoundation\Response;
/**
* Helper for HTTP requests
@@ -75,6 +74,7 @@ class HttpRequestHelper {
* than download it all up-front.
* @param int|null $timeout
* @param Client|null $client
* @param string|null $bearerToken
*
* @return ResponseInterface
* @throws GuzzleException
@@ -92,42 +92,8 @@ class HttpRequestHelper {
bool $stream = false,
?int $timeout = 0,
?Client $client = null,
?string $bearerToken = null
): ResponseInterface {
$bearerToken = null;
if (TokenHelper::useBearerToken() && $user && $user !== 'public') {
$bearerToken = TokenHelper::getTokens($user, $password, $url)['access_token'];
// check token is still valid
$parsedUrl = parse_url($url);
$baseUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
$baseUrl .= isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '';
$testUrl = $baseUrl . "/graph/v1.0/use/$user";
if (OcHelper::isTestingOnReva()) {
$url = $baseUrl . "/ocs/v2.php/cloud/users/$user";
}
// check token validity with a GET request
$c = self::createClient(
$user,
$password,
$config,
$cookies,
$stream,
$timeout,
$bearerToken
);
$testReq = self::createRequest($testUrl, $xRequestId, 'GET');
try {
$testRes = $c->send($testReq);
} catch (RequestException $ex) {
$testRes = $ex->getResponse();
if ($testRes && $testRes->getStatusCode() === Response::HTTP_UNAUTHORIZED) {
// token is invalid or expired, get a new one
echo "[INFO] Bearer token expired or invalid, getting a new one...\n";
TokenHelper::clearAllTokens();
$bearerToken = TokenHelper::getTokens($user, $password, $url)['access_token'];
}
}
}
if ($client === null) {
$client = self::createClient(
$user,
@@ -194,24 +160,6 @@ class HttpRequestHelper {
}
HttpLogger::logResponse($response);
// wait for post-processing to finish if applicable
if (WebdavHelper::isDAVRequest($url)
&& \str_starts_with($url, OcHelper::getServerUrl())
&& \in_array($method, ["PUT", "MOVE", "COPY"])
&& \in_array($response->getStatusCode(), [Response::HTTP_CREATED, Response::HTTP_NO_CONTENT])
&& OcConfigHelper::getPostProcessingDelay() === 0
) {
if (\in_array($method, ["MOVE", "COPY"])) {
$url = $headers['Destination'];
}
WebDavHelper::waitForPostProcessingToFinish(
$url,
$user,
$password,
$headers,
);
}
return $response;
}
@@ -255,6 +203,13 @@ class HttpRequestHelper {
} else {
$debugResponses = false;
}
// use basic auth for 'public' user or no user
if ($user === 'public' || $user === null || $user === '') {
$bearerToken = null;
} else {
$useBearerToken = TokenHelper::useBearerToken();
$bearerToken = $useBearerToken ? TokenHelper::getTokens($user, $password, $url)['access_token'] : null;
}
$sendRetryLimit = self::numRetriesOnHttpTooEarly();
$sendCount = 0;
@@ -273,6 +228,7 @@ class HttpRequestHelper {
$stream,
$timeout,
$client,
$bearerToken,
);
if ($response->getStatusCode() >= 400
@@ -300,8 +256,7 @@ class HttpRequestHelper {
// we need to repeat the send request, because we got HTTP_TOO_EARLY or HTTP_CONFLICT
// wait 1 second before sending again, to give the server some time
// to finish whatever post-processing it might be doing.
echo "[INFO] Received '" . $response->getStatusCode() .
"' status code, retrying request ($sendCount)...\n";
self::debugResponse($response);
\sleep(1);
}
} while ($loopAgain);

View File

@@ -30,26 +30,6 @@ use Psr\Http\Message\ResponseInterface;
* A helper class for configuring OpenCloud server
*/
class OcConfigHelper {
public static $postProcessingDelay = 0;
/**
* @return int
*/
public static function getPostProcessingDelay(): int {
return self::$postProcessingDelay;
}
/**
* @param string $postProcessingDelay
*
* @return void
*/
public static function setPostProcessingDelay(string $postProcessingDelay): void {
// extract number from string
$delay = (int) filter_var($postProcessingDelay, FILTER_SANITIZE_NUMBER_INT);
self::$postProcessingDelay = $delay;
}
/**
* @param string $url
* @param string $method

View File

@@ -84,9 +84,7 @@ class TokenHelper {
$tokenData = [
'access_token' => $refreshedToken['access_token'],
'refresh_token' => $refreshedToken['refresh_token'],
// set expiry to 240 (4 minutes) seconds to allow for some buffer
// token actually expires in 300 seconds (5 minutes)
'expires_at' => time() + 240
'expires_at' => time() + 300 // 5 minutes
];
self::$tokenCache[$cacheKey] = $tokenData;
return $tokenData;
@@ -102,9 +100,7 @@ class TokenHelper {
$tokenData = [
'access_token' => $tokens['access_token'],
'refresh_token' => $tokens['refresh_token'],
// set expiry to 240 (4 minutes) seconds to allow for some buffer
// token actually expires in 300 seconds (5 minutes)
'expires_at' => time() + 240
'expires_at' => time() + 290 // set expiry to 290 seconds to allow for some buffer
];
// Save to cache

View File

@@ -923,45 +923,4 @@ class WebDavHelper {
$mtime = new DateTime($xmlPart[0]->__toString());
return $mtime->format('U');
}
/**
* wait until the reqeust doesn't return 425 anymore
*
* @param string $url
* @param ?string $user
* @param ?string $password
* @param ?array $headers
*
* @return void
*/
public static function waitForPostProcessingToFinish(
string $url,
?string $user = null,
?string $password = null,
?array $headers = [],
): void {
$retried = 0;
do {
$response = HttpRequestHelper::sendRequest(
$url,
'check-425-status',
'GET',
$user,
$password,
$headers,
);
$statusCode = $response->getStatusCode();
if ($statusCode !== 425) {
return;
}
$tryAgain = $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
if ($tryAgain) {
$retried += 1;
echo "[INFO] Waiting for post processing to finish, attempt ($retried)...\n";
// wait 1s and try again
\sleep(1);
}
} while ($tryAgain);
echo "[ERROR] 10 seconds timeout! Post processing did not finish in time.\n";
}
}

View File

@@ -2026,12 +2026,8 @@ class FeatureContext extends BehatVariablesContext {
if ($response === null) {
$response = $this->getResponse();
}
$body = (string)$response->getBody();
if (!$body) {
return [];
}
return \json_decode(
$body,
(string)$response->getBody(),
true
);
}

View File

@@ -68,7 +68,6 @@ class OcConfigContext implements Context {
$response->getStatusCode(),
"Failed to set async upload with delayed post processing"
);
OcConfigHelper::setPostProcessingDelay($delayTime);
}
/**
@@ -91,9 +90,6 @@ class OcConfigContext implements Context {
$response->getStatusCode(),
"Failed to set config $configVariable=$configValue"
);
if ($configVariable === "POSTPROCESSING_DELAY") {
OcConfigHelper::setPostProcessingDelay($configValue);
}
}
/**
@@ -188,9 +184,6 @@ class OcConfigContext implements Context {
$envs = [];
foreach ($table->getHash() as $row) {
$envs[$row['config']] = $row['value'];
if ($row['config'] === "POSTPROCESSING_DELAY") {
OcConfigHelper::setPostProcessingDelay($row['value']);
}
}
$response = OcConfigHelper::reConfigureOc($envs);
@@ -207,7 +200,6 @@ class OcConfigContext implements Context {
* @return void
*/
public function rollbackOc(): void {
OcConfigHelper::setPostProcessingDelay('0');
$response = OcConfigHelper::rollbackOc();
Assert::assertEquals(
200,

View File

@@ -607,7 +607,7 @@ trait Provisioning {
Assert::assertEquals(
201,
$response->getStatusCode(),
__METHOD__ . " cannot create user '$userName'.\nResponse:" .
__METHOD__ . " cannot create user '$userName' using Graph API.\nResponse:" .
json_encode($this->getJsonDecodedResponse($response))
);
@@ -1083,7 +1083,7 @@ trait Provisioning {
Assert::assertEquals(
201,
$response->getStatusCode(),
__METHOD__ . " cannot create user '$user'.\nResponse:" .
__METHOD__ . " cannot create user '$user' using Graph API.\nResponse:" .
json_encode($this->getJsonDecodedResponse($response))
);
$userId = $this->getJsonDecodedResponse($response)['id'];

View File

@@ -750,9 +750,6 @@ class SpacesContext implements Context {
} else {
$rawBody = $this->featureContext->getResponse()->getBody()->getContents();
}
if (!$rawBody) {
throw new Exception(__METHOD__ . " - Response body is empty");
}
$drives = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
if (isset($drives["value"])) {
$drives = $drives["value"];

View File

@@ -216,44 +216,6 @@ class TUSContext implements Context {
);
}
/**
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data with retry on offset mismatch using the WebDAV API
*
* @param string $user
* @param string $offset
* @param string $data
*
* @return void
*
* @throws GuzzleException
* @throws JsonException
*/
public function userSendsAChunkToTUSLocationWithOffsetAndDataWithRetryOnOffsetMismatch(
string $user,
string $offset,
string $data,
): void {
$resourceLocation = $this->getLastTusResourceLocation();
$retried = 0;
do {
$tryAgain = false;
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data);
// retry on 409 Conflict (Offset mismatch during TUS upload)
if ($response->getStatusCode() === 409) {
$tryAgain = true;
}
$tryAgain = $tryAgain && $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
if ($tryAgain) {
$retried += 1;
echo "Offset mismatch during TUS upload, retrying ($retried)...\n";
// wait 1s and try again
\sleep(1);
}
} while ($tryAgain);
$this->featureContext->setResponse($response);
}
/**
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data using the WebDAV API
*

View File

@@ -25,7 +25,6 @@ use GuzzleHttp\Exception\GuzzleException;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Stream\StreamInterface;
use TestHelpers\OcConfigHelper;
use TestHelpers\OcHelper;
use TestHelpers\UploadHelper;
use TestHelpers\WebDavHelper;
@@ -744,7 +743,6 @@ trait WebDav {
/**
* @When the user waits for :time seconds for postprocessing to finish
* @When the user waits for :time seconds
*
* @param int $time
*
@@ -975,61 +973,6 @@ trait WebDav {
$this->checkDownloadedContentMatches($content, '', $response);
}
/**
* check file content with retry
*
* @param string $user
* @param string $fileName
* @param string $content
*
* @return void
* @throws Exception
*/
public function checkFileContentWithRetry(string $user, string $fileName, string $content): void {
$retried = 0;
do {
$tryAgain = false;
$response = $this->downloadFileAsUserUsingPassword($this->getActualUsername($user), $fileName);
$status = $response->getStatusCode();
$downloadedContent = $response->getBody()->getContents();
if ($status !== 200) {
$tryAgain = true;
$message = "Expected '200' but got '$status'";
} elseif ($downloadedContent !== $content) {
$tryAgain = true;
$message = "Expected content '$content' but got '$downloadedContent'";
}
$tryAgain = $tryAgain && $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
if ($tryAgain) {
$retried += 1;
echo "[INFO] File content mismatch. $message, checking content again ($retried)...\n";
// break the loop if status is 425 as the request will already be retried
if ($status === HttpRequestHelper::HTTP_TOO_EARLY) {
break;
}
// wait 1s and try again
\sleep(1);
}
} while ($tryAgain);
$this->theHTTPStatusCodeShouldBe(200, '', $response);
$this->checkDownloadedContentMatches($content, '', $response);
}
/**
* @Then as :user the final content of file :fileName should be :content
*
* @param string $user
* @param string $fileName
* @param string $content
*
* @return void
*/
public function asUserFinalContentOfFileShouldBe(string $user, string $fileName, string $content): void {
$this->checkFileContentWithRetry($user, $fileName, $content);
}
/**
* @Then /^the content of the following files for user "([^"]*)" should be "([^"]*)"$/
*
@@ -1321,18 +1264,20 @@ trait WebDav {
$type
);
$statusCode = $response->getStatusCode();
// when checking path with '..' it may return 405 Method Not Allowed
if ($statusCode === 404 || $statusCode === 405) {
return;
}
if ($statusCode === 207) {
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
if ($statusCode < 400 || $statusCode > 499) {
try {
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
} catch (Exception $e) {
Assert::fail(
"$entry '$path' should not exist. But API returned $statusCode without XML in the body"
);
}
Assert::assertTrue(
$this->isEtagValid($this->getEtagFromResponseXmlObject($responseXmlObject)),
"$entry '$path' should not exist but found with invalid etag."
"$entry '$path' should not exist. But API returned $statusCode without an etag in the body"
);
$isCollection = $responseXmlObject->xpath("//d:prop/d:resourcetype/d:collection");
if (\count($isCollection) === 0) {
@@ -1346,11 +1291,7 @@ trait WebDav {
"$entry '$path' should not exist. But it does."
);
}
return;
}
Assert::fail(
"$entry '$path' should not exist. But API returned $statusCode without XML in the body"
);
}
/**
@@ -2329,11 +2270,6 @@ trait WebDav {
"HTTP status code was not 201 or 204 while trying to upload file '$destination' for user '$user'",
$response
);
// check uploaded content only if post-processing delay is not configured
if (OcConfigHelper::getPostProcessingDelay() === 0) {
$this->checkFileContentWithRetry($user, $destination, $content);
}
return $response->getHeader('oc-fileid');
}

View File

@@ -190,7 +190,7 @@
#### [Readiness check for some services returns 500 status code](https://github.com/owncloud/ocis/issues/10661)
- [apiServiceAvailability/serviceAvailabilityCheck.feature:125](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L125)
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
#### [Skip tests for different languages](https://github.com/opencloud-eu/opencloud/issues/183)
- [apiActivities/activities.feature:2598](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiActivities/activities.feature#L2598)

View File

@@ -190,7 +190,7 @@
#### [Readiness check for some services returns 500 status code](https://github.com/owncloud/ocis/issues/10661)
- [apiServiceAvailability/serviceAvailabilityCheck.feature:125](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L125)
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
#### [Skip tests for different languages](https://github.com/opencloud-eu/opencloud/issues/183)
- [apiActivities/activities.feature:2598](https://github.com/opencloud-eu/opencloud/blob/main/tests/acceptance/features/apiActivities/activities.feature#L2598)

View File

@@ -19,7 +19,6 @@ Feature: service health check
| http://%base_url_hostname%:9239/healthz | idm |
| http://%base_url_hostname%:9134/healthz | idp |
| http://%base_url_hostname%:9234/healthz | nats |
| http://%base_url_hostname%:9163/healthz | ocdav |
| http://%base_url_hostname%:9281/healthz | ocm |
| http://%base_url_hostname%:9114/healthz | ocs |
| http://%base_url_hostname%:9255/healthz | postprocessing |
@@ -74,7 +73,6 @@ Feature: service health check
| http://%base_url_hostname%:9161/readyz | groups |
| http://%base_url_hostname%:9239/readyz | idm |
| http://%base_url_hostname%:9234/readyz | nats |
| http://%base_url_hostname%:9163/readyz | ocdav |
| http://%base_url_hostname%:9281/readyz | ocm |
| http://%base_url_hostname%:9114/readyz | ocs |
| http://%base_url_hostname%:9255/readyz | postprocessing |

View File

@@ -29,7 +29,7 @@ Feature: create a resources using collaborative posixfs
Scenario: create file
When the administrator creates the file "test.txt" with content "content" for user "Alice" on the POSIX filesystem
Then the command should be successful
And as "Alice" the final content of file "test.txt" should be "content"
And the content of file "/test.txt" for user "Alice" should be "content"
Scenario: create large file
@@ -41,22 +41,21 @@ Feature: create a resources using collaborative posixfs
Scenario: creates files sequentially in a folder
When the administrator creates 50 files sequentially in the directory "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And as "Alice" the final content of file "/firstFolder/file_1.txt" should be "file 1 content"
And as "Alice" the final content of file "/firstFolder/file_50.txt" should be "file 50 content"
And the content of file "/firstFolder/file_1.txt" for user "Alice" should be "file 1 content"
And the content of file "/firstFolder/file_50.txt" for user "Alice" should be "file 50 content"
Scenario: creates files in parallel in a folder
When the administrator creates 100 files in parallel in the directory "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And as "Alice" the final content of file "/firstFolder/parallel_1.txt" should be "parallel file 1 content"
And as "Alice" the final content of file "/firstFolder/parallel_100.txt" should be "parallel file 100 content"
And the content of file "/firstFolder/parallel_1.txt" for user "Alice" should be "parallel file 1 content"
And the content of file "/firstFolder/parallel_100.txt" for user "Alice" should be "parallel file 100 content"
Scenario: edit file
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator puts the content "new" into the file "test.txt" in the POSIX storage folder of user "Alice"
Then the command should be successful
And as "Alice" the final content of file "test.txt" should be "contentnew"
Then the content of file "/test.txt" for user "Alice" should be "contentnew"
Scenario: read file content
@@ -69,28 +68,43 @@ Feature: create a resources using collaborative posixfs
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator copies the file "test.txt" to the folder "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And as "Alice" the final content of file "/firstFolder/test.txt" should be "content"
And the content of file "/firstFolder/test.txt" for user "Alice" should be "content"
Scenario: rename file
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator renames the file "test.txt" to "new-name.txt" for user "Alice" on the POSIX filesystem
Then the command should be successful
And as "Alice" the final content of file "/new-name.txt" should be "content"
And the content of file "/new-name.txt" for user "Alice" should be "content"
Scenario: check propfind after rename file
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator renames the file "test.txt" to "new-name.txt" for user "Alice" on the POSIX filesystem
Then the command should be successful
When user "Alice" sends PROPFIND request to space "Personal" using the WebDAV API
Then the HTTP status code should be "207"
And as user "Alice" the PROPFIND response should contain a resource "new-name.txt" with these key and value pairs:
| key | value |
| oc:fileid | %file_id_pattern% |
| oc:name | new-name.txt |
| oc:permissions | RDNVWZP |
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
| oc:size | 7 |
Scenario: rename a created file
Given the administrator has created the file "test.txt" with content "content" for user "Alice" on the POSIX filesystem
When the administrator renames the file "test.txt" to "test.md" for user "Alice" on the POSIX filesystem
Then the command should be successful
And as "Alice" the final content of file "/test.md" should be "content"
And the content of file "/test.md" for user "Alice" should be "content"
Scenario: move file to folder
Given user "Alice" has uploaded file with content "content" to "test.txt"
When the administrator moves the file "test.txt" to the folder "firstFolder" for user "Alice" on the POSIX filesystem
Then the command should be successful
And as "Alice" the final content of file "/firstFolder/test.txt" should be "content"
And the content of file "/firstFolder/test.txt" for user "Alice" should be "content"
And as "Alice" file "/test.txt" should not exist
@@ -188,4 +202,4 @@ Feature: create a resources using collaborative posixfs
And the administrator renames the file "test.txt" to "renamed.txt" for user "Alice" on the POSIX filesystem
And the administrator checks the attribute "user.oc.name" of file "renamed.txt" for user "Alice" on the POSIX filesystem
Then the command output should contain "renamed.txt"
And as "Alice" the final content of file "/renamed.txt" should be "content"
And the content of file "/renamed.txt" for user "Alice" should be "content"

View File

@@ -202,7 +202,7 @@ Feature: capabilities
"properties": {
"edition": {
"type": "string",
"enum": ["dev"]
"enum": ["%edition%"]
},
"product": {
"type": "string",
@@ -240,7 +240,7 @@ Feature: capabilities
},
"edition": {
"type": "string",
"enum": ["dev"]
"enum": ["%edition%"]
},
"product": {
"type": "string",

View File

@@ -58,7 +58,7 @@ Feature: default capabilities for normal user
"const": "%versionstring%"
},
"edition": {
"const": "dev"
"const": "%edition%"
},
"productname": {
"const": "%productname%"

View File

@@ -50,7 +50,8 @@ Feature: low level tests for upload of chunks
| Upload-Metadata | filename ZmlsZS50eHQ= |
When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "4567890" using the WebDAV API
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "0000000" with retry on offset mismatch using the WebDAV API
And the user waits for "2" seconds for postprocessing to finish
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "0000000" using the WebDAV API
Then the HTTP status code should be "404"
And the content of file "/file.txt" for user "Alice" should be "1234567890"
Examples:
@@ -60,22 +61,6 @@ Feature: low level tests for upload of chunks
| spaces |
Scenario Outline: send last chunk with mismatch offset
Given using <dav-path-version> DAV path
And user "Alice" has created a new TUS resource on the WebDAV API with these headers:
| Upload-Length | 10 |
# ZmlsZS50eHQ= is the base64 encode of file.txt
| Upload-Metadata | filename ZmlsZS50eHQ= |
When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API
And user "Alice" sends a chunk to the last created TUS Location with offset "2" and data "34567890" using the WebDAV API
Then the HTTP status code should be "409"
Examples:
| dav-path-version |
| old |
| new |
| spaces |
Scenario Outline: start with uploading not at the beginning of the file
Given using <dav-path-version> DAV path
And user "Alice" has created a new TUS resource on the WebDAV API with these headers:

View File

@@ -45,8 +45,6 @@ export IDP_DEBUG_ADDR=127.0.0.1:10134
export IDP_HTTP_ADDR=127.0.0.1:10130
export NATS_DEBUG_ADDR=127.0.0.1:10234
export NATS_NATS_PORT=10233
export OCDAV_HTTP_ADDR=127.0.0.1:10350
export OCDAV_DEBUG_ADDR=127.0.0.1:10163
export OCM_DEBUG_ADDR=127.0.0.1:10281
export OCM_HTTP_ADDR=127.0.0.1:10280
export OCM_GRPC_ADDR=127.0.0.1:10282

View File

@@ -81,7 +81,6 @@ info:
{
"username": "string",
"password": "string",
"email": "string",
"serveraddress": "string"
}
```
@@ -637,6 +636,9 @@ definitions:
by the default (runc) runtime.
This field is omitted when empty.
**Deprecated**: This field is deprecated as kernel 6.12 has deprecated `memory.kmem.tcp.limit_in_bytes` field
for cgroups v1. This field will be removed in a future release.
type: "integer"
format: "int64"
MemoryReservation:
@@ -1531,37 +1533,6 @@ definitions:
items:
type: "string"
example: ["/bin/sh", "-c"]
# FIXME(thaJeztah): temporarily using a full example to remove some "omitempty" fields. Remove once the fields are removed.
example:
"User": "web:web"
"ExposedPorts": {
"80/tcp": {},
"443/tcp": {}
}
"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]
"Cmd": ["/bin/sh"]
"Healthcheck": {
"Test": ["string"],
"Interval": 0,
"Timeout": 0,
"Retries": 0,
"StartPeriod": 0,
"StartInterval": 0
}
"ArgsEscaped": true
"Volumes": {
"/app/data": {},
"/app/config": {}
}
"WorkingDir": "/public/"
"Entrypoint": []
"OnBuild": []
"Labels": {
"com.example.some-label": "some-value",
"com.example.some-other-label": "some-other-value"
}
"StopSignal": "SIGTERM"
"Shell": ["/bin/sh", "-c"]
NetworkingConfig:
description: |
@@ -1608,6 +1579,8 @@ definitions:
Bridge:
description: |
Name of the default bridge interface when dockerd's --bridge flag is set.
Deprecated: This field is only set when the daemon is started with the --bridge flag specified.
type: "string"
example: "docker0"
SandboxID:
@@ -1965,6 +1938,11 @@ definitions:
Depending on how the image was created, this field may be empty and
is only set for images that were built/created locally. This field
is empty if the image was pulled from an image registry.
> **Deprecated**: This field is only set when using the deprecated
> legacy builder. It is included in API responses for informational
> purposes, but should not be depended on as it will be omitted
> once the legacy builder is removed.
type: "string"
x-nullable: false
example: ""
@@ -1990,6 +1968,11 @@ definitions:
The version of Docker that was used to build the image.
Depending on how the image was created, this field may be empty.
> **Deprecated**: This field is only set when using the deprecated
> legacy builder. It is included in API responses for informational
> purposes, but should not be depended on as it will be omitted
> once the legacy builder is removed.
type: "string"
x-nullable: false
example: "27.0.1"
@@ -2034,14 +2017,6 @@ definitions:
format: "int64"
x-nullable: false
example: 1239828
VirtualSize:
description: |
Total size of the image including all layers it is composed of.
Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead.
type: "integer"
format: "int64"
example: 1239828
GraphDriver:
$ref: "#/definitions/DriverData"
RootFS:
@@ -2174,14 +2149,6 @@ definitions:
format: "int64"
x-nullable: false
example: 1239828
VirtualSize:
description: |-
Total size of the image including all layers it is composed of.
Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead.
type: "integer"
format: "int64"
example: 172064416
Labels:
description: "User-defined key/value metadata."
type: "object"
@@ -2234,6 +2201,10 @@ definitions:
password:
type: "string"
email:
description: |
Email is an optional value associated with the username.
> **Deprecated**: This field is deprecated since docker 1.11 (API v1.23) and will be removed in a future release.
type: "string"
serveraddress:
type: "string"
@@ -3171,10 +3142,15 @@ definitions:
- Args
properties:
DockerVersion:
description: "Docker Version used to create the plugin"
description: |-
Docker Version used to create the plugin.
Depending on how the plugin was created, this field may be empty or omitted.
Deprecated: this field is no longer set, and will be removed in the next API version.
type: "string"
x-nullable: false
example: "17.06.0-ce"
x-omitempty: true
Description:
type: "string"
x-nullable: false
@@ -4392,6 +4368,7 @@ definitions:
A counter that triggers an update even if no relevant parameters have
been changed.
type: "integer"
format: "uint64"
Runtime:
description: |
Runtime is the type of runtime specified for the task executor.
@@ -6375,6 +6352,8 @@ definitions:
Kernel memory TCP limits are not supported when using cgroups v2, which
does not support the corresponding `memory.kmem.tcp.limit_in_bytes` cgroup.
**Deprecated**: This field is deprecated as kernel 6.12 has deprecated kernel memory TCP accounting.
type: "boolean"
example: true
CpuCfsPeriod:
@@ -6412,29 +6391,6 @@ definitions:
description: "Indicates IPv4 forwarding is enabled."
type: "boolean"
example: true
BridgeNfIptables:
description: |
Indicates if `bridge-nf-call-iptables` is available on the host when
the daemon was started.
<p><br /></p>
> **Deprecated**: netfilter module is now loaded on-demand and no longer
> during daemon startup, making this field obsolete. This field is always
> `false` and will be removed in a API v1.49.
type: "boolean"
example: false
BridgeNfIp6tables:
description: |
Indicates if `bridge-nf-call-ip6tables` is available on the host.
<p><br /></p>
> **Deprecated**: netfilter module is now loaded on-demand, and no longer
> during daemon startup, making this field obsolete. This field is always
> `false` and will be removed in a API v1.49.
type: "boolean"
example: false
Debug:
description: |
Indicates if the daemon is running in debug-mode / with debug-level

View File

@@ -1,6 +1,8 @@
package build
// CacheDiskUsage contains disk usage for the build cache.
//
// Deprecated: this type is no longer used and will be removed in the next release.
type CacheDiskUsage struct {
TotalSize int64
Reclaimable int64

View File

@@ -1,6 +1,8 @@
package container
// DiskUsage contains disk usage for containers.
//
// Deprecated: this type is no longer used and will be removed in the next release.
type DiskUsage struct {
TotalSize int64
Reclaimable int64

View File

@@ -394,7 +394,12 @@ type Resources struct {
// KernelMemory specifies the kernel memory limit (in bytes) for the container.
// Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes.
KernelMemory int64 `json:",omitempty"`
KernelMemory int64 `json:",omitempty"`
// Hard limit for kernel TCP buffer memory (in bytes).
//
// Deprecated: This field is deprecated and will be removed in the next release.
// Starting with 6.12, the kernel has deprecated kernel memory tcp accounting
// for cgroups v1.
KernelMemoryTCP int64 `json:",omitempty"` // Hard limit for kernel TCP buffer memory (in bytes)
MemoryReservation int64 // Memory soft limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap

View File

@@ -13,8 +13,11 @@ type NetworkSettings struct {
}
// NetworkSettingsBase holds networking state for a container when inspecting it.
//
// Deprecated: Most fields in NetworkSettingsBase are deprecated. Fields which aren't deprecated will move to
// NetworkSettings in v29.0, and this struct will be removed.
type NetworkSettingsBase struct {
Bridge string // Bridge contains the name of the default bridge interface iff it was set through the daemon --bridge flag.
Bridge string // Deprecated: This field is only set when the daemon is started with the --bridge flag specified.
SandboxID string // SandboxID uniquely represents a container's network stack
SandboxKey string // SandboxKey identifies the sandbox
Ports nat.PortMap // Ports is a collection of PortBinding indexed by Port
@@ -35,18 +38,44 @@ type NetworkSettingsBase struct {
SecondaryIPv6Addresses []network.Address // Deprecated: This field is never set and will be removed in a future release.
}
// DefaultNetworkSettings holds network information
// during the 2 release deprecation period.
// It will be removed in Docker 1.11.
// DefaultNetworkSettings holds the networking state for the default bridge, if the container is connected to that
// network.
//
// Deprecated: this struct is deprecated since Docker v1.11 and will be removed in v29. You should look for the default
// network in NetworkSettings.Networks instead.
type DefaultNetworkSettings struct {
EndpointID string // EndpointID uniquely represents a service endpoint in a Sandbox
Gateway string // Gateway holds the gateway address for the network
GlobalIPv6Address string // GlobalIPv6Address holds network's global IPv6 address
GlobalIPv6PrefixLen int // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address
IPAddress string // IPAddress holds the IPv4 address for the network
IPPrefixLen int // IPPrefixLen represents mask length of network's IPv4 address
IPv6Gateway string // IPv6Gateway holds gateway address specific for IPv6
MacAddress string // MacAddress holds the MAC address for the network
// EndpointID uniquely represents a service endpoint in a Sandbox
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
EndpointID string
// Gateway holds the gateway address for the network
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
Gateway string
// GlobalIPv6Address holds network's global IPv6 address
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
GlobalIPv6Address string
// GlobalIPv6PrefixLen represents mask length of network's global IPv6 address
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
GlobalIPv6PrefixLen int
// IPAddress holds the IPv4 address for the network
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
IPAddress string
// IPPrefixLen represents mask length of network's IPv4 address
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
IPPrefixLen int
// IPv6Gateway holds gateway address specific for IPv6
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
IPv6Gateway string
// MacAddress holds the MAC address for the network
//
// Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead.
MacAddress string
}
// NetworkSettingsSummary provides a summary of container's networks

View File

@@ -0,0 +1,61 @@
package filters
import (
"encoding/json"
"github.com/docker/docker/api/types/versions"
)
// ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22
// then the encoded format will use an older legacy format where the values are a
// list of strings, instead of a set.
//
// Deprecated: do not use in any new code; use ToJSON instead
func ToParamWithVersion(version string, a Args) (string, error) {
out, err := ToJSON(a)
if out == "" || err != nil {
return "", nil
}
if version != "" && versions.LessThan(version, "1.22") {
return encodeLegacyFilters(out)
}
return out, nil
}
// encodeLegacyFilters encodes Args in the legacy format as used in API v1.21 and older.
// where values are a list of strings, instead of a set.
//
// Don't use in any new code; use [filters.ToJSON]] instead.
func encodeLegacyFilters(currentFormat string) (string, error) {
// The Args.fields field is not exported, but used to marshal JSON,
// so we'll marshal to the new format, then unmarshal to get the
// fields, and marshal again.
//
// This is far from optimal, but this code is only used for deprecated
// API versions, so should not be hit commonly.
var argsFields map[string]map[string]bool
err := json.Unmarshal([]byte(currentFormat), &argsFields)
if err != nil {
return "", err
}
buf, err := json.Marshal(convertArgsToSlice(argsFields))
if err != nil {
return "", err
}
return string(buf), nil
}
func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
m := map[string][]string{}
for k, v := range f {
values := []string{}
for kk := range v {
if v[kk] {
values = append(values, kk)
}
}
m[k] = values
}
return m
}

View File

@@ -8,8 +8,6 @@ import (
"encoding/json"
"regexp"
"strings"
"github.com/docker/docker/api/types/versions"
)
// Args stores a mapping of keys to a set of multiple values.
@@ -63,24 +61,6 @@ func ToJSON(a Args) (string, error) {
return string(buf), err
}
// ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22
// then the encoded format will use an older legacy format where the values are a
// list of strings, instead of a set.
//
// Deprecated: do not use in any new code; use ToJSON instead
func ToParamWithVersion(version string, a Args) (string, error) {
if a.Len() == 0 {
return "", nil
}
if version != "" && versions.LessThan(version, "1.22") {
buf, err := json.Marshal(convertArgsToSlice(a.fields))
return string(buf), err
}
return ToJSON(a)
}
// FromJSON decodes a JSON encoded string into Args
func FromJSON(p string) (Args, error) {
args := NewArgs()
@@ -320,17 +300,3 @@ func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
}
return m
}
func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
m := map[string][]string{}
for k, v := range f {
values := []string{}
for kk := range v {
if v[kk] {
values = append(values, kk)
}
}
m[k] = values
}
return m
}

View File

@@ -1,6 +1,8 @@
package image
// DiskUsage contains disk usage for images.
//
// Deprecated: this type is no longer used and will be removed in the next release.
type DiskUsage struct {
TotalSize int64
Reclaimable int64

View File

@@ -48,6 +48,8 @@ type InspectResponse struct {
// Depending on how the image was created, this field may be empty and
// is only set for images that were built/created locally. This field
// is empty if the image was pulled from an image registry.
//
// Deprecated: this field is deprecated, and will be removed in the next release.
Parent string
// Comment is an optional message that can be set when committing or
@@ -80,6 +82,8 @@ type InspectResponse struct {
// DockerVersion is the version of Docker that was used to build the image.
//
// Depending on how the image was created, this field may be empty.
//
// Deprecated: this field is deprecated, and will be removed in the next release.
DockerVersion string
// Author is the name of the author that was specified when committing the

View File

@@ -4,8 +4,6 @@ import (
"errors"
"fmt"
"net"
"github.com/docker/docker/internal/multierror"
)
// EndpointSettings stores the network endpoint details
@@ -99,7 +97,7 @@ func (cfg *EndpointIPAMConfig) IsInRange(v4Subnets []NetworkSubnet, v6Subnets []
errs = append(errs, err)
}
return multierror.Join(errs...)
return errJoin(errs...)
}
func validateEndpointIPAddress(epAddr string, ipamSubnets []NetworkSubnet) error {
@@ -149,5 +147,5 @@ func (cfg *EndpointIPAMConfig) Validate() error {
}
}
return multierror.Join(errs...)
return errJoin(errs...)
}

View File

@@ -4,8 +4,7 @@ import (
"errors"
"fmt"
"net/netip"
"github.com/docker/docker/internal/multierror"
"strings"
)
// IPAM represents IP Address Management
@@ -72,7 +71,7 @@ func ValidateIPAM(ipam *IPAM, enableIPv6 bool) error {
}
}
if err := multierror.Join(errs...); err != nil {
if err := errJoin(errs...); err != nil {
return fmt.Errorf("invalid network config:\n%w", err)
}
@@ -132,3 +131,43 @@ func validateAddress(address string, subnet netip.Prefix, subnetFamily ipFamily)
return nil
}
func errJoin(errs ...error) error {
n := 0
for _, err := range errs {
if err != nil {
n++
}
}
if n == 0 {
return nil
}
e := &joinError{
errs: make([]error, 0, n),
}
for _, err := range errs {
if err != nil {
e.errs = append(e.errs, err)
}
}
return e
}
type joinError struct {
errs []error
}
func (e *joinError) Error() string {
if len(e.errs) == 1 {
return strings.TrimSpace(e.errs[0].Error())
}
stringErrs := make([]string, 0, len(e.errs))
for _, subErr := range e.errs {
stringErrs = append(stringErrs, strings.ReplaceAll(subErr.Error(), "\n", "\n\t"))
}
return "* " + strings.Join(stringErrs, "\n* ")
}
func (e *joinError) Unwrap() []error {
return e.errs
}

View File

@@ -42,7 +42,11 @@ type PluginConfig struct {
// Required: true
Description string `json:"Description"`
// Docker Version used to create the plugin
// Docker Version used to create the plugin.
//
// Depending on how the plugin was created, this field may be empty or omitted.
//
// Deprecated: this field is no longer set, and will be removed in the next API version.
DockerVersion string `json:"DockerVersion,omitempty"`
// documentation

View File

@@ -32,8 +32,8 @@ type AuthConfig struct {
Auth string `json:"auth,omitempty"`
// Email is an optional value associated with the username.
// This field is deprecated and will be removed in a later
// version of docker.
//
// Deprecated: This field is deprecated since docker 1.11 (API v1.23) and will be removed in the next release.
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`

View File

@@ -1,5 +1,7 @@
package swarm
import "github.com/docker/docker/api/types/swarm/runtime"
// RuntimeType is the type of runtime used for the TaskSpec
type RuntimeType string
@@ -25,3 +27,11 @@ const (
type NetworkAttachmentSpec struct {
ContainerID string
}
// RuntimeSpec defines the base payload which clients can specify for creating
// a service with the plugin runtime.
type RuntimeSpec = runtime.PluginSpec
// RuntimePrivilege describes a permission the user has to accept
// upon installing a plugin.
type RuntimePrivilege = runtime.PluginPrivilege

View File

@@ -1,3 +0,0 @@
//go:generate protoc --gogofaster_out=import_path=github.com/docker/docker/api/types/swarm/runtime:. plugin.proto
package runtime

View File

@@ -1,808 +0,0 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: plugin.proto
package runtime
import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// PluginSpec defines the base payload which clients can specify for creating
// a service with the plugin runtime.
type PluginSpec struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Remote string `protobuf:"bytes,2,opt,name=remote,proto3" json:"remote,omitempty"`
Privileges []*PluginPrivilege `protobuf:"bytes,3,rep,name=privileges,proto3" json:"privileges,omitempty"`
Disabled bool `protobuf:"varint,4,opt,name=disabled,proto3" json:"disabled,omitempty"`
Env []string `protobuf:"bytes,5,rep,name=env,proto3" json:"env,omitempty"`
}
func (m *PluginSpec) Reset() { *m = PluginSpec{} }
func (m *PluginSpec) String() string { return proto.CompactTextString(m) }
func (*PluginSpec) ProtoMessage() {}
func (*PluginSpec) Descriptor() ([]byte, []int) {
return fileDescriptor_22a625af4bc1cc87, []int{0}
}
func (m *PluginSpec) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *PluginSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_PluginSpec.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *PluginSpec) XXX_Merge(src proto.Message) {
xxx_messageInfo_PluginSpec.Merge(m, src)
}
func (m *PluginSpec) XXX_Size() int {
return m.Size()
}
func (m *PluginSpec) XXX_DiscardUnknown() {
xxx_messageInfo_PluginSpec.DiscardUnknown(m)
}
var xxx_messageInfo_PluginSpec proto.InternalMessageInfo
func (m *PluginSpec) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *PluginSpec) GetRemote() string {
if m != nil {
return m.Remote
}
return ""
}
func (m *PluginSpec) GetPrivileges() []*PluginPrivilege {
if m != nil {
return m.Privileges
}
return nil
}
func (m *PluginSpec) GetDisabled() bool {
if m != nil {
return m.Disabled
}
return false
}
func (m *PluginSpec) GetEnv() []string {
if m != nil {
return m.Env
}
return nil
}
// PluginPrivilege describes a permission the user has to accept
// upon installing a plugin.
type PluginPrivilege struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
Value []string `protobuf:"bytes,3,rep,name=value,proto3" json:"value,omitempty"`
}
func (m *PluginPrivilege) Reset() { *m = PluginPrivilege{} }
func (m *PluginPrivilege) String() string { return proto.CompactTextString(m) }
func (*PluginPrivilege) ProtoMessage() {}
func (*PluginPrivilege) Descriptor() ([]byte, []int) {
return fileDescriptor_22a625af4bc1cc87, []int{1}
}
func (m *PluginPrivilege) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *PluginPrivilege) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_PluginPrivilege.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *PluginPrivilege) XXX_Merge(src proto.Message) {
xxx_messageInfo_PluginPrivilege.Merge(m, src)
}
func (m *PluginPrivilege) XXX_Size() int {
return m.Size()
}
func (m *PluginPrivilege) XXX_DiscardUnknown() {
xxx_messageInfo_PluginPrivilege.DiscardUnknown(m)
}
var xxx_messageInfo_PluginPrivilege proto.InternalMessageInfo
func (m *PluginPrivilege) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *PluginPrivilege) GetDescription() string {
if m != nil {
return m.Description
}
return ""
}
func (m *PluginPrivilege) GetValue() []string {
if m != nil {
return m.Value
}
return nil
}
func init() {
proto.RegisterType((*PluginSpec)(nil), "PluginSpec")
proto.RegisterType((*PluginPrivilege)(nil), "PluginPrivilege")
}
func init() { proto.RegisterFile("plugin.proto", fileDescriptor_22a625af4bc1cc87) }
var fileDescriptor_22a625af4bc1cc87 = []byte{
// 225 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xc8, 0x29, 0x4d,
0xcf, 0xcc, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x9a, 0xc1, 0xc8, 0xc5, 0x15, 0x00, 0x16,
0x08, 0x2e, 0x48, 0x4d, 0x16, 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60,
0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x52, 0x73, 0xf3, 0x4b, 0x52, 0x25,
0x98, 0xc0, 0xa2, 0x50, 0x9e, 0x90, 0x01, 0x17, 0x57, 0x41, 0x51, 0x66, 0x59, 0x66, 0x4e, 0x6a,
0x7a, 0x6a, 0xb1, 0x04, 0xb3, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0x80, 0x1e, 0xc4, 0xb0, 0x00, 0x98,
0x44, 0x10, 0x92, 0x1a, 0x21, 0x29, 0x2e, 0x8e, 0x94, 0xcc, 0xe2, 0xc4, 0xa4, 0x9c, 0xd4, 0x14,
0x09, 0x16, 0x05, 0x46, 0x0d, 0x8e, 0x20, 0x38, 0x5f, 0x48, 0x80, 0x8b, 0x39, 0x35, 0xaf, 0x4c,
0x82, 0x55, 0x81, 0x59, 0x83, 0x33, 0x08, 0xc4, 0x54, 0x8a, 0xe5, 0xe2, 0x47, 0x33, 0x0c, 0xab,
0xf3, 0x14, 0xb8, 0xb8, 0x53, 0x52, 0x8b, 0x93, 0x8b, 0x32, 0x0b, 0x4a, 0x32, 0xf3, 0xf3, 0xa0,
0x6e, 0x44, 0x16, 0x12, 0x12, 0xe1, 0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d, 0x05, 0xbb, 0x91, 0x33,
0x08, 0xc2, 0x71, 0x92, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4,
0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36,
0x70, 0xd0, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x37, 0xea, 0xe2, 0xca, 0x2a, 0x01, 0x00,
0x00,
}
func (m *PluginSpec) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *PluginSpec) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *PluginSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Env) > 0 {
for iNdEx := len(m.Env) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.Env[iNdEx])
copy(dAtA[i:], m.Env[iNdEx])
i = encodeVarintPlugin(dAtA, i, uint64(len(m.Env[iNdEx])))
i--
dAtA[i] = 0x2a
}
}
if m.Disabled {
i--
if m.Disabled {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x20
}
if len(m.Privileges) > 0 {
for iNdEx := len(m.Privileges) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Privileges[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintPlugin(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x1a
}
}
if len(m.Remote) > 0 {
i -= len(m.Remote)
copy(dAtA[i:], m.Remote)
i = encodeVarintPlugin(dAtA, i, uint64(len(m.Remote)))
i--
dAtA[i] = 0x12
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarintPlugin(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *PluginPrivilege) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *PluginPrivilege) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *PluginPrivilege) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Value) > 0 {
for iNdEx := len(m.Value) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.Value[iNdEx])
copy(dAtA[i:], m.Value[iNdEx])
i = encodeVarintPlugin(dAtA, i, uint64(len(m.Value[iNdEx])))
i--
dAtA[i] = 0x1a
}
}
if len(m.Description) > 0 {
i -= len(m.Description)
copy(dAtA[i:], m.Description)
i = encodeVarintPlugin(dAtA, i, uint64(len(m.Description)))
i--
dAtA[i] = 0x12
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarintPlugin(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintPlugin(dAtA []byte, offset int, v uint64) int {
offset -= sovPlugin(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *PluginSpec) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sovPlugin(uint64(l))
}
l = len(m.Remote)
if l > 0 {
n += 1 + l + sovPlugin(uint64(l))
}
if len(m.Privileges) > 0 {
for _, e := range m.Privileges {
l = e.Size()
n += 1 + l + sovPlugin(uint64(l))
}
}
if m.Disabled {
n += 2
}
if len(m.Env) > 0 {
for _, s := range m.Env {
l = len(s)
n += 1 + l + sovPlugin(uint64(l))
}
}
return n
}
func (m *PluginPrivilege) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sovPlugin(uint64(l))
}
l = len(m.Description)
if l > 0 {
n += 1 + l + sovPlugin(uint64(l))
}
if len(m.Value) > 0 {
for _, s := range m.Value {
l = len(s)
n += 1 + l + sovPlugin(uint64(l))
}
}
return n
}
func sovPlugin(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozPlugin(x uint64) (n int) {
return sovPlugin(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *PluginSpec) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: PluginSpec: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: PluginSpec: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthPlugin
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthPlugin
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Remote", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthPlugin
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthPlugin
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Remote = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Privileges", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthPlugin
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthPlugin
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Privileges = append(m.Privileges, &PluginPrivilege{})
if err := m.Privileges[len(m.Privileges)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Disabled", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.Disabled = bool(v != 0)
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Env", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthPlugin
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthPlugin
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Env = append(m.Env, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipPlugin(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthPlugin
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *PluginPrivilege) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: PluginPrivilege: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: PluginPrivilege: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthPlugin
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthPlugin
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthPlugin
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthPlugin
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Description = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPlugin
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthPlugin
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthPlugin
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Value = append(m.Value, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipPlugin(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthPlugin
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipPlugin(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowPlugin
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowPlugin
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowPlugin
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthPlugin
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupPlugin
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthPlugin
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthPlugin = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowPlugin = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupPlugin = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -1,19 +0,0 @@
syntax = "proto3";
// PluginSpec defines the base payload which clients can specify for creating
// a service with the plugin runtime.
message PluginSpec {
string name = 1;
string remote = 2;
repeated PluginPrivilege privileges = 3;
bool disabled = 4;
repeated string env = 5;
}
// PluginPrivilege describes a permission the user has to accept
// upon installing a plugin.
message PluginPrivilege {
string name = 1;
string description = 2;
repeated string value = 3;
}

View File

@@ -0,0 +1,27 @@
package runtime
import "fmt"
// PluginSpec defines the base payload which clients can specify for creating
// a service with the plugin runtime.
type PluginSpec struct {
Name string `json:"name,omitempty"`
Remote string `json:"remote,omitempty"`
Privileges []*PluginPrivilege `json:"privileges,omitempty"`
Disabled bool `json:"disabled,omitempty"`
Env []string `json:"env,omitempty"`
}
// PluginPrivilege describes a permission the user has to accept
// upon installing a plugin.
type PluginPrivilege struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Value []string `json:"value,omitempty"`
}
var (
ErrInvalidLengthPlugin = fmt.Errorf("proto: negative length found during unmarshaling") // Deprecated: this error was only used internally and is no longer used.
ErrIntOverflowPlugin = fmt.Errorf("proto: integer overflow") // Deprecated: this error was only used internally and is no longer used.
ErrUnexpectedEndOfGroupPlugin = fmt.Errorf("proto: unexpected end of group") // Deprecated: this error was only used internally and is no longer used.
)

View File

@@ -4,7 +4,6 @@ import (
"time"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm/runtime"
)
// TaskState represents the state of a task.
@@ -77,7 +76,7 @@ type TaskSpec struct {
// NetworkAttachmentSpec is used if the `Runtime` field is set to
// `attachment`.
ContainerSpec *ContainerSpec `json:",omitempty"`
PluginSpec *runtime.PluginSpec `json:",omitempty"`
PluginSpec *RuntimeSpec `json:",omitempty"`
NetworkAttachmentSpec *NetworkAttachmentSpec `json:",omitempty"`
Resources *ResourceRequirements `json:",omitempty"`

View File

@@ -1,17 +0,0 @@
package system
import (
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/volume"
)
// DiskUsage contains response of Engine API for API 1.49 and greater:
// GET "/system/df"
type DiskUsage struct {
Images *image.DiskUsage
Containers *container.DiskUsage
Volumes *volume.DiskUsage
BuildCache *build.CacheDiskUsage
}

View File

@@ -9,19 +9,23 @@ import (
// Info contains response of Engine API:
// GET "/info"
type Info struct {
ID string
Containers int
ContainersRunning int
ContainersPaused int
ContainersStopped int
Images int
Driver string
DriverStatus [][2]string
SystemStatus [][2]string `json:",omitempty"` // SystemStatus is only propagated by the Swarm standalone API
Plugins PluginsInfo
MemoryLimit bool
SwapLimit bool
KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
ID string
Containers int
ContainersRunning int
ContainersPaused int
ContainersStopped int
Images int
Driver string
DriverStatus [][2]string
SystemStatus [][2]string `json:",omitempty"` // SystemStatus is only propagated by the Swarm standalone API
Plugins PluginsInfo
MemoryLimit bool
SwapLimit bool
KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
// KernelMemoryLimit is not supported on cgroups v2.
//
// Deprecated: This field is deprecated and will be removed in the next release.
// Starting with kernel 6.12, the kernel has deprecated kernel memory tcp accounting
KernelMemoryTCP bool `json:",omitempty"` // KernelMemoryTCP is not supported on cgroups v2.
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
CPUCfsQuota bool `json:"CpuCfsQuota"`

View File

@@ -46,15 +46,16 @@ type NetworkSettings = container.NetworkSettings
// NetworkSettingsBase holds networking state for a container when inspecting it.
//
// Deprecated: use [container.NetworkSettingsBase].
type NetworkSettingsBase = container.NetworkSettingsBase
// Deprecated: [container.NetworkSettingsBase] will be removed in v29. Prefer
// accessing the fields it contains through [container.NetworkSettings].
type NetworkSettingsBase = container.NetworkSettingsBase //nolint:staticcheck // ignore SA1019: NetworkSettingsBase is deprecated in v28.4.
// DefaultNetworkSettings holds network information
// during the 2 release deprecation period.
// It will be removed in Docker 1.11.
//
// Deprecated: use [container.DefaultNetworkSettings].
type DefaultNetworkSettings = container.DefaultNetworkSettings
type DefaultNetworkSettings = container.DefaultNetworkSettings //nolint:staticcheck // ignore SA1019: DefaultNetworkSettings is deprecated in v28.4.
// SummaryNetworkSettings provides a summary of container's networks
// in /containers/json.

View File

@@ -1,6 +1,8 @@
package volume
// DiskUsage contains disk usage for volumes.
//
// Deprecated: this type is no longer used and will be removed in the next release.
type DiskUsage struct {
TotalSize int64
Reclaimable int64

View File

@@ -463,7 +463,9 @@ func (cli *Client) dialer() func(context.Context) (net.Conn, error) {
case "unix":
return net.Dial(cli.proto, cli.addr)
case "npipe":
return sockets.DialPipe(cli.addr, 32*time.Second)
ctx, cancel := context.WithTimeout(ctx, 32*time.Second)
defer cancel()
return dialPipeContext(ctx, cli.addr)
default:
if tlsConfig := cli.tlsConfig(); tlsConfig != nil {
return tls.Dial(cli.proto, cli.addr, tlsConfig)

View File

@@ -2,6 +2,17 @@
package client
import (
"context"
"net"
"syscall"
)
// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
// (EnvOverrideHost) environment variable is unset or empty.
const DefaultDockerHost = "unix:///var/run/docker.sock"
// dialPipeContext connects to a Windows named pipe. It is not supported on non-Windows.
func dialPipeContext(_ context.Context, _ string) (net.Conn, error) {
return nil, syscall.EAFNOSUPPORT
}

View File

@@ -1,5 +1,17 @@
package client
import (
"context"
"net"
"github.com/Microsoft/go-winio"
)
// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
// (EnvOverrideHost) environment variable is unset or empty.
const DefaultDockerHost = "npipe:////./pipe/docker_engine"
// dialPipeContext connects to a Windows named pipe. It is not supported on non-Windows.
func dialPipeContext(ctx context.Context, addr string) (net.Conn, error) {
return winio.DialPipeContext(ctx, addr)
}

View File

@@ -28,7 +28,7 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
return container.StatsResponseReader{
Body: resp.Body,
OSType: getDockerOS(resp.Header.Get("Server")),
OSType: resp.Header.Get("Ostype"),
}, nil
}
@@ -51,6 +51,6 @@ func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string
return container.StatsResponseReader{
Body: resp.Body,
OSType: getDockerOS(resp.Header.Get("Server")),
OSType: resp.Header.Get("Ostype"),
}, nil
}

View File

@@ -40,7 +40,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
return build.ImageBuildResponse{
Body: resp.Body,
OSType: getDockerOS(resp.Header.Get("Server")),
OSType: resp.Header.Get("Ostype"),
}, nil
}

View File

@@ -8,12 +8,9 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/internal/lazyregexp"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
var headerRegexp = lazyregexp.New(`\ADocker/.+\s\((.+)\)\z`)
type emptyIDError string
func (e emptyIDError) InvalidParameter() {}
@@ -31,16 +28,6 @@ func trimID(objType, id string) (string, error) {
return id, nil
}
// getDockerOS returns the operating system based on the server header from the daemon.
func getDockerOS(serverHeader string) string {
var osType string
matches := headerRegexp.FindStringSubmatch(serverHeader)
if len(matches) > 0 {
osType = matches[1]
}
return osType
}
// getFiltersQuery returns a url query with "filters" query term, based on the
// filters provided.
func getFiltersQuery(f filters.Args) (url.Values, error) {

View File

@@ -1,90 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code below was largely copied from golang.org/x/mod@v0.22;
// https://github.com/golang/mod/blob/v0.22.0/internal/lazyregexp/lazyre.go
// with some additional methods added.
// Package lazyregexp is a thin wrapper over regexp, allowing the use of global
// regexp variables without forcing them to be compiled at init.
package lazyregexp
import (
"os"
"regexp"
"strings"
"sync"
)
// Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be
// compiled the first time it is needed.
type Regexp struct {
str string
once sync.Once
rx *regexp.Regexp
}
func (r *Regexp) re() *regexp.Regexp {
r.once.Do(r.build)
return r.rx
}
func (r *Regexp) build() {
r.rx = regexp.MustCompile(r.str)
r.str = ""
}
func (r *Regexp) FindSubmatch(s []byte) [][]byte {
return r.re().FindSubmatch(s)
}
func (r *Regexp) FindAllStringSubmatch(s string, n int) [][]string {
return r.re().FindAllStringSubmatch(s, n)
}
func (r *Regexp) FindStringSubmatch(s string) []string {
return r.re().FindStringSubmatch(s)
}
func (r *Regexp) FindStringSubmatchIndex(s string) []int {
return r.re().FindStringSubmatchIndex(s)
}
func (r *Regexp) ReplaceAllString(src, repl string) string {
return r.re().ReplaceAllString(src, repl)
}
func (r *Regexp) FindString(s string) string {
return r.re().FindString(s)
}
func (r *Regexp) FindAllString(s string, n int) []string {
return r.re().FindAllString(s, n)
}
func (r *Regexp) MatchString(s string) bool {
return r.re().MatchString(s)
}
func (r *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string {
return r.re().ReplaceAllStringFunc(src, repl)
}
func (r *Regexp) SubexpNames() []string {
return r.re().SubexpNames()
}
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
// New creates a new lazy regexp, delaying the compiling work until it is first
// needed. If the code is being run as part of tests, the regexp compiling will
// happen immediately.
func New(str string) *Regexp {
lr := &Regexp{str: str}
if inTest {
// In tests, always compile the regexps early.
lr.re()
}
return lr
}

View File

@@ -1,46 +0,0 @@
package multierror
import (
"strings"
)
// Join is a drop-in replacement for errors.Join with better formatting.
func Join(errs ...error) error {
n := 0
for _, err := range errs {
if err != nil {
n++
}
}
if n == 0 {
return nil
}
e := &joinError{
errs: make([]error, 0, n),
}
for _, err := range errs {
if err != nil {
e.errs = append(e.errs, err)
}
}
return e
}
type joinError struct {
errs []error
}
func (e *joinError) Error() string {
if len(e.errs) == 1 {
return strings.TrimSpace(e.errs[0].Error())
}
stringErrs := make([]string, 0, len(e.errs))
for _, subErr := range e.errs {
stringErrs = append(stringErrs, strings.ReplaceAll(subErr.Error(), "\n", "\n\t"))
}
return "* " + strings.Join(stringErrs, "\n* ")
}
func (e *joinError) Unwrap() []error {
return e.errs
}

View File

@@ -151,9 +151,9 @@ type JSONMessage struct {
// Deprecated: this field is deprecated since docker v0.7.1 / API v1.8. Use the information in [Progress] instead. This field will be omitted in a future release.
ProgressMessage string `json:"progress,omitempty"`
ID string `json:"id,omitempty"`
From string `json:"from,omitempty"`
Time int64 `json:"time,omitempty"`
TimeNano int64 `json:"timeNano,omitempty"`
From string `json:"from,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used.
Time int64 `json:"time,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used.
TimeNano int64 `json:"timeNano,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used.
Error *JSONError `json:"errorDetail,omitempty"`
// ErrorMessage contains errors encountered during the operation.

View File

@@ -12,10 +12,12 @@ linters:
enable:
- errorlint
- gocritic
- govet
- misspell
- nakedret
- nolintlint
- perfsprint
- prealloc
- revive
- testifylint
- thelper
@@ -32,6 +34,11 @@ linters:
comparison: true
errorf: true
errorf-multi: true
govet:
disable:
- fieldalignment
- shadow
enable-all: true
revive:
rules:
- name: blank-imports
@@ -85,5 +92,6 @@ output:
formats:
text:
path: stdout
path-prefix: .
run:
relative-path-mode: gitroot
version: "2"

View File

@@ -0,0 +1,229 @@
# AI Coding Agent Guidelines
This document provides guidelines for AI coding agents working on the Testcontainers for Go repository.
## Repository Overview
This is a **Go monorepo** containing:
- **Core library**: Root directory contains the main testcontainers-go library
- **Modules**: `./modules/` directory with 50+ technology-specific modules (postgres, redis, kafka, etc.)
- **Examples**: `./examples/` directory with example implementations
- **Module generator**: `./modulegen/` directory with tools to generate new modules
- **Documentation**: `./docs/` directory with MkDocs-based documentation
## Environment Setup
### Go Version
- **Required**: Go 1.24.7
- **Tool**: Use [gvm](https://github.com/andrewkroh/gvm) for version management
- **CRITICAL**: Always run this before ANY Go command:
```bash
# For Apple Silicon (M1/M2/M3)
eval "$(gvm 1.24.7 --arch=arm64)"
# For Intel/AMD (x86_64)
eval "$(gvm 1.24.7 --arch=amd64)"
```
### Project Structure
Each module in `./modules/` is a separate Go module with:
- `go.mod` / `go.sum` - Module dependencies
- `{module}.go` - Main module implementation
- `{module}_test.go` - Unit tests
- `examples_test.go` - Testable examples for documentation
- `Makefile` - Standard targets: `pre-commit`, `test-unit`
## Development Workflow
### Before Making Changes
1. **Read existing code** in similar modules for patterns
2. **Check documentation** in `docs/modules/index.md` for best practices
3. **Run tests** to ensure baseline passes
### Working with Modules
1. **Change to module directory**: `cd modules/{module-name}`
2. **Run pre-commit checks**: `make pre-commit` (linting, formatting, tidy)
3. **Run tests**: `make test-unit`
4. **Both together**: `make pre-commit test-unit`
### Git Workflow
- **Branch naming**: Use descriptive names like `chore-module-use-run`, `feat-add-xyz`, `fix-module-issue`
- **NEVER** use `main` branch for PRs (they will be auto-closed)
- **Commit format**: Conventional commits (enforced by CI)
```text
type(scope): description
Longer explanation if needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
```
- **Commit types** (enforced): `security`, `fix`, `feat`, `docs`, `chore`, `deps`
- **Scope rules**:
- Optional (can be omitted for repo-level changes)
- Must be lowercase (uppercase scopes are rejected)
- Examples: `feat(redis)`, `chore(kafka)`, `docs`, `fix(postgres)`
- **Subject rules**:
- Must NOT start with uppercase letter
- ✅ Good: `feat(redis): add support for clustering`
- ❌ Bad: `feat(redis): Add support for clustering`
- **Breaking changes**: Add `!` after type: `feat(redis)!: remove deprecated API`
- **Always include co-author footer** when AI assists with changes
### Pull Requests
- **Title format**: Same as commit format (validated by CI)
- `type(scope): description`
- Examples: `feat(redis): add clustering support`, `docs: improve module guide`, `chore(kafka): update tests`
- **Title validation** enforced by `.github/workflows/conventions.yml`
- **Labels**: Use appropriate labels (`chore`, `breaking change`, `documentation`, etc.)
- **Body template**:
```markdown
## What does this PR do?
Brief description of changes.
## Why is it important?
Context and rationale.
## Related issues
- Relates to #issue-number
```
## Module Development Best Practices
**📖 Detailed guide**: See [`docs/modules/index.md`](docs/modules/index.md) for comprehensive module development documentation.
### Quick Reference
#### Container Struct
- **Name**: Use `Container`, not module-specific names like `PostgresContainer`
- **Fields**: Use private fields for state management
- **Embedding**: Always embed `testcontainers.Container`
```go
type Container struct {
testcontainers.Container
dbName string // private
user string // private
}
```
#### Run Function Pattern
Five-step implementation:
1. Process custom options (if using intermediate settings)
2. Build `moduleOpts` with defaults
3. Add conditional options based on settings
4. Append user options (allows overrides)
5. Call `testcontainers.Run` and return with proper error wrapping
```go
func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
// See docs/modules/index.md for complete implementation
moduleOpts := []testcontainers.ContainerCustomizer{
testcontainers.WithExposedPorts("5432/tcp"),
// ... defaults
}
moduleOpts = append(moduleOpts, opts...)
ctr, err := testcontainers.Run(ctx, img, moduleOpts...)
if err != nil {
return nil, fmt.Errorf("run modulename: %w", err)
}
return &Container{Container: ctr}, nil
}
```
#### Container Options
- **Simple config**: Use built-in `testcontainers.With*` options
- **Complex logic**: Use `testcontainers.CustomizeRequestOption`
- **State transfer**: Create custom `Option` type
**Critical rules:**
- ✅ Return struct types (not interfaces)
- ✅ Call built-in options directly: `testcontainers.WithFiles(f)(req)`
- ❌ Don't use `.Customize()` method
- ❌ Don't pass slices to variadic functions
#### Common Patterns
- **Env inspection**: Use `strings.CutPrefix` with early exit
- **Variadic args**: Pass directly, not as slices
- **Option order**: defaults → user options → post-processing
- **Error format**: `fmt.Errorf("run modulename: %w", err)`
**For complete examples and detailed explanations**, see [`docs/modules/index.md`](docs/modules/index.md).
## Testing Guidelines
### Running Tests
- **From module directory**: `cd modules/{module} && make test-unit`
- **Pre-commit checks**: `make pre-commit` (run this first to catch lint issues)
- **Full check**: `make pre-commit test-unit`
### Test Patterns
- Use testable examples in `examples_test.go`
- Follow existing test patterns in similar modules
- Test both success and error cases
- Use `t.Parallel()` when tests are independent
### When Tests Fail
1. **Read the error message carefully** - it usually tells you exactly what's wrong
2. **Check if it's a lint issue** - run `make pre-commit` first
3. **Verify Go version** - ensure using Go 1.24.7
4. **Check Docker** - some tests require Docker daemon running
## Common Pitfalls to Avoid
### Code Issues
- ❌ Using interface types as return values
- ❌ Forgetting to run `eval "$(gvm 1.24.7 --arch=arm64)"`
- ❌ Not handling errors from built-in options
- ❌ Using module-specific container names (`PostgresContainer`)
- ❌ Calling `.Customize()` method instead of direct function call
### Git Issues
- ❌ Forgetting co-author footer in commits
- ❌ Not running tests before committing
- ❌ Committing files outside module scope (use `git add modules/{module}/`)
- ❌ Using uppercase in scope: `feat(Redis)` → use `feat(redis)`
- ❌ Starting subject with uppercase: `fix: Add feature` → use `fix: add feature`
- ❌ Using wrong commit type (only: `security`, `fix`, `feat`, `docs`, `chore`, `deps`)
- ❌ Creating PR from `main` branch (will be auto-closed)
### Testing Issues
- ❌ Running tests without pre-commit checks first
- ❌ Not changing to module directory before running make
- ❌ Forgetting to set Go version before testing
## Reference Documentation
For detailed information, see:
- **Module development**: `docs/modules/index.md` - Comprehensive best practices
- **Contributing**: `docs/contributing.md` - General contribution guidelines
- **Modules catalog**: [testcontainers.com/modules](https://testcontainers.com/modules/?language=go)
- **API docs**: [pkg.go.dev/github.com/testcontainers/testcontainers-go](https://pkg.go.dev/github.com/testcontainers/testcontainers-go)
## Module Generator
To create a new module:
```bash
cd modulegen
go run . new module --name mymodule --image "docker.io/myimage:tag"
```
This generates:
- Module scaffolding with proper structure
- Documentation template
- Test files with examples
- Makefile with standard targets
The generator uses templates in `modulegen/_template/` that follow current best practices.
## Need Help?
- **Slack**: [testcontainers.slack.com](https://slack.testcontainers.org/)
- **GitHub Discussions**: [github.com/testcontainers/testcontainers-go/discussions](https://github.com/testcontainers/testcontainers-go/discussions)
- **Issues**: Check existing issues or create a new one with detailed context

Some files were not shown because too many files have changed in this diff Show More