Bump reva to 85468735db
To get new GetUserNoGroups() helper
This commit is contained in:
@@ -64,7 +64,7 @@ require (
|
|||||||
github.com/onsi/gomega v1.37.0
|
github.com/onsi/gomega v1.37.0
|
||||||
github.com/open-policy-agent/opa v1.5.1
|
github.com/open-policy-agent/opa v1.5.1
|
||||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450
|
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450
|
||||||
github.com/opencloud-eu/reva/v2 v2.33.1
|
github.com/opencloud-eu/reva/v2 v2.33.2-0.20250612080213-85468735dbd6
|
||||||
github.com/orcaman/concurrent-map v1.0.0
|
github.com/orcaman/concurrent-map v1.0.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/xattr v0.4.11
|
github.com/pkg/xattr v0.4.11
|
||||||
@@ -152,10 +152,10 @@ require (
|
|||||||
github.com/bluele/gcache v0.0.2 // indirect
|
github.com/bluele/gcache v0.0.2 // indirect
|
||||||
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
|
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||||
github.com/ceph/go-ceph v0.33.0 // indirect
|
github.com/ceph/go-ceph v0.34.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 // indirect
|
github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/cornelk/hashmap v1.0.8 // indirect
|
github.com/cornelk/hashmap v1.0.8 // indirect
|
||||||
@@ -254,7 +254,7 @@ require (
|
|||||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||||
github.com/minio/highwayhash v1.0.3 // indirect
|
github.com/minio/highwayhash v1.0.3 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/minio/minio-go/v7 v7.0.92 // indirect
|
github.com/minio/minio-go/v7 v7.0.93 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
|||||||
@@ -206,8 +206,8 @@ github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7
|
|||||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/ceph/go-ceph v0.33.0 h1:xT9v/MAa+DIBmflyITyFkGRgWngATghGegKJguEOInQ=
|
github.com/ceph/go-ceph v0.34.0 h1:C45yU8VRl0Rg+/I0qw5bzT337HG6DL0yBQ0VR6QHv4o=
|
||||||
github.com/ceph/go-ceph v0.33.0/go.mod h1:6ef0lIyDHnwArykqfWZDWCfbbJAVTXL1tOYrM1M4bAE=
|
github.com/ceph/go-ceph v0.34.0/go.mod h1:otRLwpVgM81lK5zdGYOfr4OELdeS97luDBE/PjXAB5o=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@@ -219,8 +219,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
|
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
@@ -784,8 +784,8 @@ github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD
|
|||||||
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw=
|
github.com/minio/minio-go/v7 v7.0.93 h1:lAB4QJp8Nq3vDMOU0eKgMuyBiEGMNlXQ5Glc8qAxqSU=
|
||||||
github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0=
|
github.com/minio/minio-go/v7 v7.0.93/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
@@ -869,8 +869,8 @@ github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-202505121527
|
|||||||
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
|
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
|
||||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 h1:QWn9G2f1R/EbyZSbkjtd9jqNq9X0NIphmmD4KYLNZtA=
|
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 h1:QWn9G2f1R/EbyZSbkjtd9jqNq9X0NIphmmD4KYLNZtA=
|
||||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
||||||
github.com/opencloud-eu/reva/v2 v2.33.1 h1:dm3AxLx7MCHPI9h23iYr/Uw/AQHmQN+G4XlbZQnm35Y=
|
github.com/opencloud-eu/reva/v2 v2.33.2-0.20250612080213-85468735dbd6 h1:9NUMVWfrRIJtm41kPNc4bXjCsaIyerumuMx3rnFYNro=
|
||||||
github.com/opencloud-eu/reva/v2 v2.33.1/go.mod h1:0Eu27TSdmj1+0PGn8MjFNh84nW16kGzt2Cxdz9oUncM=
|
github.com/opencloud-eu/reva/v2 v2.33.2-0.20250612080213-85468735dbd6/go.mod h1:o3ilVD4jpf3v6nk2oG9D7rCh/TRVEBAUOzH9z83d+Yc=
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
//go:build !(octopus || pacific || quincy || reef || squid) && ceph_preview
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
// SubVolumeSnapshotPath returns the path for a snapshot from the source subvolume.
|
||||||
|
//
|
||||||
|
// Similar To:
|
||||||
|
//
|
||||||
|
// ceph fs subvolume snapshot getpath <volume> --group-name=<group> <source> <name>
|
||||||
|
func (fsa *FSAdmin) SubVolumeSnapshotPath(volume, group, source, name string) (string, error) {
|
||||||
|
m := map[string]string{
|
||||||
|
"prefix": "fs subvolume snapshot getpath",
|
||||||
|
"vol_name": volume,
|
||||||
|
"sub_name": source,
|
||||||
|
"snap_name": name,
|
||||||
|
"format": "json",
|
||||||
|
}
|
||||||
|
if group != NoGroup {
|
||||||
|
m["group_name"] = group
|
||||||
|
}
|
||||||
|
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||||
|
}
|
||||||
+6
-2
@@ -7,6 +7,10 @@ package cephfs
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <cephfs/libcephfs.h>
|
#include <cephfs/libcephfs.h>
|
||||||
|
|
||||||
|
int _go_ceph_fchown(struct ceph_mount_info *cmount, int fd, uid_t uid, gid_t gid) {
|
||||||
|
return ceph_fchown(cmount, fd, uid, gid);
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
@@ -278,13 +282,13 @@ func (f *File) Fchmod(mode uint32) error {
|
|||||||
//
|
//
|
||||||
// Implements:
|
// Implements:
|
||||||
//
|
//
|
||||||
// int ceph_fchown(struct ceph_mount_info *cmount, int fd, int uid, int gid);
|
// int ceph_fchown(struct ceph_mount_info *cmount, int fd, uid_t uid, gid_t gid);
|
||||||
func (f *File) Fchown(user uint32, group uint32) error {
|
func (f *File) Fchown(user uint32, group uint32) error {
|
||||||
if err := f.validate(); err != nil {
|
if err := f.validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := C.ceph_fchown(f.mount.mount, f.fd, C.int(user), C.int(group))
|
ret := C._go_ceph_fchown(f.mount.mount, f.fd, C.uid_t(user), C.gid_t(group))
|
||||||
return getError(ret)
|
return getError(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -5,6 +5,14 @@ package cephfs
|
|||||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <cephfs/libcephfs.h>
|
#include <cephfs/libcephfs.h>
|
||||||
|
|
||||||
|
int _go_ceph_chown(struct ceph_mount_info *cmount, const char *path, uid_t uid, gid_t gid) {
|
||||||
|
return ceph_chown(cmount, path, uid, gid);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _go_ceph_lchown(struct ceph_mount_info *cmount, const char *path, uid_t uid, gid_t gid) {
|
||||||
|
return ceph_lchown(cmount, path, uid, gid);
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
@@ -26,7 +34,7 @@ func (mount *MountInfo) Chown(path string, user uint32, group uint32) error {
|
|||||||
cPath := C.CString(path)
|
cPath := C.CString(path)
|
||||||
defer C.free(unsafe.Pointer(cPath))
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
ret := C.ceph_chown(mount.mount, cPath, C.int(user), C.int(group))
|
ret := C._go_ceph_chown(mount.mount, cPath, C.uid_t(user), C.gid_t(group))
|
||||||
return getError(ret)
|
return getError(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +43,6 @@ func (mount *MountInfo) Lchown(path string, user uint32, group uint32) error {
|
|||||||
cPath := C.CString(path)
|
cPath := C.CString(path)
|
||||||
defer C.free(unsafe.Pointer(cPath))
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
ret := C.ceph_lchown(mount.mount, cPath, C.int(user), C.int(group))
|
ret := C._go_ceph_lchown(mount.mount, cPath, C.uid_t(user), C.gid_t(group))
|
||||||
return getError(ret)
|
return getError(ret)
|
||||||
}
|
}
|
||||||
|
|||||||
+20
@@ -6,15 +6,35 @@ type MgrCommander interface {
|
|||||||
MgrCommand(buf [][]byte) ([]byte, string, error)
|
MgrCommand(buf [][]byte) ([]byte, string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MgrBufferCommander is an interface for the API needed to execute JSON
|
||||||
|
// formatted commands with an input buffer on the Ceph mgr.
|
||||||
|
type MgrBufferCommander interface {
|
||||||
|
MgrCommandWithInputBuffer([][]byte, []byte) ([]byte, string, error)
|
||||||
|
}
|
||||||
|
|
||||||
// MonCommander is an interface for the API needed to execute JSON formatted
|
// MonCommander is an interface for the API needed to execute JSON formatted
|
||||||
// commands on the ceph mon(s).
|
// commands on the ceph mon(s).
|
||||||
type MonCommander interface {
|
type MonCommander interface {
|
||||||
MonCommand(buf []byte) ([]byte, string, error)
|
MonCommand(buf []byte) ([]byte, string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonBufferCommander is an interface for the API needed to execute JSON
|
||||||
|
// formatted commands with an input buffer on the Ceph mon(s).
|
||||||
|
type MonBufferCommander interface {
|
||||||
|
MonCommandWithInputBuffer([]byte, []byte) ([]byte, string, error)
|
||||||
|
}
|
||||||
|
|
||||||
// RadosCommander provides an interface for APIs needed to execute JSON
|
// RadosCommander provides an interface for APIs needed to execute JSON
|
||||||
// formatted commands on the Ceph cluster.
|
// formatted commands on the Ceph cluster.
|
||||||
type RadosCommander interface {
|
type RadosCommander interface {
|
||||||
MgrCommander
|
MgrCommander
|
||||||
MonCommander
|
MonCommander
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RadosBufferCommander provides an interface for APIs that need to execute
|
||||||
|
// JSON formatted commands with an input buffer on the Ceph cluster.
|
||||||
|
type RadosBufferCommander interface {
|
||||||
|
RadosCommander
|
||||||
|
MgrBufferCommander
|
||||||
|
MonBufferCommander
|
||||||
|
}
|
||||||
|
|||||||
+38
@@ -51,3 +51,41 @@ func MarshalMonCommand(m ccom.MonCommander, v interface{}) Response {
|
|||||||
}
|
}
|
||||||
return RawMonCommand(m, b)
|
return RawMonCommand(m, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalMgrCommandWithBuffer takes a generic interface{} value, converts
|
||||||
|
// it to JSON and sends the JSON, along with the buffer data, to the MGR as
|
||||||
|
// a command.
|
||||||
|
func MarshalMgrCommandWithBuffer(
|
||||||
|
m ccom.MgrBufferCommander, v interface{}, buf []byte) Response {
|
||||||
|
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return Response{err: err}
|
||||||
|
}
|
||||||
|
if err := validate(m); err != nil {
|
||||||
|
return Response{err: err}
|
||||||
|
}
|
||||||
|
return NewResponse(m.MgrCommandWithInputBuffer(
|
||||||
|
[][]byte{b},
|
||||||
|
buf,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMonCommandWithBuffer takes a generic interface{} value, converts
|
||||||
|
// it to JSON and sends the JSON, along with the buffer data, to the MON(s)
|
||||||
|
// as a command.
|
||||||
|
func MarshalMonCommandWithBuffer(
|
||||||
|
m ccom.MonBufferCommander, v interface{}, buf []byte) Response {
|
||||||
|
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return Response{err: err}
|
||||||
|
}
|
||||||
|
if err := validate(m); err != nil {
|
||||||
|
return Response{err: err}
|
||||||
|
}
|
||||||
|
return NewResponse(m.MonCommandWithInputBuffer(
|
||||||
|
b,
|
||||||
|
buf,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
+38
-2
@@ -9,7 +9,7 @@ import (
|
|||||||
// NewTraceCommander is a RadosCommander that wraps a given RadosCommander
|
// NewTraceCommander is a RadosCommander that wraps a given RadosCommander
|
||||||
// and when commands are executes prints debug level "traces" to the
|
// and when commands are executes prints debug level "traces" to the
|
||||||
// standard output.
|
// standard output.
|
||||||
func NewTraceCommander(c ccom.RadosCommander) ccom.RadosCommander {
|
func NewTraceCommander(c ccom.RadosBufferCommander) ccom.RadosBufferCommander {
|
||||||
return &tracingCommander{c}
|
return &tracingCommander{c}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ func NewTraceCommander(c ccom.RadosCommander) ccom.RadosCommander {
|
|||||||
// interface in FSAdmin. You can layer any sort of debugging, error injection,
|
// interface in FSAdmin. You can layer any sort of debugging, error injection,
|
||||||
// or whatnot between the FSAdmin layer and the RADOS layer.
|
// or whatnot between the FSAdmin layer and the RADOS layer.
|
||||||
type tracingCommander struct {
|
type tracingCommander struct {
|
||||||
conn ccom.RadosCommander
|
conn ccom.RadosBufferCommander
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tracingCommander) MgrCommand(buf [][]byte) ([]byte, string, error) {
|
func (t *tracingCommander) MgrCommand(buf [][]byte) ([]byte, string, error) {
|
||||||
@@ -51,3 +51,39 @@ func (t *tracingCommander) MonCommand(buf []byte) ([]byte, string, error) {
|
|||||||
}
|
}
|
||||||
return r, s, err
|
return r, s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tracingCommander) MgrCommandWithInputBuffer(
|
||||||
|
cbuf [][]byte, dbuf []byte) ([]byte, string, error) {
|
||||||
|
|
||||||
|
fmt.Println("(MGR Command w/buffer)")
|
||||||
|
for i := range cbuf {
|
||||||
|
fmt.Println("IN:", string(cbuf[i]))
|
||||||
|
}
|
||||||
|
fmt.Println("IN DATA:", string(dbuf))
|
||||||
|
r, s, err := t.conn.MgrCommandWithInputBuffer(cbuf, dbuf)
|
||||||
|
fmt.Println("OUT(result):", string(r))
|
||||||
|
if s != "" {
|
||||||
|
fmt.Println("OUT(status):", s)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("OUT(error):", err.Error())
|
||||||
|
}
|
||||||
|
return r, s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracingCommander) MonCommandWithInputBuffer(
|
||||||
|
cbuf []byte, dbuf []byte) ([]byte, string, error) {
|
||||||
|
|
||||||
|
fmt.Println("(MON Command w/buffer)")
|
||||||
|
fmt.Println("IN:", string(cbuf))
|
||||||
|
fmt.Println("IN DATA:", string(dbuf))
|
||||||
|
r, s, err := t.conn.MonCommandWithInputBuffer(cbuf, dbuf)
|
||||||
|
fmt.Println("OUT(result):", string(r))
|
||||||
|
if s != "" {
|
||||||
|
fmt.Println("OUT(status):", s)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("OUT(error):", err.Error())
|
||||||
|
}
|
||||||
|
return r, s, err
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
// +build amd64
|
//go:build amd64 && !purego
|
||||||
|
// +build amd64,!purego
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
// +build amd64
|
//go:build amd64 && !purego
|
||||||
|
// +build amd64,!purego
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -18,6 +18,9 @@ func (Curve) Identity() *Point {
|
|||||||
func (Curve) IsOnCurve(P *Point) bool {
|
func (Curve) IsOnCurve(P *Point) bool {
|
||||||
x2, y2, t, t2, z2 := &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{}
|
x2, y2, t, t2, z2 := &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{}
|
||||||
rhs, lhs := &fp.Elt{}, &fp.Elt{}
|
rhs, lhs := &fp.Elt{}, &fp.Elt{}
|
||||||
|
// Check z != 0
|
||||||
|
eq0 := !fp.IsZero(&P.z)
|
||||||
|
|
||||||
fp.Mul(t, &P.ta, &P.tb) // t = ta*tb
|
fp.Mul(t, &P.ta, &P.tb) // t = ta*tb
|
||||||
fp.Sqr(x2, &P.x) // x^2
|
fp.Sqr(x2, &P.x) // x^2
|
||||||
fp.Sqr(y2, &P.y) // y^2
|
fp.Sqr(y2, &P.y) // y^2
|
||||||
@@ -27,13 +30,14 @@ func (Curve) IsOnCurve(P *Point) bool {
|
|||||||
fp.Mul(rhs, t2, ¶mD) // dt^2
|
fp.Mul(rhs, t2, ¶mD) // dt^2
|
||||||
fp.Add(rhs, rhs, z2) // z^2 + dt^2
|
fp.Add(rhs, rhs, z2) // z^2 + dt^2
|
||||||
fp.Sub(lhs, lhs, rhs) // x^2 + y^2 - (z^2 + dt^2)
|
fp.Sub(lhs, lhs, rhs) // x^2 + y^2 - (z^2 + dt^2)
|
||||||
eq0 := fp.IsZero(lhs)
|
eq1 := fp.IsZero(lhs)
|
||||||
|
|
||||||
fp.Mul(lhs, &P.x, &P.y) // xy
|
fp.Mul(lhs, &P.x, &P.y) // xy
|
||||||
fp.Mul(rhs, t, &P.z) // tz
|
fp.Mul(rhs, t, &P.z) // tz
|
||||||
fp.Sub(lhs, lhs, rhs) // xy - tz
|
fp.Sub(lhs, lhs, rhs) // xy - tz
|
||||||
eq1 := fp.IsZero(lhs)
|
eq2 := fp.IsZero(lhs)
|
||||||
return eq0 && eq1
|
|
||||||
|
return eq0 && eq1 && eq2
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generator returns the generator point.
|
// Generator returns the generator point.
|
||||||
|
|||||||
+33
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BytesLe2Hex returns an hexadecimal string of a number stored in a
|
// BytesLe2Hex returns an hexadecimal string of a number stored in a
|
||||||
@@ -138,3 +140,34 @@ func BigInt2Uint64Le(z []uint64, x *big.Int) {
|
|||||||
z[i] = 0
|
z[i] = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalBinary encodes a value into a byte array in a format readable by UnmarshalBinary.
|
||||||
|
func MarshalBinary(v cryptobyte.MarshalingValue) ([]byte, error) {
|
||||||
|
const DefaultSize = 32
|
||||||
|
b := cryptobyte.NewBuilder(make([]byte, 0, DefaultSize))
|
||||||
|
b.AddValue(v)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinaryLen encodes a value into an array of n bytes in a format readable by UnmarshalBinary.
|
||||||
|
func MarshalBinaryLen(v cryptobyte.MarshalingValue, length uint) ([]byte, error) {
|
||||||
|
b := cryptobyte.NewFixedBuilder(make([]byte, 0, length))
|
||||||
|
b.AddValue(v)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A UnmarshalingValue decodes itself from a cryptobyte.String and advances the pointer.
|
||||||
|
// It reports whether the read was successful.
|
||||||
|
type UnmarshalingValue interface {
|
||||||
|
Unmarshal(*cryptobyte.String) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary recovers a value from a byte array.
|
||||||
|
// It returns an error if the read was unsuccessful.
|
||||||
|
func UnmarshalBinary(v UnmarshalingValue, data []byte) (err error) {
|
||||||
|
s := cryptobyte.String(data)
|
||||||
|
if data == nil || !v.Unmarshal(&s) || !s.Empty() {
|
||||||
|
err = fmt.Errorf("cannot read %T from input string", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
// +build amd64
|
//go:build amd64 && !purego
|
||||||
|
// +build amd64,!purego
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
#include "fp_amd64.h"
|
#include "fp_amd64.h"
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
// +build amd64
|
//go:build amd64 && !purego
|
||||||
|
// +build amd64,!purego
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
#include "fp_amd64.h"
|
#include "fp_amd64.h"
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package math
|
||||||
|
|
||||||
|
import "math/bits"
|
||||||
|
|
||||||
|
// NextPow2 finds the next power of two (N=2^k, k>=0) greater than n.
|
||||||
|
// If n is already a power of two, then this function returns n, and log2(n).
|
||||||
|
func NextPow2(n uint) (N uint, k uint) {
|
||||||
|
if bits.OnesCount(n) == 1 {
|
||||||
|
k = uint(bits.TrailingZeros(n))
|
||||||
|
N = n
|
||||||
|
} else {
|
||||||
|
k = uint(bits.Len(n))
|
||||||
|
N = uint(1) << k
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+1
-1
@@ -164,7 +164,7 @@ func (P *pointR1) isEqual(Q *pointR1) bool {
|
|||||||
fp.Mul(r, r, &P.z)
|
fp.Mul(r, r, &P.z)
|
||||||
fp.Sub(l, l, r)
|
fp.Sub(l, l, r)
|
||||||
b = b && fp.IsZero(l)
|
b = b && fp.IsZero(l)
|
||||||
return b
|
return b && !fp.IsZero(&P.z) && !fp.IsZero(&Q.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (P *pointR3) neg() {
|
func (P *pointR3) neg() {
|
||||||
|
|||||||
+1
-1
@@ -206,7 +206,7 @@ func newKeyFromSeed(privateKey, seed []byte) {
|
|||||||
|
|
||||||
func signAll(signature []byte, privateKey PrivateKey, message, ctx []byte, preHash bool) {
|
func signAll(signature []byte, privateKey PrivateKey, message, ctx []byte, preHash bool) {
|
||||||
if len(ctx) > ContextMaxSize {
|
if len(ctx) > ContextMaxSize {
|
||||||
panic(fmt.Errorf("ed448: bad context length: " + strconv.Itoa(len(ctx))))
|
panic(fmt.Errorf("ed448: bad context length: %v", len(ctx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
H := sha3.NewShake256()
|
H := sha3.NewShake256()
|
||||||
|
|||||||
+3
@@ -107,4 +107,7 @@ var (
|
|||||||
// ErrContextNotSupported is the error used if a context is not
|
// ErrContextNotSupported is the error used if a context is not
|
||||||
// supported.
|
// supported.
|
||||||
ErrContextNotSupported = errors.New("context not supported")
|
ErrContextNotSupported = errors.New("context not supported")
|
||||||
|
|
||||||
|
// ErrContextTooLong is the error used if the context string is too long.
|
||||||
|
ErrContextTooLong = errors.New("context string too long")
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -98,7 +98,7 @@ func (c *Client) GetBucketCors(ctx context.Context, bucketName string) (*cors.Co
|
|||||||
bucketCors, err := c.getBucketCors(ctx, bucketName)
|
bucketCors, err := c.getBucketCors(ctx, bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse := ToErrorResponse(err)
|
errResponse := ToErrorResponse(err)
|
||||||
if errResponse.Code == "NoSuchCORSConfiguration" {
|
if errResponse.Code == NoSuchCORSConfiguration {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+1
-1
@@ -104,7 +104,7 @@ func (c *Client) GetBucketPolicy(ctx context.Context, bucketName string) (string
|
|||||||
bucketPolicy, err := c.getBucketPolicy(ctx, bucketName)
|
bucketPolicy, err := c.getBucketPolicy(ctx, bucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse := ToErrorResponse(err)
|
errResponse := ToErrorResponse(err)
|
||||||
if errResponse.Code == "NoSuchBucketPolicy" {
|
if errResponse.Code == NoSuchBucketPolicy {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
+19
-18
@@ -136,15 +136,15 @@ func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string)
|
|||||||
if objectName == "" {
|
if objectName == "" {
|
||||||
errResp = ErrorResponse{
|
errResp = ErrorResponse{
|
||||||
StatusCode: resp.StatusCode,
|
StatusCode: resp.StatusCode,
|
||||||
Code: "NoSuchBucket",
|
Code: NoSuchBucket,
|
||||||
Message: "The specified bucket does not exist.",
|
Message: s3ErrorResponseMap[NoSuchBucket],
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errResp = ErrorResponse{
|
errResp = ErrorResponse{
|
||||||
StatusCode: resp.StatusCode,
|
StatusCode: resp.StatusCode,
|
||||||
Code: "NoSuchKey",
|
Code: NoSuchKey,
|
||||||
Message: "The specified key does not exist.",
|
Message: s3ErrorResponseMap[NoSuchKey],
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
}
|
}
|
||||||
@@ -152,23 +152,23 @@ func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string)
|
|||||||
case http.StatusForbidden:
|
case http.StatusForbidden:
|
||||||
errResp = ErrorResponse{
|
errResp = ErrorResponse{
|
||||||
StatusCode: resp.StatusCode,
|
StatusCode: resp.StatusCode,
|
||||||
Code: "AccessDenied",
|
Code: AccessDenied,
|
||||||
Message: "Access Denied.",
|
Message: s3ErrorResponseMap[AccessDenied],
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
}
|
}
|
||||||
case http.StatusConflict:
|
case http.StatusConflict:
|
||||||
errResp = ErrorResponse{
|
errResp = ErrorResponse{
|
||||||
StatusCode: resp.StatusCode,
|
StatusCode: resp.StatusCode,
|
||||||
Code: "Conflict",
|
Code: Conflict,
|
||||||
Message: "Bucket not empty.",
|
Message: s3ErrorResponseMap[Conflict],
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
}
|
}
|
||||||
case http.StatusPreconditionFailed:
|
case http.StatusPreconditionFailed:
|
||||||
errResp = ErrorResponse{
|
errResp = ErrorResponse{
|
||||||
StatusCode: resp.StatusCode,
|
StatusCode: resp.StatusCode,
|
||||||
Code: "PreconditionFailed",
|
Code: PreconditionFailed,
|
||||||
Message: s3ErrorResponseMap["PreconditionFailed"],
|
Message: s3ErrorResponseMap[PreconditionFailed],
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string)
|
|||||||
if errResp.Region == "" {
|
if errResp.Region == "" {
|
||||||
errResp.Region = resp.Header.Get("x-amz-bucket-region")
|
errResp.Region = resp.Header.Get("x-amz-bucket-region")
|
||||||
}
|
}
|
||||||
if errResp.Code == "InvalidRegion" && errResp.Region != "" {
|
if errResp.Code == InvalidRegion && errResp.Region != "" {
|
||||||
errResp.Message = fmt.Sprintf("Region does not match, expecting region ‘%s’.", errResp.Region)
|
errResp.Message = fmt.Sprintf("Region does not match, expecting region ‘%s’.", errResp.Region)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,10 +218,11 @@ func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string)
|
|||||||
|
|
||||||
// errTransferAccelerationBucket - bucket name is invalid to be used with transfer acceleration.
|
// errTransferAccelerationBucket - bucket name is invalid to be used with transfer acceleration.
|
||||||
func errTransferAccelerationBucket(bucketName string) error {
|
func errTransferAccelerationBucket(bucketName string) error {
|
||||||
|
msg := "The name of the bucket used for Transfer Acceleration must be DNS-compliant and must not contain periods ‘.’."
|
||||||
return ErrorResponse{
|
return ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "InvalidArgument",
|
Code: InvalidArgument,
|
||||||
Message: "The name of the bucket used for Transfer Acceleration must be DNS-compliant and must not contain periods ‘.’.",
|
Message: msg,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +232,7 @@ func errEntityTooLarge(totalSize, maxObjectSize int64, bucketName, objectName st
|
|||||||
msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", totalSize, maxObjectSize)
|
msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", totalSize, maxObjectSize)
|
||||||
return ErrorResponse{
|
return ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "EntityTooLarge",
|
Code: EntityTooLarge,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
@@ -243,7 +244,7 @@ func errEntityTooSmall(totalSize int64, bucketName, objectName string) error {
|
|||||||
msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size ‘0B’ for single PUT operation.", totalSize)
|
msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size ‘0B’ for single PUT operation.", totalSize)
|
||||||
return ErrorResponse{
|
return ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "EntityTooSmall",
|
Code: EntityTooSmall,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
@@ -255,7 +256,7 @@ func errUnexpectedEOF(totalRead, totalSize int64, bucketName, objectName string)
|
|||||||
msg := fmt.Sprintf("Data read ‘%d’ is not equal to the size ‘%d’ of the input Reader.", totalRead, totalSize)
|
msg := fmt.Sprintf("Data read ‘%d’ is not equal to the size ‘%d’ of the input Reader.", totalRead, totalSize)
|
||||||
return ErrorResponse{
|
return ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "UnexpectedEOF",
|
Code: UnexpectedEOF,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
@@ -266,7 +267,7 @@ func errUnexpectedEOF(totalRead, totalSize int64, bucketName, objectName string)
|
|||||||
func errInvalidArgument(message string) error {
|
func errInvalidArgument(message string) error {
|
||||||
return ErrorResponse{
|
return ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "InvalidArgument",
|
Code: InvalidArgument,
|
||||||
Message: message,
|
Message: message,
|
||||||
RequestID: "minio",
|
RequestID: "minio",
|
||||||
}
|
}
|
||||||
@@ -277,7 +278,7 @@ func errInvalidArgument(message string) error {
|
|||||||
func errAPINotSupported(message string) error {
|
func errAPINotSupported(message string) error {
|
||||||
return ErrorResponse{
|
return ErrorResponse{
|
||||||
StatusCode: http.StatusNotImplemented,
|
StatusCode: http.StatusNotImplemented,
|
||||||
Code: "APINotSupported",
|
Code: APINotSupported,
|
||||||
Message: message,
|
Message: message,
|
||||||
RequestID: "minio",
|
RequestID: "minio",
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -34,14 +34,14 @@ func (c *Client) GetObject(ctx context.Context, bucketName, objectName string, o
|
|||||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||||
return nil, ErrorResponse{
|
return nil, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "InvalidBucketName",
|
Code: InvalidBucketName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
||||||
return nil, ErrorResponse{
|
return nil, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "XMinioInvalidObjectName",
|
Code: XMinioInvalidObjectName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -659,14 +659,14 @@ func (c *Client) getObject(ctx context.Context, bucketName, objectName string, o
|
|||||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||||
return nil, ObjectInfo{}, nil, ErrorResponse{
|
return nil, ObjectInfo{}, nil, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "InvalidBucketName",
|
Code: InvalidBucketName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
||||||
return nil, ObjectInfo{}, nil, ErrorResponse{
|
return nil, ObjectInfo{}, nil, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "XMinioInvalidObjectName",
|
Code: XMinioInvalidObjectName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-14
@@ -285,7 +285,7 @@ func (c *Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefi
|
|||||||
// sure proper responses are received.
|
// sure proper responses are received.
|
||||||
if listBucketResult.IsTruncated && listBucketResult.NextContinuationToken == "" {
|
if listBucketResult.IsTruncated && listBucketResult.NextContinuationToken == "" {
|
||||||
return listBucketResult, ErrorResponse{
|
return listBucketResult, ErrorResponse{
|
||||||
Code: "NotImplemented",
|
Code: NotImplemented,
|
||||||
Message: "Truncated response should have continuation token set",
|
Message: "Truncated response should have continuation token set",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,19 +419,25 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
|
|||||||
}
|
}
|
||||||
for _, version := range vers {
|
for _, version := range vers {
|
||||||
info := ObjectInfo{
|
info := ObjectInfo{
|
||||||
ETag: trimEtag(version.ETag),
|
ETag: trimEtag(version.ETag),
|
||||||
Key: version.Key,
|
Key: version.Key,
|
||||||
LastModified: version.LastModified.Truncate(time.Millisecond),
|
LastModified: version.LastModified.Truncate(time.Millisecond),
|
||||||
Size: version.Size,
|
Size: version.Size,
|
||||||
Owner: version.Owner,
|
Owner: version.Owner,
|
||||||
StorageClass: version.StorageClass,
|
StorageClass: version.StorageClass,
|
||||||
IsLatest: version.IsLatest,
|
IsLatest: version.IsLatest,
|
||||||
VersionID: version.VersionID,
|
VersionID: version.VersionID,
|
||||||
IsDeleteMarker: version.isDeleteMarker,
|
IsDeleteMarker: version.isDeleteMarker,
|
||||||
UserTags: version.UserTags,
|
UserTags: version.UserTags,
|
||||||
UserMetadata: version.UserMetadata,
|
UserMetadata: version.UserMetadata,
|
||||||
Internal: version.Internal,
|
Internal: version.Internal,
|
||||||
NumVersions: numVersions,
|
NumVersions: numVersions,
|
||||||
|
ChecksumMode: version.ChecksumType,
|
||||||
|
ChecksumCRC32: version.ChecksumCRC32,
|
||||||
|
ChecksumCRC32C: version.ChecksumCRC32C,
|
||||||
|
ChecksumSHA1: version.ChecksumSHA1,
|
||||||
|
ChecksumSHA256: version.ChecksumSHA256,
|
||||||
|
ChecksumCRC64NVME: version.ChecksumCRC64NVME,
|
||||||
}
|
}
|
||||||
if !yield(info) {
|
if !yield(info) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
+2
-2
@@ -35,14 +35,14 @@ func (c *Client) PromptObject(ctx context.Context, bucketName, objectName, promp
|
|||||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||||
return nil, ErrorResponse{
|
return nil, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "InvalidBucketName",
|
Code: InvalidBucketName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
||||||
return nil, ErrorResponse{
|
return nil, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "XMinioInvalidObjectName",
|
Code: XMinioInvalidObjectName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ func (c *Client) makeBucket(ctx context.Context, bucketName string, opts MakeBuc
|
|||||||
|
|
||||||
err = c.doMakeBucket(ctx, bucketName, opts)
|
err = c.doMakeBucket(ctx, bucketName, opts)
|
||||||
if err != nil && (opts.Region == "" || opts.Region == "us-east-1") {
|
if err != nil && (opts.Region == "" || opts.Region == "us-east-1") {
|
||||||
if resp, ok := err.(ErrorResponse); ok && resp.Code == "AuthorizationHeaderMalformed" && resp.Region != "" {
|
if resp, ok := err.(ErrorResponse); ok && resp.Code == AuthorizationHeaderMalformed && resp.Region != "" {
|
||||||
opts.Region = resp.Region
|
opts.Region = resp.Region
|
||||||
err = c.doMakeBucket(ctx, bucketName, opts)
|
err = c.doMakeBucket(ctx, bucketName, opts)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-8
@@ -44,7 +44,7 @@ func (c *Client) putObjectMultipart(ctx context.Context, bucketName, objectName
|
|||||||
errResp := ToErrorResponse(err)
|
errResp := ToErrorResponse(err)
|
||||||
// Verify if multipart functionality is not available, if not
|
// Verify if multipart functionality is not available, if not
|
||||||
// fall back to single PutObject operation.
|
// fall back to single PutObject operation.
|
||||||
if errResp.Code == "AccessDenied" && strings.Contains(errResp.Message, "Access Denied") {
|
if errResp.Code == AccessDenied && strings.Contains(errResp.Message, "Access Denied") {
|
||||||
// Verify if size of reader is greater than '5GiB'.
|
// Verify if size of reader is greater than '5GiB'.
|
||||||
if size > maxSinglePutObjectSize {
|
if size > maxSinglePutObjectSize {
|
||||||
return UploadInfo{}, errEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
|
return UploadInfo{}, errEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
|
||||||
@@ -392,13 +392,14 @@ func (c *Client) completeMultipartUpload(ctx context.Context, bucketName, object
|
|||||||
// Instantiate all the complete multipart buffer.
|
// Instantiate all the complete multipart buffer.
|
||||||
completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
|
completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
|
||||||
reqMetadata := requestMetadata{
|
reqMetadata := requestMetadata{
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
objectName: objectName,
|
objectName: objectName,
|
||||||
queryValues: urlValues,
|
queryValues: urlValues,
|
||||||
contentBody: completeMultipartUploadBuffer,
|
contentBody: completeMultipartUploadBuffer,
|
||||||
contentLength: int64(len(completeMultipartUploadBytes)),
|
contentLength: int64(len(completeMultipartUploadBytes)),
|
||||||
contentSHA256Hex: sum256Hex(completeMultipartUploadBytes),
|
contentSHA256Hex: sum256Hex(completeMultipartUploadBytes),
|
||||||
customHeader: headers,
|
customHeader: headers,
|
||||||
|
expect200OKWithError: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute POST to complete multipart upload for an objectName.
|
// Execute POST to complete multipart upload for an objectName.
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ func (c *Client) putObjectMultipartStream(ctx context.Context, bucketName, objec
|
|||||||
errResp := ToErrorResponse(err)
|
errResp := ToErrorResponse(err)
|
||||||
// Verify if multipart functionality is not available, if not
|
// Verify if multipart functionality is not available, if not
|
||||||
// fall back to single PutObject operation.
|
// fall back to single PutObject operation.
|
||||||
if errResp.Code == "AccessDenied" && strings.Contains(errResp.Message, "Access Denied") {
|
if errResp.Code == AccessDenied && strings.Contains(errResp.Message, "Access Denied") {
|
||||||
// Verify if size of reader is greater than '5GiB'.
|
// Verify if size of reader is greater than '5GiB'.
|
||||||
if size > maxSinglePutObjectSize {
|
if size > maxSinglePutObjectSize {
|
||||||
return UploadInfo{}, errEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
|
return UploadInfo{}, errEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
|
||||||
|
|||||||
+177
-10
@@ -22,6 +22,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"io"
|
"io"
|
||||||
|
"iter"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
@@ -271,7 +272,7 @@ func processRemoveMultiObjectsResponse(body io.Reader, resultCh chan<- RemoveObj
|
|||||||
for _, obj := range rmResult.UnDeletedObjects {
|
for _, obj := range rmResult.UnDeletedObjects {
|
||||||
// Version does not exist is not an error ignore and continue.
|
// Version does not exist is not an error ignore and continue.
|
||||||
switch obj.Code {
|
switch obj.Code {
|
||||||
case "InvalidArgument", "NoSuchVersion":
|
case InvalidArgument, NoSuchVersion:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resultCh <- RemoveObjectResult{
|
resultCh <- RemoveObjectResult{
|
||||||
@@ -333,6 +334,33 @@ func (c *Client) RemoveObjects(ctx context.Context, bucketName string, objectsCh
|
|||||||
return errorCh
|
return errorCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveObjectsWithIter bulk deletes multiple objects from a bucket.
|
||||||
|
// Objects (with optional versions) to be removed must be provided with
|
||||||
|
// an iterator. Objects are removed asynchronously and results must be
|
||||||
|
// consumed. If the returned result iterator is stopped, the context is
|
||||||
|
// canceled, or a remote call failed, the provided iterator will no
|
||||||
|
// longer accept more objects.
|
||||||
|
func (c *Client) RemoveObjectsWithIter(ctx context.Context, bucketName string, objectsIter iter.Seq[ObjectInfo], opts RemoveObjectsOptions) (iter.Seq[RemoveObjectResult], error) {
|
||||||
|
// Validate if bucket name is valid.
|
||||||
|
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Validate objects channel to be properly allocated.
|
||||||
|
if objectsIter == nil {
|
||||||
|
return nil, errInvalidArgument("Objects iter can never by nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(yield func(RemoveObjectResult) bool) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
c.removeObjectsIter(ctx, bucketName, objectsIter, yield, opts)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveObjectsWithResult removes multiple objects from a bucket while
|
// RemoveObjectsWithResult removes multiple objects from a bucket while
|
||||||
// it is possible to specify objects versions which are received from
|
// it is possible to specify objects versions which are received from
|
||||||
// objectsCh. Remove results, successes and failures are sent back via
|
// objectsCh. Remove results, successes and failures are sent back via
|
||||||
@@ -381,6 +409,144 @@ func hasInvalidXMLChar(str string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate and call MultiDelete S3 requests based on entries received from the iterator.
|
||||||
|
func (c *Client) removeObjectsIter(ctx context.Context, bucketName string, objectsIter iter.Seq[ObjectInfo], yield func(RemoveObjectResult) bool, opts RemoveObjectsOptions) {
|
||||||
|
maxEntries := 1000
|
||||||
|
urlValues := make(url.Values)
|
||||||
|
urlValues.Set("delete", "")
|
||||||
|
|
||||||
|
// Build headers.
|
||||||
|
headers := make(http.Header)
|
||||||
|
if opts.GovernanceBypass {
|
||||||
|
// Set the bypass goverenance retention header
|
||||||
|
headers.Set(amzBypassGovernance, "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
processRemoveMultiObjectsResponseIter := func(batch []ObjectInfo, yield func(RemoveObjectResult) bool) bool {
|
||||||
|
if len(batch) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate remove multi objects XML request
|
||||||
|
removeBytes := generateRemoveMultiObjectsRequest(batch)
|
||||||
|
// Execute POST on bucket to remove objects.
|
||||||
|
resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
|
||||||
|
bucketName: bucketName,
|
||||||
|
queryValues: urlValues,
|
||||||
|
contentBody: bytes.NewReader(removeBytes),
|
||||||
|
contentLength: int64(len(removeBytes)),
|
||||||
|
contentMD5Base64: sumMD5Base64(removeBytes),
|
||||||
|
contentSHA256Hex: sum256Hex(removeBytes),
|
||||||
|
customHeader: headers,
|
||||||
|
})
|
||||||
|
if resp != nil {
|
||||||
|
defer closeResponse(resp)
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
err = httpRespToErrorResponse(resp, bucketName, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
for _, b := range batch {
|
||||||
|
if !yield(RemoveObjectResult{
|
||||||
|
ObjectName: b.Key,
|
||||||
|
ObjectVersionID: b.VersionID,
|
||||||
|
Err: err,
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse multi delete XML response
|
||||||
|
rmResult := &deleteMultiObjectsResult{}
|
||||||
|
if err := xmlDecoder(resp.Body, rmResult); err != nil {
|
||||||
|
yield(RemoveObjectResult{ObjectName: "", Err: err})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill deletion that returned an error.
|
||||||
|
for _, obj := range rmResult.UnDeletedObjects {
|
||||||
|
// Version does not exist is not an error ignore and continue.
|
||||||
|
switch obj.Code {
|
||||||
|
case "InvalidArgument", "NoSuchVersion":
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !yield(RemoveObjectResult{
|
||||||
|
ObjectName: obj.Key,
|
||||||
|
ObjectVersionID: obj.VersionID,
|
||||||
|
Err: ErrorResponse{
|
||||||
|
Code: obj.Code,
|
||||||
|
Message: obj.Message,
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill deletion that returned success
|
||||||
|
for _, obj := range rmResult.DeletedObjects {
|
||||||
|
if !yield(RemoveObjectResult{
|
||||||
|
ObjectName: obj.Key,
|
||||||
|
// Only filled with versioned buckets
|
||||||
|
ObjectVersionID: obj.VersionID,
|
||||||
|
DeleteMarker: obj.DeleteMarker,
|
||||||
|
DeleteMarkerVersionID: obj.DeleteMarkerVersionID,
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var batch []ObjectInfo
|
||||||
|
|
||||||
|
next, stop := iter.Pull(objectsIter)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Loop over entries by 1000 and call MultiDelete requests
|
||||||
|
object, ok := next()
|
||||||
|
if !ok {
|
||||||
|
// delete the remaining batch.
|
||||||
|
processRemoveMultiObjectsResponseIter(batch, yield)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasInvalidXMLChar(object.Key) {
|
||||||
|
// Use single DELETE so the object name will be in the request URL instead of the multi-delete XML document.
|
||||||
|
removeResult := c.removeObject(ctx, bucketName, object.Key, RemoveObjectOptions{
|
||||||
|
VersionID: object.VersionID,
|
||||||
|
GovernanceBypass: opts.GovernanceBypass,
|
||||||
|
})
|
||||||
|
if err := removeResult.Err; err != nil {
|
||||||
|
// Version does not exist is not an error ignore and continue.
|
||||||
|
switch ToErrorResponse(err).Code {
|
||||||
|
case "InvalidArgument", "NoSuchVersion":
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !yield(removeResult) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
batch = append(batch, object)
|
||||||
|
if len(batch) < maxEntries {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !processRemoveMultiObjectsResponseIter(batch, yield) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
batch = batch[:0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate and call MultiDelete S3 requests based on entries received from objectsCh
|
// Generate and call MultiDelete S3 requests based on entries received from objectsCh
|
||||||
func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, resultCh chan<- RemoveObjectResult, opts RemoveObjectsOptions) {
|
func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, resultCh chan<- RemoveObjectResult, opts RemoveObjectsOptions) {
|
||||||
maxEntries := 1000
|
maxEntries := 1000
|
||||||
@@ -407,7 +573,7 @@ func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh
|
|||||||
if err := removeResult.Err; err != nil {
|
if err := removeResult.Err; err != nil {
|
||||||
// Version does not exist is not an error ignore and continue.
|
// Version does not exist is not an error ignore and continue.
|
||||||
switch ToErrorResponse(err).Code {
|
switch ToErrorResponse(err).Code {
|
||||||
case "InvalidArgument", "NoSuchVersion":
|
case InvalidArgument, NoSuchVersion:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resultCh <- removeResult
|
resultCh <- removeResult
|
||||||
@@ -442,13 +608,14 @@ func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh
|
|||||||
removeBytes := generateRemoveMultiObjectsRequest(batch)
|
removeBytes := generateRemoveMultiObjectsRequest(batch)
|
||||||
// Execute POST on bucket to remove objects.
|
// Execute POST on bucket to remove objects.
|
||||||
resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
|
resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
queryValues: urlValues,
|
queryValues: urlValues,
|
||||||
contentBody: bytes.NewReader(removeBytes),
|
contentBody: bytes.NewReader(removeBytes),
|
||||||
contentLength: int64(len(removeBytes)),
|
contentLength: int64(len(removeBytes)),
|
||||||
contentMD5Base64: sumMD5Base64(removeBytes),
|
contentMD5Base64: sumMD5Base64(removeBytes),
|
||||||
contentSHA256Hex: sum256Hex(removeBytes),
|
contentSHA256Hex: sum256Hex(removeBytes),
|
||||||
customHeader: headers,
|
customHeader: headers,
|
||||||
|
expect200OKWithError: true,
|
||||||
})
|
})
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -535,7 +702,7 @@ func (c *Client) abortMultipartUpload(ctx context.Context, bucketName, objectNam
|
|||||||
// This is needed specifically for abort and it cannot
|
// This is needed specifically for abort and it cannot
|
||||||
// be converged into default case.
|
// be converged into default case.
|
||||||
errorResponse = ErrorResponse{
|
errorResponse = ErrorResponse{
|
||||||
Code: "NoSuchUpload",
|
Code: NoSuchUpload,
|
||||||
Message: "The specified multipart upload does not exist.",
|
Message: "The specified multipart upload does not exist.",
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
|
|||||||
+8
@@ -107,6 +107,14 @@ type Version struct {
|
|||||||
M int // Parity blocks
|
M int // Parity blocks
|
||||||
} `xml:"Internal"`
|
} `xml:"Internal"`
|
||||||
|
|
||||||
|
// Checksum values. Only returned by AiStor servers.
|
||||||
|
ChecksumCRC32 string `xml:",omitempty"`
|
||||||
|
ChecksumCRC32C string `xml:",omitempty"`
|
||||||
|
ChecksumSHA1 string `xml:",omitempty"`
|
||||||
|
ChecksumSHA256 string `xml:",omitempty"`
|
||||||
|
ChecksumCRC64NVME string `xml:",omitempty"`
|
||||||
|
ChecksumType string `xml:",omitempty"`
|
||||||
|
|
||||||
isDeleteMarker bool
|
isDeleteMarker bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -39,14 +39,14 @@ func (c *Client) BucketExists(ctx context.Context, bucketName string) (bool, err
|
|||||||
})
|
})
|
||||||
defer closeResponse(resp)
|
defer closeResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ToErrorResponse(err).Code == "NoSuchBucket" {
|
if ToErrorResponse(err).Code == NoSuchBucket {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
resperr := httpRespToErrorResponse(resp, bucketName, "")
|
resperr := httpRespToErrorResponse(resp, bucketName, "")
|
||||||
if ToErrorResponse(resperr).Code == "NoSuchBucket" {
|
if ToErrorResponse(resperr).Code == NoSuchBucket {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -63,14 +63,14 @@ func (c *Client) StatObject(ctx context.Context, bucketName, objectName string,
|
|||||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||||
return ObjectInfo{}, ErrorResponse{
|
return ObjectInfo{}, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "InvalidBucketName",
|
Code: InvalidBucketName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
||||||
return ObjectInfo{}, ErrorResponse{
|
return ObjectInfo{}, ErrorResponse{
|
||||||
StatusCode: http.StatusBadRequest,
|
StatusCode: http.StatusBadRequest,
|
||||||
Code: "XMinioInvalidObjectName",
|
Code: XMinioInvalidObjectName,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,8 +102,8 @@ func (c *Client) StatObject(ctx context.Context, bucketName, objectName string,
|
|||||||
if resp.StatusCode == http.StatusMethodNotAllowed && opts.VersionID != "" && deleteMarker {
|
if resp.StatusCode == http.StatusMethodNotAllowed && opts.VersionID != "" && deleteMarker {
|
||||||
errResp := ErrorResponse{
|
errResp := ErrorResponse{
|
||||||
StatusCode: resp.StatusCode,
|
StatusCode: resp.StatusCode,
|
||||||
Code: "MethodNotAllowed",
|
Code: MethodNotAllowed,
|
||||||
Message: "The specified method is not allowed against this resource.",
|
Message: s3ErrorResponseMap[MethodNotAllowed],
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-11
@@ -21,6 +21,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -38,6 +39,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
md5simd "github.com/minio/md5-simd"
|
md5simd "github.com/minio/md5-simd"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/kvcache"
|
"github.com/minio/minio-go/v7/pkg/kvcache"
|
||||||
@@ -45,6 +47,8 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/signer"
|
"github.com/minio/minio-go/v7/pkg/signer"
|
||||||
"github.com/minio/minio-go/v7/pkg/singleflight"
|
"github.com/minio/minio-go/v7/pkg/singleflight"
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
|
|
||||||
|
internalutils "github.com/minio/minio-go/v7/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client implements Amazon S3 compatible methods.
|
// Client implements Amazon S3 compatible methods.
|
||||||
@@ -159,7 +163,7 @@ type Options struct {
|
|||||||
// Global constants.
|
// Global constants.
|
||||||
const (
|
const (
|
||||||
libraryName = "minio-go"
|
libraryName = "minio-go"
|
||||||
libraryVersion = "v7.0.92"
|
libraryVersion = "v7.0.93"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User Agent should always following the below style.
|
// User Agent should always following the below style.
|
||||||
@@ -455,7 +459,7 @@ func (c *Client) HealthCheck(hcDuration time.Duration) (context.CancelFunc, erro
|
|||||||
gcancel()
|
gcancel()
|
||||||
if !IsNetworkOrHostDown(err, false) {
|
if !IsNetworkOrHostDown(err, false) {
|
||||||
switch ToErrorResponse(err).Code {
|
switch ToErrorResponse(err).Code {
|
||||||
case "NoSuchBucket", "AccessDenied", "":
|
case NoSuchBucket, AccessDenied, "":
|
||||||
atomic.CompareAndSwapInt32(&c.healthStatus, offline, online)
|
atomic.CompareAndSwapInt32(&c.healthStatus, offline, online)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,7 +481,7 @@ func (c *Client) HealthCheck(hcDuration time.Duration) (context.CancelFunc, erro
|
|||||||
gcancel()
|
gcancel()
|
||||||
if !IsNetworkOrHostDown(err, false) {
|
if !IsNetworkOrHostDown(err, false) {
|
||||||
switch ToErrorResponse(err).Code {
|
switch ToErrorResponse(err).Code {
|
||||||
case "NoSuchBucket", "AccessDenied", "":
|
case NoSuchBucket, AccessDenied, "":
|
||||||
atomic.CompareAndSwapInt32(&c.healthStatus, offline, online)
|
atomic.CompareAndSwapInt32(&c.healthStatus, offline, online)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,6 +516,8 @@ type requestMetadata struct {
|
|||||||
streamSha256 bool
|
streamSha256 bool
|
||||||
addCrc *ChecksumType
|
addCrc *ChecksumType
|
||||||
trailer http.Header // (http.Request).Trailer. Requires v4 signature.
|
trailer http.Header // (http.Request).Trailer. Requires v4 signature.
|
||||||
|
|
||||||
|
expect200OKWithError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// dumpHTTP - dump HTTP request and response.
|
// dumpHTTP - dump HTTP request and response.
|
||||||
@@ -615,6 +621,28 @@ func (c *Client) do(req *http.Request) (resp *http.Response, err error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Peek resp.Body looking for S3 XMl error response:
|
||||||
|
// - Return the error XML bytes if an error is found
|
||||||
|
// - Make sure to always restablish the whole http response stream before returning
|
||||||
|
func tryParseErrRespFromBody(resp *http.Response) ([]byte, error) {
|
||||||
|
peeker := internalutils.NewPeekReadCloser(resp.Body, 5*humanize.MiByte)
|
||||||
|
defer func() {
|
||||||
|
peeker.ReplayFromStart()
|
||||||
|
resp.Body = peeker
|
||||||
|
}()
|
||||||
|
|
||||||
|
errResp := ErrorResponse{}
|
||||||
|
errBytes, err := xmlDecodeAndBody(peeker, &errResp)
|
||||||
|
if err != nil {
|
||||||
|
var unmarshalErr xml.UnmarshalError
|
||||||
|
if errors.As(err, &unmarshalErr) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return errBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
// List of success status.
|
// List of success status.
|
||||||
var successStatus = []int{
|
var successStatus = []int{
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
@@ -702,16 +730,30 @@ func (c *Client) executeMethod(ctx context.Context, method string, metadata requ
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// For any known successful http status, return quickly.
|
var success bool
|
||||||
|
var errBodyBytes []byte
|
||||||
|
|
||||||
for _, httpStatus := range successStatus {
|
for _, httpStatus := range successStatus {
|
||||||
if httpStatus == res.StatusCode {
|
if httpStatus == res.StatusCode {
|
||||||
return res, nil
|
success = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the body to be saved later.
|
if success {
|
||||||
errBodyBytes, err := io.ReadAll(res.Body)
|
if !metadata.expect200OKWithError {
|
||||||
// res.Body should be closed
|
return res, nil
|
||||||
|
}
|
||||||
|
errBodyBytes, err = tryParseErrRespFromBody(res)
|
||||||
|
if err == nil && len(errBodyBytes) == 0 {
|
||||||
|
// No S3 XML error is found
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errBodyBytes, err = io.ReadAll(res.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// By now, res.Body should be closed
|
||||||
closeResponse(res)
|
closeResponse(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -723,6 +765,7 @@ func (c *Client) executeMethod(ctx context.Context, method string, metadata requ
|
|||||||
|
|
||||||
// For errors verify if its retryable otherwise fail quickly.
|
// For errors verify if its retryable otherwise fail quickly.
|
||||||
errResponse := ToErrorResponse(httpRespToErrorResponse(res, metadata.bucketName, metadata.objectName))
|
errResponse := ToErrorResponse(httpRespToErrorResponse(res, metadata.bucketName, metadata.objectName))
|
||||||
|
err = errResponse
|
||||||
|
|
||||||
// Save the body back again.
|
// Save the body back again.
|
||||||
errBodySeeker.Seek(0, 0) // Seek back to starting point.
|
errBodySeeker.Seek(0, 0) // Seek back to starting point.
|
||||||
@@ -736,11 +779,11 @@ func (c *Client) executeMethod(ctx context.Context, method string, metadata requ
|
|||||||
// region is empty.
|
// region is empty.
|
||||||
if c.region == "" {
|
if c.region == "" {
|
||||||
switch errResponse.Code {
|
switch errResponse.Code {
|
||||||
case "AuthorizationHeaderMalformed":
|
case AuthorizationHeaderMalformed:
|
||||||
fallthrough
|
fallthrough
|
||||||
case "InvalidRegion":
|
case InvalidRegion:
|
||||||
fallthrough
|
fallthrough
|
||||||
case "AccessDenied":
|
case AccessDenied:
|
||||||
if errResponse.Region == "" {
|
if errResponse.Region == "" {
|
||||||
// Region is empty we simply return the error.
|
// Region is empty we simply return the error.
|
||||||
return res, err
|
return res, err
|
||||||
|
|||||||
+4
-4
@@ -84,18 +84,18 @@ func processBucketLocationResponse(resp *http.Response, bucketName string) (buck
|
|||||||
// request. Move forward and let the top level callers
|
// request. Move forward and let the top level callers
|
||||||
// succeed if possible based on their policy.
|
// succeed if possible based on their policy.
|
||||||
switch errResp.Code {
|
switch errResp.Code {
|
||||||
case "NotImplemented":
|
case NotImplemented:
|
||||||
switch errResp.Server {
|
switch errResp.Server {
|
||||||
case "AmazonSnowball":
|
case "AmazonSnowball":
|
||||||
return "snowball", nil
|
return "snowball", nil
|
||||||
case "cloudflare":
|
case "cloudflare":
|
||||||
return "us-east-1", nil
|
return "us-east-1", nil
|
||||||
}
|
}
|
||||||
case "AuthorizationHeaderMalformed":
|
case AuthorizationHeaderMalformed:
|
||||||
fallthrough
|
fallthrough
|
||||||
case "InvalidRegion":
|
case InvalidRegion:
|
||||||
fallthrough
|
fallthrough
|
||||||
case "AccessDenied":
|
case AccessDenied:
|
||||||
if errResp.Region == "" {
|
if errResp.Region == "" {
|
||||||
return "us-east-1", nil
|
return "us-east-1", nil
|
||||||
}
|
}
|
||||||
|
|||||||
+287
-21
@@ -31,6 +31,7 @@ import (
|
|||||||
"hash"
|
"hash"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
|
"iter"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@@ -259,7 +260,7 @@ func cleanupVersionedBucket(bucketName string, c *minio.Client) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isErrNotImplemented(err error) bool {
|
func isErrNotImplemented(err error) bool {
|
||||||
return minio.ToErrorResponse(err).Code == "NotImplemented"
|
return minio.ToErrorResponse(err).Code == minio.NotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func isRunOnFail() bool {
|
func isRunOnFail() bool {
|
||||||
@@ -465,8 +466,8 @@ func testMakeBucketError() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Verify valid error response from server.
|
// Verify valid error response from server.
|
||||||
if minio.ToErrorResponse(err).Code != "BucketAlreadyExists" &&
|
if minio.ToErrorResponse(err).Code != minio.BucketAlreadyExists &&
|
||||||
minio.ToErrorResponse(err).Code != "BucketAlreadyOwnedByYou" {
|
minio.ToErrorResponse(err).Code != minio.BucketAlreadyOwnedByYou {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error returned by server", err)
|
logError(testName, function, args, startTime, "", "Invalid error returned by server", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1073,7 +1074,7 @@ func testPutObjectWithVersioning() {
|
|||||||
var results []minio.ObjectInfo
|
var results []minio.ObjectInfo
|
||||||
for info := range objectsInfo {
|
for info := range objectsInfo {
|
||||||
if info.Err != nil {
|
if info.Err != nil {
|
||||||
logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
|
logError(testName, function, args, startTime, "", "Unexpected error during listing objects", info.Err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
results = append(results, info)
|
results = append(results, info)
|
||||||
@@ -3204,7 +3205,7 @@ func testGetObjectAttributesErrorCases() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorResponse := err.(minio.ErrorResponse)
|
errorResponse := err.(minio.ErrorResponse)
|
||||||
if errorResponse.Code != "NoSuchBucket" {
|
if errorResponse.Code != minio.NoSuchBucket {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error code, expected NoSuchBucket but got "+errorResponse.Code, nil)
|
logError(testName, function, args, startTime, "", "Invalid error code, expected NoSuchBucket but got "+errorResponse.Code, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3247,8 +3248,8 @@ func testGetObjectAttributesErrorCases() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorResponse = err.(minio.ErrorResponse)
|
errorResponse = err.(minio.ErrorResponse)
|
||||||
if errorResponse.Code != "NoSuchKey" {
|
if errorResponse.Code != minio.NoSuchKey {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error code, expected NoSuchKey but got "+errorResponse.Code, nil)
|
logError(testName, function, args, startTime, "", "Invalid error code, expected "+minio.NoSuchKey+" but got "+errorResponse.Code, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3272,8 +3273,8 @@ func testGetObjectAttributesErrorCases() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
errorResponse = err.(minio.ErrorResponse)
|
errorResponse = err.(minio.ErrorResponse)
|
||||||
if errorResponse.Code != "NoSuchVersion" {
|
if errorResponse.Code != minio.NoSuchVersion {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error code, expected NoSuchVersion but got "+errorResponse.Code, nil)
|
logError(testName, function, args, startTime, "", "Invalid error code, expected "+minio.NoSuchVersion+" but got "+errorResponse.Code, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3928,10 +3929,10 @@ func testRemoveMultipleObjects() {
|
|||||||
|
|
||||||
defer cleanupBucket(bucketName, c)
|
defer cleanupBucket(bucketName, c)
|
||||||
|
|
||||||
r := bytes.NewReader(bytes.Repeat([]byte("a"), 8))
|
r := bytes.NewReader(bytes.Repeat([]byte("a"), 1))
|
||||||
|
|
||||||
// Multi remove of 1100 objects
|
// Multi remove of 1100 objects
|
||||||
nrObjects := 200
|
nrObjects := 1100
|
||||||
|
|
||||||
objectsCh := make(chan minio.ObjectInfo)
|
objectsCh := make(chan minio.ObjectInfo)
|
||||||
|
|
||||||
@@ -3940,7 +3941,7 @@ func testRemoveMultipleObjects() {
|
|||||||
// Upload objects and send them to objectsCh
|
// Upload objects and send them to objectsCh
|
||||||
for i := 0; i < nrObjects; i++ {
|
for i := 0; i < nrObjects; i++ {
|
||||||
objectName := "sample" + strconv.Itoa(i) + ".txt"
|
objectName := "sample" + strconv.Itoa(i) + ".txt"
|
||||||
info, err := c.PutObject(context.Background(), bucketName, objectName, r, 8,
|
info, err := c.PutObject(context.Background(), bucketName, objectName, r, 1,
|
||||||
minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError(testName, function, args, startTime, "", "PutObject failed", err)
|
logError(testName, function, args, startTime, "", "PutObject failed", err)
|
||||||
@@ -3968,6 +3969,78 @@ func testRemoveMultipleObjects() {
|
|||||||
logSuccess(testName, function, args, startTime)
|
logSuccess(testName, function, args, startTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test removing multiple objects with Remove API as iterator
|
||||||
|
func testRemoveMultipleObjectsIter() {
|
||||||
|
// initialize logging params
|
||||||
|
startTime := time.Now()
|
||||||
|
testName := getFuncName()
|
||||||
|
function := "RemoveObjects(bucketName, objectsCh)"
|
||||||
|
args := map[string]interface{}{
|
||||||
|
"bucketName": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := NewClient(ClientConfig{})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random bucket name.
|
||||||
|
bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
|
||||||
|
args["bucketName"] = bucketName
|
||||||
|
|
||||||
|
// Make a new bucket.
|
||||||
|
err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "MakeBucket failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanupBucket(bucketName, c)
|
||||||
|
|
||||||
|
buf := []byte("a")
|
||||||
|
|
||||||
|
// Multi remove of 1100 objects
|
||||||
|
nrObjects := 1100
|
||||||
|
|
||||||
|
objectsIter := func() iter.Seq[minio.ObjectInfo] {
|
||||||
|
return func(yield func(minio.ObjectInfo) bool) {
|
||||||
|
// Upload objects and send them to objectsCh
|
||||||
|
for i := 0; i < nrObjects; i++ {
|
||||||
|
objectName := "sample" + strconv.Itoa(i) + ".txt"
|
||||||
|
info, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), 1,
|
||||||
|
minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "PutObject failed", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !yield(minio.ObjectInfo{
|
||||||
|
Key: info.Key,
|
||||||
|
VersionID: info.VersionID,
|
||||||
|
}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call RemoveObjects API
|
||||||
|
results, err := c.RemoveObjectsWithIter(context.Background(), bucketName, objectsIter(), minio.RemoveObjectsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "Unexpected error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for result := range results {
|
||||||
|
if result.Err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "Unexpected error", result.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logSuccess(testName, function, args, startTime)
|
||||||
|
}
|
||||||
|
|
||||||
// Test removing multiple objects and check for results
|
// Test removing multiple objects and check for results
|
||||||
func testRemoveMultipleObjectsWithResult() {
|
func testRemoveMultipleObjectsWithResult() {
|
||||||
// initialize logging params
|
// initialize logging params
|
||||||
@@ -3997,7 +4070,7 @@ func testRemoveMultipleObjectsWithResult() {
|
|||||||
|
|
||||||
defer cleanupVersionedBucket(bucketName, c)
|
defer cleanupVersionedBucket(bucketName, c)
|
||||||
|
|
||||||
r := bytes.NewReader(bytes.Repeat([]byte("a"), 8))
|
buf := []byte("a")
|
||||||
|
|
||||||
nrObjects := 10
|
nrObjects := 10
|
||||||
nrLockedObjects := 5
|
nrLockedObjects := 5
|
||||||
@@ -4009,7 +4082,7 @@ func testRemoveMultipleObjectsWithResult() {
|
|||||||
// Upload objects and send them to objectsCh
|
// Upload objects and send them to objectsCh
|
||||||
for i := 0; i < nrObjects; i++ {
|
for i := 0; i < nrObjects; i++ {
|
||||||
objectName := "sample" + strconv.Itoa(i) + ".txt"
|
objectName := "sample" + strconv.Itoa(i) + ".txt"
|
||||||
info, err := c.PutObject(context.Background(), bucketName, objectName, r, 8,
|
info, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), 1,
|
||||||
minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError(testName, function, args, startTime, "", "PutObject failed", err)
|
logError(testName, function, args, startTime, "", "PutObject failed", err)
|
||||||
@@ -7589,7 +7662,7 @@ func testGetObjectModified() {
|
|||||||
|
|
||||||
// Confirm that a Stat() call in between doesn't change the Object's cached etag.
|
// Confirm that a Stat() call in between doesn't change the Object's cached etag.
|
||||||
_, err = reader.Stat()
|
_, err = reader.Stat()
|
||||||
expectedError := "At least one of the pre-conditions you specified did not hold"
|
expectedError := "At least one of the pre-conditions you specified did not hold."
|
||||||
if err.Error() != expectedError {
|
if err.Error() != expectedError {
|
||||||
logError(testName, function, args, startTime, "", "Expected Stat to fail with error "+expectedError+", but received "+err.Error(), err)
|
logError(testName, function, args, startTime, "", "Expected Stat to fail with error "+expectedError+", but received "+err.Error(), err)
|
||||||
return
|
return
|
||||||
@@ -7751,8 +7824,8 @@ func testMakeBucketErrorV2() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Verify valid error response from server.
|
// Verify valid error response from server.
|
||||||
if minio.ToErrorResponse(err).Code != "BucketAlreadyExists" &&
|
if minio.ToErrorResponse(err).Code != minio.BucketAlreadyExists &&
|
||||||
minio.ToErrorResponse(err).Code != "BucketAlreadyOwnedByYou" {
|
minio.ToErrorResponse(err).Code != minio.BucketAlreadyOwnedByYou {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error returned by server", err)
|
logError(testName, function, args, startTime, "", "Invalid error returned by server", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -11415,6 +11488,87 @@ func testPutObject0ByteV2() {
|
|||||||
logSuccess(testName, function, args, startTime)
|
logSuccess(testName, function, args, startTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test put object with 0 byte object with non-US-ASCII characters.
|
||||||
|
func testPutObjectMetadataNonUSASCIIV2() {
|
||||||
|
// initialize logging params
|
||||||
|
startTime := time.Now()
|
||||||
|
testName := getFuncName()
|
||||||
|
function := "PutObject(bucketName, objectName, reader, size, opts)"
|
||||||
|
args := map[string]interface{}{
|
||||||
|
"bucketName": "",
|
||||||
|
"objectName": "",
|
||||||
|
"size": 0,
|
||||||
|
"opts": "",
|
||||||
|
}
|
||||||
|
metadata := map[string]string{
|
||||||
|
"test-zh": "你好",
|
||||||
|
"test-ja": "こんにちは",
|
||||||
|
"test-ko": "안녕하세요",
|
||||||
|
"test-ru": "Здравствуй",
|
||||||
|
"test-de": "Hallo",
|
||||||
|
"test-it": "Ciao",
|
||||||
|
"test-pt": "Olá",
|
||||||
|
"test-ar": "مرحبا",
|
||||||
|
"test-hi": "नमस्ते",
|
||||||
|
"test-hu": "Helló",
|
||||||
|
"test-ro": "Bună",
|
||||||
|
"test-be": "Прывiтанне",
|
||||||
|
"test-sl": "Pozdravljen",
|
||||||
|
"test-sr": "Здраво",
|
||||||
|
"test-bg": "Здравейте",
|
||||||
|
"test-uk": "Привіт",
|
||||||
|
}
|
||||||
|
c, err := NewClient(ClientConfig{CredsV2: true})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random bucket name.
|
||||||
|
bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
|
||||||
|
args["bucketName"] = bucketName
|
||||||
|
|
||||||
|
// Make a new bucket.
|
||||||
|
err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "MakeBucket failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanupBucket(bucketName, c)
|
||||||
|
|
||||||
|
objectName := bucketName + "unique"
|
||||||
|
args["objectName"] = objectName
|
||||||
|
args["opts"] = minio.PutObjectOptions{}
|
||||||
|
|
||||||
|
// Upload an object.
|
||||||
|
_, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader([]byte("")), 0, minio.PutObjectOptions{
|
||||||
|
UserMetadata: metadata,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "PutObjectWithSize failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "StatObjectWithSize failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if st.Size != 0 {
|
||||||
|
logError(testName, function, args, startTime, "", "Expected upload object size 0 but got "+string(st.Size), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range metadata {
|
||||||
|
if st.Metadata.Get(http.CanonicalHeaderKey("X-Amz-Meta-"+k)) != v {
|
||||||
|
logError(testName, function, args, startTime, "", "Expected upload object metadata "+k+": "+v+" but got "+st.Metadata.Get("X-Amz-Meta-"+k), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logSuccess(testName, function, args, startTime)
|
||||||
|
}
|
||||||
|
|
||||||
// Test expected error cases
|
// Test expected error cases
|
||||||
func testComposeObjectErrorCases() {
|
func testComposeObjectErrorCases() {
|
||||||
// initialize logging params
|
// initialize logging params
|
||||||
@@ -13557,6 +13711,115 @@ func testRemoveObjects() {
|
|||||||
logSuccess(testName, function, args, startTime)
|
logSuccess(testName, function, args, startTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test deleting multiple objects with object retention set in Governance mode, via iterators
|
||||||
|
func testRemoveObjectsIter() {
|
||||||
|
// initialize logging params
|
||||||
|
startTime := time.Now()
|
||||||
|
testName := getFuncName()
|
||||||
|
function := "RemoveObjects(bucketName, objectsCh, opts)"
|
||||||
|
args := map[string]interface{}{
|
||||||
|
"bucketName": "",
|
||||||
|
"objectPrefix": "",
|
||||||
|
"recursive": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := NewClient(ClientConfig{})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random bucket name.
|
||||||
|
bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
|
||||||
|
args["bucketName"] = bucketName
|
||||||
|
objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
|
||||||
|
args["objectName"] = objectName
|
||||||
|
|
||||||
|
// Make a new bucket.
|
||||||
|
err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "MakeBucket failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bufSize := dataFileMap["datafile-129-MB"]
|
||||||
|
reader := getDataReader("datafile-129-MB")
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
_, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "Error uploading object", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace with smaller...
|
||||||
|
bufSize = dataFileMap["datafile-10-kB"]
|
||||||
|
reader = getDataReader("datafile-10-kB")
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
_, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "Error uploading object", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.Date(2030, time.April, 25, 14, 0, 0, 0, time.UTC)
|
||||||
|
m := minio.RetentionMode(minio.Governance)
|
||||||
|
opts := minio.PutObjectRetentionOptions{
|
||||||
|
GovernanceBypass: false,
|
||||||
|
RetainUntilDate: &t,
|
||||||
|
Mode: &m,
|
||||||
|
}
|
||||||
|
err = c.PutObjectRetention(context.Background(), bucketName, objectName, opts)
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "Error setting retention", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objectsIter := c.ListObjectsIter(context.Background(), bucketName, minio.ListObjectsOptions{
|
||||||
|
WithVersions: true,
|
||||||
|
Recursive: true,
|
||||||
|
})
|
||||||
|
results, err := c.RemoveObjectsWithIter(context.Background(), bucketName, objectsIter, minio.RemoveObjectsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "Error sending delete request", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for result := range results {
|
||||||
|
if result.Err != nil {
|
||||||
|
// Error is expected here because Retention is set on the object
|
||||||
|
// and RemoveObjects is called without Bypass Governance
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logError(testName, function, args, startTime, "", "Expected error during deletion", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objectsIter = c.ListObjectsIter(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Recursive: true})
|
||||||
|
results, err = c.RemoveObjectsWithIter(context.Background(), bucketName, objectsIter, minio.RemoveObjectsOptions{
|
||||||
|
GovernanceBypass: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "Error sending delete request", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for result := range results {
|
||||||
|
if result.Err != nil {
|
||||||
|
// Error is not expected here because Retention is set on the object
|
||||||
|
// and RemoveObjects is called with Bypass Governance
|
||||||
|
logError(testName, function, args, startTime, "", "Error detected during deletion", result.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all objects and buckets
|
||||||
|
if err = cleanupVersionedBucket(bucketName, c); err != nil {
|
||||||
|
logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logSuccess(testName, function, args, startTime)
|
||||||
|
}
|
||||||
|
|
||||||
// Test get bucket tags
|
// Test get bucket tags
|
||||||
func testGetBucketTagging() {
|
func testGetBucketTagging() {
|
||||||
// initialize logging params
|
// initialize logging params
|
||||||
@@ -13585,7 +13848,7 @@ func testGetBucketTagging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
||||||
if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
|
if minio.ToErrorResponse(err).Code != minio.NoSuchTagSet {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error from server failed", err)
|
logError(testName, function, args, startTime, "", "Invalid error from server failed", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -13627,7 +13890,7 @@ func testSetBucketTagging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
||||||
if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
|
if minio.ToErrorResponse(err).Code != minio.NoSuchTagSet {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error from server", err)
|
logError(testName, function, args, startTime, "", "Invalid error from server", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -13699,7 +13962,7 @@ func testRemoveBucketTagging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
||||||
if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
|
if minio.ToErrorResponse(err).Code != minio.NoSuchTagSet {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error from server", err)
|
logError(testName, function, args, startTime, "", "Invalid error from server", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -13740,7 +14003,7 @@ func testRemoveBucketTagging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
_, err = c.GetBucketTagging(context.Background(), bucketName)
|
||||||
if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
|
if minio.ToErrorResponse(err).Code != minio.NoSuchTagSet {
|
||||||
logError(testName, function, args, startTime, "", "Invalid error from server", err)
|
logError(testName, function, args, startTime, "", "Invalid error from server", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -13809,6 +14072,7 @@ func main() {
|
|||||||
testPutMultipartObjectWithChecksums(false)
|
testPutMultipartObjectWithChecksums(false)
|
||||||
testPutMultipartObjectWithChecksums(true)
|
testPutMultipartObjectWithChecksums(true)
|
||||||
testPutObject0ByteV2()
|
testPutObject0ByteV2()
|
||||||
|
testPutObjectMetadataNonUSASCIIV2()
|
||||||
testPutObjectNoLengthV2()
|
testPutObjectNoLengthV2()
|
||||||
testPutObjectsUnknownV2()
|
testPutObjectsUnknownV2()
|
||||||
testGetObjectContextV2()
|
testGetObjectContextV2()
|
||||||
@@ -13826,6 +14090,7 @@ func main() {
|
|||||||
testGetObjectS3Zip()
|
testGetObjectS3Zip()
|
||||||
testRemoveMultipleObjects()
|
testRemoveMultipleObjects()
|
||||||
testRemoveMultipleObjectsWithResult()
|
testRemoveMultipleObjectsWithResult()
|
||||||
|
testRemoveMultipleObjectsIter()
|
||||||
testFPutObjectMultipart()
|
testFPutObjectMultipart()
|
||||||
testFPutObject()
|
testFPutObject()
|
||||||
testGetObjectReadSeekFunctional()
|
testGetObjectReadSeekFunctional()
|
||||||
@@ -13852,6 +14117,7 @@ func main() {
|
|||||||
testPutObjectWithContentLanguage()
|
testPutObjectWithContentLanguage()
|
||||||
testListObjects()
|
testListObjects()
|
||||||
testRemoveObjects()
|
testRemoveObjects()
|
||||||
|
testRemoveObjectsIter()
|
||||||
testListObjectVersions()
|
testListObjectVersions()
|
||||||
testStatObjectWithVersioning()
|
testStatObjectWithVersioning()
|
||||||
testGetObjectWithVersioning()
|
testGetObjectWithVersioning()
|
||||||
|
|||||||
+38
-7
@@ -730,6 +730,8 @@ type Metrics struct {
|
|||||||
Errors TimedErrStats `json:"failed,omitempty"`
|
Errors TimedErrStats `json:"failed,omitempty"`
|
||||||
// Total number of entries that are queued for replication
|
// Total number of entries that are queued for replication
|
||||||
QStats InQueueMetric `json:"queued"`
|
QStats InQueueMetric `json:"queued"`
|
||||||
|
// Total number of entries that have replication in progress
|
||||||
|
InProgress InProgressMetric `json:"inProgress"`
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
// Total Pending size in bytes across targets
|
// Total Pending size in bytes across targets
|
||||||
PendingSize uint64 `json:"pendingReplicationSize,omitempty"`
|
PendingSize uint64 `json:"pendingReplicationSize,omitempty"`
|
||||||
@@ -830,6 +832,9 @@ type InQueueMetric struct {
|
|||||||
Max QStat `json:"peak" msg:"pq"`
|
Max QStat `json:"peak" msg:"pq"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InProgressMetric holds stats for objects with replication in progress
|
||||||
|
type InProgressMetric InQueueMetric
|
||||||
|
|
||||||
// MetricName name of replication metric
|
// MetricName name of replication metric
|
||||||
type MetricName string
|
type MetricName string
|
||||||
|
|
||||||
@@ -849,6 +854,14 @@ type WorkerStat struct {
|
|||||||
Max int32 `json:"max"`
|
Max int32 `json:"max"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TgtHealth holds health status of a target
|
||||||
|
type TgtHealth struct {
|
||||||
|
Online bool `json:"online"`
|
||||||
|
LastOnline time.Time `json:"lastOnline"`
|
||||||
|
TotalDowntime time.Duration `json:"totalDowntime"`
|
||||||
|
OfflineCount int64 `json:"offlineCount"`
|
||||||
|
}
|
||||||
|
|
||||||
// ReplMRFStats holds stats of MRF backlog saved to disk in the last 5 minutes
|
// ReplMRFStats holds stats of MRF backlog saved to disk in the last 5 minutes
|
||||||
// and number of entries that failed replication after 3 retries
|
// and number of entries that failed replication after 3 retries
|
||||||
type ReplMRFStats struct {
|
type ReplMRFStats struct {
|
||||||
@@ -863,15 +876,18 @@ type ReplMRFStats struct {
|
|||||||
type ReplQNodeStats struct {
|
type ReplQNodeStats struct {
|
||||||
NodeName string `json:"nodeName"`
|
NodeName string `json:"nodeName"`
|
||||||
Uptime int64 `json:"uptime"`
|
Uptime int64 `json:"uptime"`
|
||||||
Workers WorkerStat `json:"activeWorkers"`
|
Workers WorkerStat `json:"workers"`
|
||||||
|
|
||||||
XferStats map[MetricName]XferStats `json:"transferSummary"`
|
XferStats map[MetricName]XferStats `json:"transferSummary"`
|
||||||
TgtXferStats map[string]map[MetricName]XferStats `json:"tgtTransferStats"`
|
TgtXferStats map[string]map[MetricName]XferStats `json:"tgtTransferStats"`
|
||||||
|
|
||||||
QStats InQueueMetric `json:"queueStats"`
|
QStats InQueueMetric `json:"queueStats"`
|
||||||
MRFStats ReplMRFStats `json:"mrfStats"`
|
InProgressStats InProgressMetric `json:"progressStats"`
|
||||||
Retries CounterSummary `json:"retries"`
|
|
||||||
Errors CounterSummary `json:"errors"`
|
MRFStats ReplMRFStats `json:"mrfStats"`
|
||||||
|
Retries CounterSummary `json:"retries"`
|
||||||
|
Errors CounterSummary `json:"errors"`
|
||||||
|
TgtHealth map[string]TgtHealth `json:"tgtHealth,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CounterSummary denotes the stats counter summary
|
// CounterSummary denotes the stats counter summary
|
||||||
@@ -918,6 +934,19 @@ func (q ReplQueueStats) qStatSummary() InQueueMetric {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inProgressSummary returns cluster level stats for objects with replication in progress
|
||||||
|
func (q ReplQueueStats) inProgressSummary() InProgressMetric {
|
||||||
|
m := InProgressMetric{}
|
||||||
|
for _, v := range q.Nodes {
|
||||||
|
m.Avg.Add(v.InProgressStats.Avg)
|
||||||
|
m.Curr.Add(v.InProgressStats.Curr)
|
||||||
|
if m.Max.Count < v.InProgressStats.Max.Count {
|
||||||
|
m.Max.Add(v.InProgressStats.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// ReplQStats holds stats for objects in replication queue
|
// ReplQStats holds stats for objects in replication queue
|
||||||
type ReplQStats struct {
|
type ReplQStats struct {
|
||||||
Uptime int64 `json:"uptime"`
|
Uptime int64 `json:"uptime"`
|
||||||
@@ -926,7 +955,9 @@ type ReplQStats struct {
|
|||||||
XferStats map[MetricName]XferStats `json:"xferStats"`
|
XferStats map[MetricName]XferStats `json:"xferStats"`
|
||||||
TgtXferStats map[string]map[MetricName]XferStats `json:"tgtXferStats"`
|
TgtXferStats map[string]map[MetricName]XferStats `json:"tgtXferStats"`
|
||||||
|
|
||||||
QStats InQueueMetric `json:"qStats"`
|
QStats InQueueMetric `json:"qStats"`
|
||||||
|
InProgressStats InProgressMetric `json:"progressStats"`
|
||||||
|
|
||||||
MRFStats ReplMRFStats `json:"mrfStats"`
|
MRFStats ReplMRFStats `json:"mrfStats"`
|
||||||
Retries CounterSummary `json:"retries"`
|
Retries CounterSummary `json:"retries"`
|
||||||
Errors CounterSummary `json:"errors"`
|
Errors CounterSummary `json:"errors"`
|
||||||
@@ -935,10 +966,10 @@ type ReplQStats struct {
|
|||||||
// QStats returns cluster level stats for objects in replication queue
|
// QStats returns cluster level stats for objects in replication queue
|
||||||
func (q ReplQueueStats) QStats() (r ReplQStats) {
|
func (q ReplQueueStats) QStats() (r ReplQStats) {
|
||||||
r.QStats = q.qStatSummary()
|
r.QStats = q.qStatSummary()
|
||||||
|
r.InProgressStats = q.inProgressSummary()
|
||||||
r.XferStats = make(map[MetricName]XferStats)
|
r.XferStats = make(map[MetricName]XferStats)
|
||||||
r.TgtXferStats = make(map[string]map[MetricName]XferStats)
|
r.TgtXferStats = make(map[string]map[MetricName]XferStats)
|
||||||
r.Workers = q.Workers()
|
r.Workers = q.Workers()
|
||||||
|
|
||||||
for _, node := range q.Nodes {
|
for _, node := range q.Nodes {
|
||||||
for arn := range node.TgtXferStats {
|
for arn := range node.TgtXferStats {
|
||||||
xmap, ok := node.TgtXferStats[arn]
|
xmap, ok := node.TgtXferStats[arn]
|
||||||
|
|||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Go Library for Amazon S3 Compatible Cloud Storage
|
||||||
|
* Copyright 2015-2025 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PeekReadCloser offers a way to peek a ReadCloser stream and then
|
||||||
|
// return the exact stream of the underlying ReadCloser
|
||||||
|
type PeekReadCloser struct {
|
||||||
|
io.ReadCloser
|
||||||
|
|
||||||
|
recordMode bool
|
||||||
|
recordMaxBuf int
|
||||||
|
recordBuf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplayFromStart ensures next Read() will restart to stream the
|
||||||
|
// underlying ReadCloser stream from the beginning
|
||||||
|
func (prc *PeekReadCloser) ReplayFromStart() {
|
||||||
|
prc.recordMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prc *PeekReadCloser) Read(p []byte) (int, error) {
|
||||||
|
if prc.recordMode {
|
||||||
|
if prc.recordBuf.Len() > prc.recordMaxBuf {
|
||||||
|
return 0, errors.New("maximum peek buffer exceeded")
|
||||||
|
}
|
||||||
|
n, err := prc.ReadCloser.Read(p)
|
||||||
|
prc.recordBuf.Write(p[:n])
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
// Replay mode
|
||||||
|
if prc.recordBuf.Len() > 0 {
|
||||||
|
pn, _ := prc.recordBuf.Read(p)
|
||||||
|
return pn, nil
|
||||||
|
}
|
||||||
|
return prc.ReadCloser.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases the record buffer memory and close the underlying ReadCloser
|
||||||
|
func (prc *PeekReadCloser) Close() error {
|
||||||
|
prc.recordBuf.Reset()
|
||||||
|
return prc.ReadCloser.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPeekReadCloser returns a new peek reader
|
||||||
|
func NewPeekReadCloser(rc io.ReadCloser, maxBufSize int) *PeekReadCloser {
|
||||||
|
return &PeekReadCloser{
|
||||||
|
ReadCloser: rc,
|
||||||
|
recordMode: true, // recording mode by default
|
||||||
|
recordBuf: bytes.NewBuffer(make([]byte, 0, 1024)),
|
||||||
|
recordMaxBuf: maxBufSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -161,7 +161,7 @@ func (p *PostPolicy) SetTagging(tagging string) error {
|
|||||||
}
|
}
|
||||||
_, err := tags.ParseObjectXML(strings.NewReader(tagging))
|
_, err := tags.ParseObjectXML(strings.NewReader(tagging))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("The XML you provided was not well-formed or did not validate against our published schema.") //nolint
|
return errors.New(s3ErrorResponseMap[MalformedXML]) //nolint
|
||||||
}
|
}
|
||||||
policyCond := policyCondition{
|
policyCond := policyCondition{
|
||||||
matchType: "eq",
|
matchType: "eq",
|
||||||
|
|||||||
+2
@@ -104,6 +104,8 @@ var retryableS3Codes = map[string]struct{}{
|
|||||||
"ExpiredToken": {},
|
"ExpiredToken": {},
|
||||||
"ExpiredTokenException": {},
|
"ExpiredTokenException": {},
|
||||||
"SlowDown": {},
|
"SlowDown": {},
|
||||||
|
"SlowDownWrite": {},
|
||||||
|
"SlowDownRead": {},
|
||||||
// Add more AWS S3 codes here.
|
// Add more AWS S3 codes here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+92
-38
@@ -17,46 +17,100 @@
|
|||||||
|
|
||||||
package minio
|
package minio
|
||||||
|
|
||||||
|
// Constants for error keys
|
||||||
|
const (
|
||||||
|
NoSuchBucket = "NoSuchBucket"
|
||||||
|
NoSuchKey = "NoSuchKey"
|
||||||
|
NoSuchUpload = "NoSuchUpload"
|
||||||
|
AccessDenied = "AccessDenied"
|
||||||
|
Conflict = "Conflict"
|
||||||
|
PreconditionFailed = "PreconditionFailed"
|
||||||
|
InvalidArgument = "InvalidArgument"
|
||||||
|
EntityTooLarge = "EntityTooLarge"
|
||||||
|
EntityTooSmall = "EntityTooSmall"
|
||||||
|
UnexpectedEOF = "UnexpectedEOF"
|
||||||
|
APINotSupported = "APINotSupported"
|
||||||
|
InvalidRegion = "InvalidRegion"
|
||||||
|
NoSuchBucketPolicy = "NoSuchBucketPolicy"
|
||||||
|
BadDigest = "BadDigest"
|
||||||
|
IncompleteBody = "IncompleteBody"
|
||||||
|
InternalError = "InternalError"
|
||||||
|
InvalidAccessKeyID = "InvalidAccessKeyId"
|
||||||
|
InvalidBucketName = "InvalidBucketName"
|
||||||
|
InvalidDigest = "InvalidDigest"
|
||||||
|
InvalidRange = "InvalidRange"
|
||||||
|
MalformedXML = "MalformedXML"
|
||||||
|
MissingContentLength = "MissingContentLength"
|
||||||
|
MissingContentMD5 = "MissingContentMD5"
|
||||||
|
MissingRequestBodyError = "MissingRequestBodyError"
|
||||||
|
NotImplemented = "NotImplemented"
|
||||||
|
RequestTimeTooSkewed = "RequestTimeTooSkewed"
|
||||||
|
SignatureDoesNotMatch = "SignatureDoesNotMatch"
|
||||||
|
MethodNotAllowed = "MethodNotAllowed"
|
||||||
|
InvalidPart = "InvalidPart"
|
||||||
|
InvalidPartOrder = "InvalidPartOrder"
|
||||||
|
InvalidObjectState = "InvalidObjectState"
|
||||||
|
AuthorizationHeaderMalformed = "AuthorizationHeaderMalformed"
|
||||||
|
MalformedPOSTRequest = "MalformedPOSTRequest"
|
||||||
|
BucketNotEmpty = "BucketNotEmpty"
|
||||||
|
AllAccessDisabled = "AllAccessDisabled"
|
||||||
|
MalformedPolicy = "MalformedPolicy"
|
||||||
|
MissingFields = "MissingFields"
|
||||||
|
AuthorizationQueryParametersError = "AuthorizationQueryParametersError"
|
||||||
|
MalformedDate = "MalformedDate"
|
||||||
|
BucketAlreadyOwnedByYou = "BucketAlreadyOwnedByYou"
|
||||||
|
InvalidDuration = "InvalidDuration"
|
||||||
|
XAmzContentSHA256Mismatch = "XAmzContentSHA256Mismatch"
|
||||||
|
XMinioInvalidObjectName = "XMinioInvalidObjectName"
|
||||||
|
NoSuchCORSConfiguration = "NoSuchCORSConfiguration"
|
||||||
|
BucketAlreadyExists = "BucketAlreadyExists"
|
||||||
|
NoSuchVersion = "NoSuchVersion"
|
||||||
|
NoSuchTagSet = "NoSuchTagSet"
|
||||||
|
Testing = "Testing"
|
||||||
|
Success = "Success"
|
||||||
|
)
|
||||||
|
|
||||||
// Non exhaustive list of AWS S3 standard error responses -
|
// Non exhaustive list of AWS S3 standard error responses -
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||||
var s3ErrorResponseMap = map[string]string{
|
var s3ErrorResponseMap = map[string]string{
|
||||||
"AccessDenied": "Access Denied.",
|
AccessDenied: "Access Denied.",
|
||||||
"BadDigest": "The Content-Md5 you specified did not match what we received.",
|
BadDigest: "The Content-Md5 you specified did not match what we received.",
|
||||||
"EntityTooSmall": "Your proposed upload is smaller than the minimum allowed object size.",
|
EntityTooSmall: "Your proposed upload is smaller than the minimum allowed object size.",
|
||||||
"EntityTooLarge": "Your proposed upload exceeds the maximum allowed object size.",
|
EntityTooLarge: "Your proposed upload exceeds the maximum allowed object size.",
|
||||||
"IncompleteBody": "You did not provide the number of bytes specified by the Content-Length HTTP header.",
|
IncompleteBody: "You did not provide the number of bytes specified by the Content-Length HTTP header.",
|
||||||
"InternalError": "We encountered an internal error, please try again.",
|
InternalError: "We encountered an internal error, please try again.",
|
||||||
"InvalidAccessKeyId": "The access key ID you provided does not exist in our records.",
|
InvalidAccessKeyID: "The access key ID you provided does not exist in our records.",
|
||||||
"InvalidBucketName": "The specified bucket is not valid.",
|
InvalidBucketName: "The specified bucket is not valid.",
|
||||||
"InvalidDigest": "The Content-Md5 you specified is not valid.",
|
InvalidDigest: "The Content-Md5 you specified is not valid.",
|
||||||
"InvalidRange": "The requested range is not satisfiable",
|
InvalidRange: "The requested range is not satisfiable.",
|
||||||
"MalformedXML": "The XML you provided was not well-formed or did not validate against our published schema.",
|
MalformedXML: "The XML you provided was not well-formed or did not validate against our published schema.",
|
||||||
"MissingContentLength": "You must provide the Content-Length HTTP header.",
|
MissingContentLength: "You must provide the Content-Length HTTP header.",
|
||||||
"MissingContentMD5": "Missing required header for this request: Content-Md5.",
|
MissingContentMD5: "Missing required header for this request: Content-Md5.",
|
||||||
"MissingRequestBodyError": "Request body is empty.",
|
MissingRequestBodyError: "Request body is empty.",
|
||||||
"NoSuchBucket": "The specified bucket does not exist.",
|
NoSuchBucket: "The specified bucket does not exist.",
|
||||||
"NoSuchBucketPolicy": "The bucket policy does not exist",
|
NoSuchBucketPolicy: "The bucket policy does not exist.",
|
||||||
"NoSuchKey": "The specified key does not exist.",
|
NoSuchKey: "The specified key does not exist.",
|
||||||
"NoSuchUpload": "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
|
NoSuchUpload: "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
|
||||||
"NotImplemented": "A header you provided implies functionality that is not implemented",
|
NotImplemented: "A header you provided implies functionality that is not implemented.",
|
||||||
"PreconditionFailed": "At least one of the pre-conditions you specified did not hold",
|
PreconditionFailed: "At least one of the pre-conditions you specified did not hold.",
|
||||||
"RequestTimeTooSkewed": "The difference between the request time and the server's time is too large.",
|
RequestTimeTooSkewed: "The difference between the request time and the server's time is too large.",
|
||||||
"SignatureDoesNotMatch": "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
|
SignatureDoesNotMatch: "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
|
||||||
"MethodNotAllowed": "The specified method is not allowed against this resource.",
|
MethodNotAllowed: "The specified method is not allowed against this resource.",
|
||||||
"InvalidPart": "One or more of the specified parts could not be found.",
|
InvalidPart: "One or more of the specified parts could not be found.",
|
||||||
"InvalidPartOrder": "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
InvalidPartOrder: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
||||||
"InvalidObjectState": "The operation is not valid for the current state of the object.",
|
InvalidObjectState: "The operation is not valid for the current state of the object.",
|
||||||
"AuthorizationHeaderMalformed": "The authorization header is malformed; the region is wrong.",
|
AuthorizationHeaderMalformed: "The authorization header is malformed; the region is wrong.",
|
||||||
"MalformedPOSTRequest": "The body of your POST request is not well-formed multipart/form-data.",
|
MalformedPOSTRequest: "The body of your POST request is not well-formed multipart/form-data.",
|
||||||
"BucketNotEmpty": "The bucket you tried to delete is not empty",
|
BucketNotEmpty: "The bucket you tried to delete is not empty.",
|
||||||
"AllAccessDisabled": "All access to this bucket has been disabled.",
|
AllAccessDisabled: "All access to this bucket has been disabled.",
|
||||||
"MalformedPolicy": "Policy has invalid resource.",
|
MalformedPolicy: "Policy has invalid resource.",
|
||||||
"MissingFields": "Missing fields in request.",
|
MissingFields: "Missing fields in request.",
|
||||||
"AuthorizationQueryParametersError": "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".",
|
AuthorizationQueryParametersError: "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".",
|
||||||
"MalformedDate": "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
|
MalformedDate: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
|
||||||
"BucketAlreadyOwnedByYou": "Your previous request to create the named bucket succeeded and you already own it.",
|
BucketAlreadyOwnedByYou: "Your previous request to create the named bucket succeeded and you already own it.",
|
||||||
"InvalidDuration": "Duration provided in the request is invalid.",
|
InvalidDuration: "Duration provided in the request is invalid.",
|
||||||
"XAmzContentSHA256Mismatch": "The provided 'x-amz-content-sha256' header does not match what was computed.",
|
XAmzContentSHA256Mismatch: "The provided 'x-amz-content-sha256' header does not match what was computed.",
|
||||||
"NoSuchCORSConfiguration": "The specified bucket does not have a CORS configuration.",
|
NoSuchCORSConfiguration: "The specified bucket does not have a CORS configuration.",
|
||||||
|
Conflict: "Bucket not empty.",
|
||||||
// Add new API errors here.
|
// Add new API errors here.
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-5
@@ -30,6 +30,7 @@ import (
|
|||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -210,6 +211,7 @@ func extractObjMetadata(header http.Header) http.Header {
|
|||||||
"X-Amz-Server-Side-Encryption",
|
"X-Amz-Server-Side-Encryption",
|
||||||
"X-Amz-Tagging-Count",
|
"X-Amz-Tagging-Count",
|
||||||
"X-Amz-Meta-",
|
"X-Amz-Meta-",
|
||||||
|
"X-Minio-Meta-",
|
||||||
// Add new headers to be preserved.
|
// Add new headers to be preserved.
|
||||||
// if you add new headers here, please extend
|
// if you add new headers here, please extend
|
||||||
// PutObjectOptions{} to preserve them
|
// PutObjectOptions{} to preserve them
|
||||||
@@ -223,6 +225,16 @@ func extractObjMetadata(header http.Header) http.Header {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
found = true
|
found = true
|
||||||
|
if prefix == "X-Amz-Meta-" || prefix == "X-Minio-Meta-" {
|
||||||
|
for index, val := range v {
|
||||||
|
if strings.HasPrefix(val, "=?") {
|
||||||
|
decoder := mime.WordDecoder{}
|
||||||
|
if decoded, err := decoder.DecodeHeader(val); err == nil {
|
||||||
|
v[index] = decoded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
@@ -268,7 +280,7 @@ func ToObjectInfo(bucketName, objectName string, h http.Header) (ObjectInfo, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Content-Length is not valid
|
// Content-Length is not valid
|
||||||
return ObjectInfo{}, ErrorResponse{
|
return ObjectInfo{}, ErrorResponse{
|
||||||
Code: "InternalError",
|
Code: InternalError,
|
||||||
Message: fmt.Sprintf("Content-Length is not an integer, failed with %v", err),
|
Message: fmt.Sprintf("Content-Length is not an integer, failed with %v", err),
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
@@ -283,7 +295,7 @@ func ToObjectInfo(bucketName, objectName string, h http.Header) (ObjectInfo, err
|
|||||||
mtime, err := parseRFC7231Time(h.Get("Last-Modified"))
|
mtime, err := parseRFC7231Time(h.Get("Last-Modified"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ObjectInfo{}, ErrorResponse{
|
return ObjectInfo{}, ErrorResponse{
|
||||||
Code: "InternalError",
|
Code: InternalError,
|
||||||
Message: fmt.Sprintf("Last-Modified time format is invalid, failed with %v", err),
|
Message: fmt.Sprintf("Last-Modified time format is invalid, failed with %v", err),
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
@@ -305,7 +317,7 @@ func ToObjectInfo(bucketName, objectName string, h http.Header) (ObjectInfo, err
|
|||||||
expiry, err = parseRFC7231Time(expiryStr)
|
expiry, err = parseRFC7231Time(expiryStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ObjectInfo{}, ErrorResponse{
|
return ObjectInfo{}, ErrorResponse{
|
||||||
Code: "InternalError",
|
Code: InternalError,
|
||||||
Message: fmt.Sprintf("'Expiry' is not in supported format: %v", err),
|
Message: fmt.Sprintf("'Expiry' is not in supported format: %v", err),
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
@@ -327,7 +339,7 @@ func ToObjectInfo(bucketName, objectName string, h http.Header) (ObjectInfo, err
|
|||||||
userTags, err := tags.ParseObjectTags(h.Get(amzTaggingHeader))
|
userTags, err := tags.ParseObjectTags(h.Get(amzTaggingHeader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ObjectInfo{}, ErrorResponse{
|
return ObjectInfo{}, ErrorResponse{
|
||||||
Code: "InternalError",
|
Code: InternalError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +348,7 @@ func ToObjectInfo(bucketName, objectName string, h http.Header) (ObjectInfo, err
|
|||||||
tagCount, err = strconv.Atoi(count)
|
tagCount, err = strconv.Atoi(count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ObjectInfo{}, ErrorResponse{
|
return ObjectInfo{}, ErrorResponse{
|
||||||
Code: "InternalError",
|
Code: InternalError,
|
||||||
Message: fmt.Sprintf("x-amz-tagging-count is not an integer, failed with %v", err),
|
Message: fmt.Sprintf("x-amz-tagging-count is not an integer, failed with %v", err),
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Key: objectName,
|
Key: objectName,
|
||||||
|
|||||||
+1
-1
@@ -485,7 +485,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to list shares")
|
return nil, errors.Wrap(err, "failed to list shares")
|
||||||
}
|
}
|
||||||
u, err := utils.GetUser(forUser, gwc)
|
u, err := utils.GetUser(ctx, forUser, gwc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errtypes.BadRequest("user not found")
|
return nil, errtypes.BadRequest("user not found")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -843,7 +843,7 @@ func (m *Manager) ListReceivedShares(ctx context.Context, filters []*collaborati
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
u, err := utils.GetUser(forUser, client)
|
u, err := utils.GetUser(ctx, forUser, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errtypes.BadRequest("user not found")
|
return nil, errtypes.BadRequest("user not found")
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
Vendored
+3
-3
@@ -61,7 +61,7 @@ type DBShare struct {
|
|||||||
type UserConverter interface {
|
type UserConverter interface {
|
||||||
UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error)
|
UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error)
|
||||||
UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error)
|
UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error)
|
||||||
GetUser(userid *userpb.UserId) (*userpb.User, error)
|
GetUser(ctx context.Context, userid *userpb.UserId) (*userpb.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GatewayUserConverter converts usernames and ids using the gateway
|
// GatewayUserConverter converts usernames and ids using the gateway
|
||||||
@@ -140,12 +140,12 @@ func (c *GatewayUserConverter) UserNameToUserID(ctx context.Context, username st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUser gets the user
|
// GetUser gets the user
|
||||||
func (c *GatewayUserConverter) GetUser(userid *userpb.UserId) (*userpb.User, error) {
|
func (c *GatewayUserConverter) GetUser(ctx context.Context, userid *userpb.UserId) (*userpb.User, error) {
|
||||||
gwc, err := pool.GetGatewayServiceClient(c.gwAddr)
|
gwc, err := pool.GetGatewayServiceClient(c.gwAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return utils.GetUser(userid, gwc)
|
return utils.GetUser(ctx, userid, gwc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mgr) formatGrantee(ctx context.Context, g *provider.Grantee) (int, string, error) {
|
func (m *mgr) formatGrantee(ctx context.Context, g *provider.Grantee) (int, string, error) {
|
||||||
|
|||||||
Generated
Vendored
+1
-1
@@ -339,7 +339,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) (
|
|||||||
func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter, forUser *userpb.UserId) ([]*collaboration.ReceivedShare, error) {
|
func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter, forUser *userpb.UserId) ([]*collaboration.ReceivedShare, error) {
|
||||||
user := ctxpkg.ContextMustGetUser(ctx)
|
user := ctxpkg.ContextMustGetUser(ctx)
|
||||||
if user.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE {
|
if user.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE {
|
||||||
u, err := m.userConverter.GetUser(forUser)
|
u, err := m.userConverter.GetUser(ctx, forUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errtypes.BadRequest("user not found")
|
return nil, errtypes.BadRequest("user not found")
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-6
@@ -71,14 +71,21 @@ func GetServiceUserToken(ctx context.Context, gwc gateway.GatewayAPIClient, serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUser gets the specified user
|
// GetUser gets the specified user
|
||||||
// Deprecated: Use GetUserWithContext()
|
func GetUser(ctx context.Context, userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
|
||||||
func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
|
return getUser(ctx, userID, false, gwc)
|
||||||
return GetUserWithContext(context.Background(), userID, gwc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserWithContext gets the specified user
|
// GetUserNoGroups gets the specified user without expanding groupmemberships
|
||||||
func GetUserWithContext(ctx context.Context, userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
|
func GetUserNoGroups(ctx context.Context, userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) {
|
||||||
getUserResponse, err := gwc.GetUser(ctx, &user.GetUserRequest{UserId: userID})
|
return getUser(ctx, userID, true, gwc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUser gets the specified user
|
||||||
|
func getUser(ctx context.Context, userID *user.UserId, skipGroups bool, gwc gateway.GatewayAPIClient) (*user.User, error) {
|
||||||
|
getUserResponse, err := gwc.GetUser(ctx, &user.GetUserRequest{
|
||||||
|
UserId: userID,
|
||||||
|
SkipFetchingUserGroups: skipGroups,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+6
-5
@@ -275,7 +275,7 @@ github.com/cenkalti/backoff
|
|||||||
# github.com/cenkalti/backoff/v5 v5.0.2
|
# github.com/cenkalti/backoff/v5 v5.0.2
|
||||||
## explicit; go 1.23
|
## explicit; go 1.23
|
||||||
github.com/cenkalti/backoff/v5
|
github.com/cenkalti/backoff/v5
|
||||||
# github.com/ceph/go-ceph v0.33.0
|
# github.com/ceph/go-ceph v0.34.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
github.com/ceph/go-ceph/cephfs
|
github.com/ceph/go-ceph/cephfs
|
||||||
github.com/ceph/go-ceph/cephfs/admin
|
github.com/ceph/go-ceph/cephfs/admin
|
||||||
@@ -294,8 +294,8 @@ github.com/cespare/xxhash/v2
|
|||||||
# github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73
|
# github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73
|
||||||
## explicit
|
## explicit
|
||||||
github.com/cevaris/ordered_map
|
github.com/cevaris/ordered_map
|
||||||
# github.com/cloudflare/circl v1.3.7
|
# github.com/cloudflare/circl v1.6.1
|
||||||
## explicit; go 1.19
|
## explicit; go 1.22.0
|
||||||
github.com/cloudflare/circl/dh/x25519
|
github.com/cloudflare/circl/dh/x25519
|
||||||
github.com/cloudflare/circl/dh/x448
|
github.com/cloudflare/circl/dh/x448
|
||||||
github.com/cloudflare/circl/ecc/goldilocks
|
github.com/cloudflare/circl/ecc/goldilocks
|
||||||
@@ -946,7 +946,7 @@ github.com/minio/highwayhash
|
|||||||
# github.com/minio/md5-simd v1.1.2
|
# github.com/minio/md5-simd v1.1.2
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/minio/md5-simd
|
github.com/minio/md5-simd
|
||||||
# github.com/minio/minio-go/v7 v7.0.92
|
# github.com/minio/minio-go/v7 v7.0.93
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
github.com/minio/minio-go/v7
|
github.com/minio/minio-go/v7
|
||||||
github.com/minio/minio-go/v7/internal/json
|
github.com/minio/minio-go/v7/internal/json
|
||||||
@@ -963,6 +963,7 @@ github.com/minio/minio-go/v7/pkg/signer
|
|||||||
github.com/minio/minio-go/v7/pkg/singleflight
|
github.com/minio/minio-go/v7/pkg/singleflight
|
||||||
github.com/minio/minio-go/v7/pkg/sse
|
github.com/minio/minio-go/v7/pkg/sse
|
||||||
github.com/minio/minio-go/v7/pkg/tags
|
github.com/minio/minio-go/v7/pkg/tags
|
||||||
|
github.com/minio/minio-go/v7/pkg/utils
|
||||||
# github.com/mitchellh/copystructure v1.2.0
|
# github.com/mitchellh/copystructure v1.2.0
|
||||||
## explicit; go 1.15
|
## explicit; go 1.15
|
||||||
github.com/mitchellh/copystructure
|
github.com/mitchellh/copystructure
|
||||||
@@ -1209,7 +1210,7 @@ github.com/open-policy-agent/opa/v1/version
|
|||||||
# github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450
|
# github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/opencloud-eu/libre-graph-api-go
|
github.com/opencloud-eu/libre-graph-api-go
|
||||||
# github.com/opencloud-eu/reva/v2 v2.33.1
|
# github.com/opencloud-eu/reva/v2 v2.33.2-0.20250612080213-85468735dbd6
|
||||||
## explicit; go 1.24.1
|
## explicit; go 1.24.1
|
||||||
github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace
|
github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace
|
||||||
github.com/opencloud-eu/reva/v2/cmd/revad/runtime
|
github.com/opencloud-eu/reva/v2/cmd/revad/runtime
|
||||||
|
|||||||
Reference in New Issue
Block a user