Compare commits

..

37 Commits

Author SHA1 Message Date
Shireesh Anjal c61c4b71b2 Upgrade madmin-go to 2.0.20 (#17054) 2023-04-20 10:56:55 -07:00
Harshavardhana 84f31ed45d simplify MRF, converge it to regular healing (#17026) 2023-04-19 07:47:42 -07:00
jiuker 8a81e317d6 verify maxPartID in object options helpers (#17015) 2023-04-18 22:34:30 -07:00
Anis Eleuch 224d9a752f fix: the race in healing tracker code (#17048) 2023-04-18 14:49:56 -07:00
Anis Eleuch 0db34e4b85 Listen bucket events to send empty events with new line (#17037) 2023-04-18 08:11:30 -07:00
Harshavardhana 8a9b9832fd add Dial timeout for Kafka broker pings (#17044) 2023-04-17 15:45:01 -07:00
Klaus Post f66625be67 Snowball: Extract headers for metadata (#17042) 2023-04-17 12:16:54 -07:00
Harshavardhana 6825bd7e75 fix: inlined objects don't need to honor long locks (#17039) 2023-04-17 12:16:37 -07:00
Denis Krivenko 18515a4e3b Reformat helm chart templates (#16947) 2023-04-16 23:04:15 -07:00
Klaus Post 839b9c9271 Reduce allocations in Walkdir (#17036) 2023-04-15 10:25:25 -07:00
Harshavardhana dd9ed85e22 implement support for FTP/SFTP server (#16952) 2023-04-15 07:34:02 -07:00
jiuker e96c88e914 fix: DeleteBucketThrottle must delete ARN (#17034) 2023-04-15 02:14:26 -07:00
jiuker 6c1410f7f5 fix: Type of rejection for FIFO quota input (#17016) 2023-04-15 01:22:18 -07:00
Krishnan Parthasarathi f92450d8b3 commonParity should pick readable FileInfo (#17032) 2023-04-14 16:23:28 -07:00
Klaus Post c133979b8e Add part count to checksum (#17035) 2023-04-14 09:44:45 -07:00
Poorna a9269cee29 heal: avoid logging version not found (#17031) 2023-04-13 19:45:52 -07:00
Harshavardhana cf42ede92c upgrade helm to v5.0.8
Signed-off-by: Harshavardhana <harsha@minio.io>
2023-04-13 14:49:51 -07:00
Klaus Post 958a480e53 fix: lambda function expiration when cred.Expiration is set (#17029) 2023-04-13 08:10:57 -07:00
吴小白 62151a751d build: support loong64 (#17027) 2023-04-13 08:10:23 -07:00
Minio Trusted f1ab9df2ee Update yaml files to latest version RELEASE.2023-04-13T03-08-07Z 2023-04-13 07:28:46 +00:00
Anis Eleuch a42650c065 Add minio_bucket_usage_version_total metric to Prometheus (#17023) 2023-04-12 20:08:07 -07:00
Harshavardhana bdad3730f7 fix: do not error out if the local bucket is missing (#17025) 2023-04-12 15:44:16 -07:00
Harshavardhana a5835cecbf fix: regression in counting total requests (#17024) 2023-04-12 14:37:19 -07:00
Denis Krivenko b19620b324 Remove redundant namespace from the helm chart (#16968) 2023-04-11 21:09:29 -07:00
Poorna cd6dec49c0 Add trace support for ilm activity (#16993) 2023-04-11 19:22:32 -07:00
Poorna d350654aee config: fix duplication of replication priority key (#17014) 2023-04-11 19:22:10 -07:00
Krishnan Parthasarathi 6877578bbc Update minio_node_bucket_scans_finished metrics (#17006) 2023-04-11 19:21:34 -07:00
Kunal Singh 10693fddfa docs: fix repetition in wordings (#16999) 2023-04-11 13:23:14 -07:00
Harshavardhana f3682b6149 allow writes to pools with inconsistent xl.meta (#17008) 2023-04-11 11:17:46 -07:00
ferhat elmas 056ca0c68e refactor: reuse open file in storage interface (#16970) 2023-04-09 23:09:28 -07:00
Krishnan Parthasarathi 25f7a8e406 Indicate RenameData is called by healObject (#16997) 2023-04-09 10:25:37 -07:00
Anis Eleuch 1f1c267b6c Add used inodes to disk info (#16994) 2023-04-07 07:52:14 -07:00
ferhat elmas eab1dc927b chore: fix automatic issue handling from linter in ci (#16969) 2023-04-07 07:51:31 -07:00
Anis Eleuch fc94ea1ced trace: Fix <unknown> func name of requests rejected by max clients (#16977) 2023-04-07 07:51:12 -07:00
jiuker 67d2cf8f30 add Err to Get getRemoteTargetClient. (#16982) 2023-04-07 07:50:46 -07:00
Minio Trusted 2c85b84cbc Update yaml files to latest version RELEASE.2023-04-07T05-28-58Z 2023-04-07 06:07:27 +00:00
Harshavardhana 09a25ea7b7 lint: fix some lint issues on files 2023-04-06 22:42:10 -07:00
97 changed files with 3935 additions and 1141 deletions
+1020
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -37,6 +37,10 @@ check-gen: ## check for updated autogenerated files
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
lint: getdeps ## runs golangci-lint suite of linters
@echo "Running $@ check"
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml
lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
@echo "Running $@ check"
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
-2
View File
@@ -86,8 +86,6 @@ chmod +x minio
./minio server /data
```
Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
The following table lists supported architectures. Replace the `wget` URL with the architecture for your Linux host.
| Architecture | URL |
+2 -2
View File
@@ -75,11 +75,11 @@ check_minimum_version() {
assert_is_supported_arch() {
case "${ARCH}" in
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x )
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x | loong64 | loongarch64 )
return
;;
*)
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x]"
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x, loong64, loongarch64]"
exit 1
esac
}
-10
View File
@@ -85,11 +85,6 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
return
}
if quotaConfig.Type == "fifo" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
@@ -1032,11 +1027,6 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
continue
}
if quotaConfig.Type == "fifo" {
rpt.SetStatus(bucket, fileName, fmt.Errorf("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore"))
continue
}
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
if err != nil {
rpt.SetStatus(bucket, fileName, err)
+1 -6
View File
@@ -869,15 +869,10 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
if !h.settings.Recursive {
if h.object != "" {
// Check if an object named as the objPrefix exists,
// and if so heal it.
oi, err := objAPI.GetObjectInfo(h.ctx, bucket, h.object, ObjectOptions{})
if err == nil {
if err = h.healObject(bucket, h.object, oi.VersionID); err != nil {
if err := h.healObject(bucket, h.object, ""); err != nil {
return err
}
}
}
return nil
}
+2
View File
@@ -2097,6 +2097,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrObjectLockInvalidHeaders
case objectlock.ErrMalformedXML:
apiErr = ErrMalformedXML
case errInvalidMaxParts:
apiErr = ErrInvalidMaxParts
}
// Compression errors
+88 -11
View File
@@ -26,6 +26,7 @@ import (
"os"
"sort"
"strings"
"sync"
"time"
"github.com/dustin/go-humanize"
@@ -44,6 +45,7 @@ const (
// healingTracker is used to persist healing information during a heal.
type healingTracker struct {
disk StorageAPI `msg:"-"`
mu *sync.RWMutex `msg:"-"`
ID string
PoolIndex int
@@ -112,22 +114,76 @@ func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker,
}
h.disk = disk
h.ID = diskID
h.mu = &sync.RWMutex{}
return &h, nil
}
// newHealingTracker will create a new healing tracker for the disk.
func newHealingTracker(disk StorageAPI, healID string) *healingTracker {
diskID, _ := disk.GetDiskID()
h := healingTracker{
disk: disk,
ID: diskID,
HealID: healID,
Path: disk.String(),
Endpoint: disk.Endpoint().String(),
Started: time.Now().UTC(),
func newHealingTracker() *healingTracker {
return &healingTracker{
mu: &sync.RWMutex{},
}
}
func initHealingTracker(disk StorageAPI, healID string) *healingTracker {
h := newHealingTracker()
diskID, _ := disk.GetDiskID()
h.disk = disk
h.ID = diskID
h.HealID = healID
h.Path = disk.String()
h.Endpoint = disk.Endpoint().String()
h.Started = time.Now().UTC()
h.PoolIndex, h.SetIndex, h.DiskIndex = disk.GetDiskLoc()
return &h
return h
}
func (h healingTracker) getLastUpdate() time.Time {
h.mu.RLock()
defer h.mu.RUnlock()
return h.LastUpdate
}
func (h healingTracker) getBucket() string {
h.mu.RLock()
defer h.mu.RUnlock()
return h.Bucket
}
func (h *healingTracker) setBucket(bucket string) {
h.mu.Lock()
defer h.mu.Unlock()
h.Bucket = bucket
}
func (h healingTracker) getObject() string {
h.mu.RLock()
defer h.mu.RUnlock()
return h.Object
}
func (h *healingTracker) setObject(object string) {
h.mu.Lock()
defer h.mu.Unlock()
h.Object = object
}
func (h *healingTracker) updateProgress(success bool, bytes uint64) {
h.mu.Lock()
defer h.mu.Unlock()
if success {
h.ItemsHealed++
h.BytesDone += bytes
} else {
h.ItemsFailed++
h.BytesFailed += bytes
}
}
// update will update the tracker on the disk.
@@ -136,15 +192,18 @@ func (h *healingTracker) update(ctx context.Context) error {
if h.disk.Healing() == nil {
return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID)
}
h.mu.Lock()
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
h.ID, _ = h.disk.GetDiskID()
h.PoolIndex, h.SetIndex, h.DiskIndex = h.disk.GetDiskLoc()
}
h.mu.Unlock()
return h.save(ctx)
}
// save will unconditionally save the tracker and will be created if not existing.
func (h *healingTracker) save(ctx context.Context) error {
h.mu.Lock()
if h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
// Attempt to get location.
if api := newObjectLayerFn(); api != nil {
@@ -155,6 +214,7 @@ func (h *healingTracker) save(ctx context.Context) error {
}
h.LastUpdate = time.Now().UTC()
htrackerBytes, err := h.MarshalMsg(nil)
h.mu.Unlock()
if err != nil {
return err
}
@@ -176,6 +236,8 @@ func (h *healingTracker) delete(ctx context.Context) error {
}
func (h *healingTracker) isHealed(bucket string) bool {
h.mu.RLock()
defer h.mu.RUnlock()
for _, v := range h.HealedBuckets {
if v == bucket {
return true
@@ -186,6 +248,9 @@ func (h *healingTracker) isHealed(bucket string) bool {
// resume will reset progress to the numbers at the start of the bucket.
func (h *healingTracker) resume() {
h.mu.Lock()
defer h.mu.Unlock()
h.ItemsHealed = h.ResumeItemsHealed
h.ItemsFailed = h.ResumeItemsFailed
h.BytesDone = h.ResumeBytesDone
@@ -195,6 +260,9 @@ func (h *healingTracker) resume() {
// bucketDone should be called when a bucket is done healing.
// Adds the bucket to the list of healed buckets and updates resume numbers.
func (h *healingTracker) bucketDone(bucket string) {
h.mu.Lock()
defer h.mu.Unlock()
h.ResumeItemsHealed = h.ItemsHealed
h.ResumeItemsFailed = h.ItemsFailed
h.ResumeBytesDone = h.BytesDone
@@ -211,6 +279,9 @@ func (h *healingTracker) bucketDone(bucket string) {
// setQueuedBuckets will add buckets, but exclude any that is already in h.HealedBuckets.
// Order is preserved.
func (h *healingTracker) setQueuedBuckets(buckets []BucketInfo) {
h.mu.Lock()
defer h.mu.Unlock()
s := set.CreateStringSet(h.HealedBuckets...)
h.QueuedBuckets = make([]string, 0, len(buckets))
for _, b := range buckets {
@@ -221,6 +292,9 @@ func (h *healingTracker) setQueuedBuckets(buckets []BucketInfo) {
}
func (h *healingTracker) printTo(writer io.Writer) {
h.mu.RLock()
defer h.mu.RUnlock()
b, err := json.MarshalIndent(h, "", " ")
if err != nil {
writer.Write([]byte(err.Error()))
@@ -230,6 +304,9 @@ func (h *healingTracker) printTo(writer io.Writer) {
// toHealingDisk converts the information to madmin.HealingDisk
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
h.mu.RLock()
defer h.mu.RUnlock()
return madmin.HealingDisk{
ID: h.ID,
HealID: h.HealID,
@@ -336,7 +413,7 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
return nil
}
logger.LogIf(ctx, fmt.Errorf("Unable to load healing tracker on '%s': %w, re-initializing..", disk, err))
tracker = newHealingTracker(disk, mustGetUUID())
tracker = initHealingTracker(disk, mustGetUUID())
}
logger.Info(fmt.Sprintf("Healing drive '%s' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint))
+1 -1
View File
@@ -694,7 +694,7 @@ func (r *BatchJobReplicateV1) ReplicateToTarget(ctx context.Context, api ObjectL
Versioned: versioned,
VersionSuspended: versionSuspended,
}
rd, err := api.GetObjectNInfo(ctx, srcBucket, srcObject, nil, http.Header{}, readLock, opts)
rd, err := api.GetObjectNInfo(ctx, srcBucket, srcObject, nil, http.Header{}, opts)
if err != nil {
return err
}
+27 -1
View File
@@ -32,6 +32,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/amztime"
sse "github.com/minio/minio/internal/bucket/encryption"
@@ -70,6 +71,30 @@ func NewLifecycleSys() *LifecycleSys {
return &LifecycleSys{}
}
func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string) madmin.TraceInfo {
return madmin.TraceInfo{
TraceType: madmin.TraceILM,
Time: startTime,
NodeName: globalLocalNodeName,
FuncName: event,
Duration: duration,
Path: pathJoin(oi.Bucket, oi.Name),
Error: "",
Message: getSource(4),
Custom: map[string]string{"version-id": oi.VersionID},
}
}
func (sys *LifecycleSys) trace(oi ObjectInfo) func(event string) {
startTime := time.Now()
return func(event string) {
duration := time.Since(startTime)
if globalTrace.NumSubscribers(madmin.TraceILM) > 0 {
globalTrace.Publish(ilmTrace(startTime, duration, oi, event))
}
}
}
type expiryTask struct {
objInfo ObjectInfo
versionExpiry bool
@@ -360,6 +385,7 @@ const (
// 1. when a restored (via PostRestoreObject API) object expires.
// 2. when a transitioned object expires (based on an ILM rule).
func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *ObjectInfo, lcOpts lifecycle.ObjectOpts, action expireAction) error {
traceFn := globalLifecycleSys.trace(*oi)
var opts ObjectOptions
opts.Versioned = globalBucketVersioningSys.PrefixEnabled(oi.Bucket, oi.Name)
opts.VersionID = lcOpts.VersionID
@@ -385,7 +411,7 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
}
// Send audit for the lifecycle delete operation
auditLogLifecycle(ctx, *oi, ILMExpiry)
defer auditLogLifecycle(ctx, *oi, ILMExpiry, traceFn)
eventName := event.ObjectRemovedDelete
if lcOpts.DeleteMarker {
+1 -1
View File
@@ -82,7 +82,7 @@ func parseBucketQuota(bucket string, data []byte) (quotaCfg *madmin.BucketQuota,
if !quotaCfg.IsValid() {
if quotaCfg.Type == "fifo" {
logger.LogIf(GlobalContext, errors.New("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore. Please clear your quota configs using 'mc admin bucket quota alias/bucket --clear' and use 'mc ilm add' for expiration of objects"))
return quotaCfg, nil
return quotaCfg, fmt.Errorf("invalid quota type 'fifo'")
}
return quotaCfg, fmt.Errorf("Invalid quota config %#v", quotaCfg)
}
+2 -2
View File
@@ -1085,7 +1085,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
VersionID: objInfo.VersionID,
Versioned: versioned,
VersionSuspended: versionSuspended,
@@ -1233,7 +1233,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, readLock, ObjectOptions{
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
VersionID: objInfo.VersionID,
Versioned: versioned,
VersionSuspended: versionSuspended,
+1 -1
View File
@@ -208,7 +208,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
}
clnt, err := sys.getRemoteTargetClient(tgt)
if err != nil {
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket, Err: err}
}
// validate if target credentials are ok
exists, err := clnt.BucketExists(ctx, tgt.TargetBucket)
+1 -6
View File
@@ -30,12 +30,7 @@ import (
var errConfigNotFound = errors.New("config file not found")
func readConfigWithMetadata(ctx context.Context, store objectIO, configFile string, opts ObjectOptions) ([]byte, ObjectInfo, error) {
lockType := readLock
if opts.NoLock {
lockType = noLock // erasureObjects.GetObjectNInfo honors lockType argument but not opts.NoLock.
}
r, err := store.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, lockType, opts)
r, err := store.GetObjectNInfo(ctx, minioMetaBucket, configFile, nil, http.Header{}, opts)
if err != nil {
if isErrObjectNotFound(err) {
return nil, ObjectInfo{}, errConfigNotFound
+6 -3
View File
@@ -957,6 +957,7 @@ func (i *scannerItem) applyLifecycle(ctx context.Context, o ObjectLayer, oi Obje
// applyTierObjSweep removes remote object pending deletion and the free-version
// tracking this information.
func (i *scannerItem) applyTierObjSweep(ctx context.Context, o ObjectLayer, oi ObjectInfo) {
traceFn := globalLifecycleSys.trace(oi)
if !oi.TransitionedObject.FreeVersion {
// nothing to be done
return
@@ -982,7 +983,7 @@ func (i *scannerItem) applyTierObjSweep(ctx context.Context, o ObjectLayer, oi O
InclFreeVersions: true,
})
if err == nil {
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete)
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete, traceFn)
}
if ignoreNotFoundErr(err) != nil {
logger.LogIf(ctx, err)
@@ -1177,6 +1178,7 @@ func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer,
}
func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, applyOnVersion bool) bool {
traceFn := globalLifecycleSys.trace(obj)
opts := ObjectOptions{
Expiration: ExpirationOptions{Expire: true},
}
@@ -1200,7 +1202,7 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
}
// Send audit for the lifecycle delete operation
auditLogLifecycle(ctx, obj, ILMExpiry)
auditLogLifecycle(ctx, obj, ILMExpiry, traceFn)
eventName := event.ObjectRemovedDelete
if obj.DeleteMarker {
@@ -1443,7 +1445,7 @@ const (
ILMTransition = " ilm:transition"
)
func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string) {
func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string, traceFn func(event string)) {
var apiName string
switch event {
case ILMExpiry:
@@ -1460,4 +1462,5 @@ func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string) {
Object: oi.Name,
VersionID: oi.VersionID,
})
traceFn(event)
}
+2 -2
View File
@@ -873,7 +873,7 @@ func (d *dataUsageCache) merge(other dataUsageCache) {
}
type objectIO interface {
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (reader *GetObjectReader, err error)
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (reader *GetObjectReader, err error)
PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
}
@@ -889,7 +889,7 @@ func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string)
// Caches are read+written without locks,
retries := 0
for retries < 5 {
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, noLock, ObjectOptions{NoLock: true})
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, ObjectOptions{NoLock: true})
if err != nil {
switch err.(type) {
case ObjectNotFound, BucketNotFound:
+15 -15
View File
@@ -80,7 +80,7 @@ type CacheStorageInfo struct {
// CacheObjectLayer implements primitives for cache object API layer.
type CacheObjectLayer interface {
// Object operations.
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error)
GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObject(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error)
DeleteObjects(ctx context.Context, bucket string, objects []ObjectToDelete, opts ObjectOptions) ([]DeletedObject, []error)
@@ -117,7 +117,7 @@ type cacheObjects struct {
// Cache stats
cacheStats *CacheStats
InnerGetObjectNInfoFn func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
InnerGetObjectNInfoFn func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error)
InnerGetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
InnerDeleteObjectFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
InnerPutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
@@ -231,16 +231,16 @@ func (c *cacheObjects) incCacheStats(size int64) {
c.cacheStats.incBytesServed(size)
}
func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error) {
if c.isCacheExclude(bucket, object) || c.skipCache() {
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, opts)
}
var cc *cacheControl
var cacheObjSize int64
// fetch diskCache if object is currently cached or nearest available cache drive
dcache, err := c.getCacheToLoc(ctx, bucket, object)
if err != nil {
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, opts)
}
cacheReader, numCacheHits, cacheErr := dcache.Get(ctx, bucket, object, rs, h, opts)
@@ -269,7 +269,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
if cc != nil && cc.noStore {
cacheReader.Close()
c.cacheStats.incMiss()
bReader, err := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
bReader, err := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, opts)
bReader.ObjInfo.CacheLookupStatus = CacheHit
bReader.ObjInfo.CacheStatus = CacheMiss
return bReader, err
@@ -304,7 +304,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
cacheReader.Close()
}
c.cacheStats.incMiss()
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, opts)
}
// skip cache for objects with locks
objRetention := objectlock.GetObjectRetentionMeta(objInfo.UserDefined)
@@ -314,7 +314,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
cacheReader.Close()
}
c.cacheStats.incMiss()
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
return c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, opts)
}
if cacheErr == nil {
// if ETag matches for stale cache entry, serve from cache
@@ -332,7 +332,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
// Reaching here implies cache miss
c.cacheStats.incMiss()
bkReader, bkErr := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, lockType, opts)
bkReader, bkErr := c.InnerGetObjectNInfoFn(ctx, bucket, object, rs, h, opts)
if bkErr != nil {
return bkReader, bkErr
@@ -359,7 +359,7 @@ func (c *cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string
// if range caching is disabled, download entire object.
rs = nil
// fill cache in the background for range GET requests
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, rs, h, lockType, opts)
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, rs, h, opts)
if bErr != nil {
return
}
@@ -713,7 +713,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
if err == nil {
go func() {
// fill cache in the background
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, nil, http.Header{}, readLock, ObjectOptions{})
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, nil, http.Header{}, ObjectOptions{})
if bErr != nil {
return
}
@@ -857,8 +857,8 @@ func newServerCacheObjects(ctx context.Context, config cache.Config) (CacheObjec
InnerGetObjectInfoFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
return newObjectLayerFn().GetObjectInfo(ctx, bucket, object, opts)
},
InnerGetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
return newObjectLayerFn().GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
InnerGetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error) {
return newObjectLayerFn().GetObjectNInfo(ctx, bucket, object, rs, h, opts)
},
InnerDeleteObjectFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
return newObjectLayerFn().DeleteObject(ctx, bucket, object, opts)
@@ -1143,7 +1143,7 @@ func (c *cacheObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObject,
End: startOffset + length,
}
// fill cache in the background
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, srcBucket, srcObject, rs, http.Header{}, readLock, ObjectOptions{})
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, srcBucket, srcObject, rs, http.Header{}, ObjectOptions{})
if bErr != nil {
return
}
@@ -1176,7 +1176,7 @@ func (c *cacheObjects) CompleteMultipartUpload(ctx context.Context, bucket, obje
_, err := dcache.CompleteMultipartUpload(bgContext(ctx), bucket, object, uploadID, uploadedParts, oi, opts)
if err != nil {
// fill cache in the background
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, nil, http.Header{}, readLock, ObjectOptions{})
bReader, bErr := c.InnerGetObjectNInfoFn(GlobalContext, bucket, object, nil, http.Header{}, ObjectOptions{})
if bErr != nil {
return
}
+130
View File
@@ -655,3 +655,133 @@ func TestDisksWithAllParts(t *testing.T) {
}
}
}
func TestCommonParities(t *testing.T) {
// This test uses two FileInfo values that represent the same object but
// have different parities. They occur in equal number of drives, but only
// one has read quorum. commonParity should pick the parity corresponding to
// the FileInfo which has read quorum.
fi1 := FileInfo{
Volume: "mybucket",
Name: "myobject",
VersionID: "",
IsLatest: true,
Deleted: false,
ExpireRestored: false,
DataDir: "4a01d9dd-0c5e-4103-88f8-b307c57d212e",
XLV1: false,
ModTime: time.Date(2023, time.March, 15, 11, 18, 4, 989906961, time.UTC),
Size: 329289, Mode: 0x0, WrittenByVersion: 0x63c77756,
Metadata: map[string]string{
"content-type": "application/octet-stream", "etag": "f205307ef9f50594c4b86d9c246bee86", "x-minio-internal-erasure-upgraded": "5->6", "x-minio-internal-inline-data": "true",
},
Parts: []ObjectPartInfo{
{
ETag: "",
Number: 1,
Size: 329289,
ActualSize: 329289,
ModTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
Index: []uint8(nil),
Checksums: map[string]string(nil),
},
},
Erasure: ErasureInfo{
Algorithm: "ReedSolomon",
DataBlocks: 6,
ParityBlocks: 6,
BlockSize: 1048576,
Index: 1,
Distribution: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
Checksums: []ChecksumInfo{{PartNumber: 1, Algorithm: 0x3, Hash: []uint8{}}},
},
NumVersions: 1,
Idx: 0,
}
fi2 := FileInfo{
Volume: "mybucket",
Name: "myobject",
VersionID: "",
IsLatest: true,
Deleted: false,
DataDir: "6f5c106d-9d28-4c85-a7f4-eac56225876b",
ModTime: time.Date(2023, time.March, 15, 19, 57, 30, 492530160, time.UTC),
Size: 329289,
Mode: 0x0,
WrittenByVersion: 0x63c77756,
Metadata: map[string]string{"content-type": "application/octet-stream", "etag": "f205307ef9f50594c4b86d9c246bee86", "x-minio-internal-inline-data": "true"},
Parts: []ObjectPartInfo{
{
ETag: "",
Number: 1,
Size: 329289,
ActualSize: 329289,
ModTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
Index: []uint8(nil),
Checksums: map[string]string(nil),
},
},
Erasure: ErasureInfo{
Algorithm: "ReedSolomon",
DataBlocks: 7,
ParityBlocks: 5,
BlockSize: 1048576,
Index: 2,
Distribution: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
Checksums: []ChecksumInfo{
{PartNumber: 1, Algorithm: 0x3, Hash: []uint8{}},
},
},
NumVersions: 1,
Idx: 0,
}
fiDel := FileInfo{
Volume: "mybucket",
Name: "myobject",
VersionID: "",
IsLatest: true,
Deleted: true,
ModTime: time.Date(2023, time.March, 15, 19, 57, 30, 492530160, time.UTC),
Mode: 0x0,
WrittenByVersion: 0x63c77756,
NumVersions: 1,
Idx: 0,
}
tests := []struct {
fi1, fi2 FileInfo
}{
{
fi1: fi1,
fi2: fi2,
},
{
fi1: fi1,
fi2: fiDel,
},
}
for idx, test := range tests {
var metaArr []FileInfo
for i := 0; i < 12; i++ {
fi := test.fi1
if i%2 == 0 {
fi = test.fi2
}
metaArr = append(metaArr, fi)
}
parities := listObjectParities(metaArr, make([]error, len(metaArr)))
parity := commonParity(parities, 5)
var match int
for _, fi := range metaArr {
if fi.Erasure.ParityBlocks == parity {
match++
}
}
if match < len(metaArr)-parity {
t.Fatalf("Test %d: Expected %d drives with parity=%d, but got %d", idx, len(metaArr)-parity, parity, match)
}
}
}
+21
View File
@@ -294,6 +294,26 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta File
return false
}
const xMinIOHealing = ReservedMetadataPrefix + "healing"
// SetHealing marks object (version) as being healed.
// Note: this is to be used only from healObject
func (fi *FileInfo) SetHealing() {
if fi.Metadata == nil {
fi.Metadata = make(map[string]string)
}
fi.Metadata[xMinIOHealing] = "true"
}
// Healing returns true if object is being healed (i.e fi is being passed down
// from healObject)
func (fi FileInfo) Healing() bool {
if _, ok := fi.Metadata[xMinIOHealing]; ok {
return true
}
return false
}
// Heals an object by re-writing corrupt/missing erasure blocks.
func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
dryRun := opts.DryRun
@@ -664,6 +684,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
partsMetadata[i].Erasure.Index = i + 1
// Attempt a rename now from healed data to final location.
partsMetadata[i].SetHealing()
if _, err = disk.RenameData(ctx, minioMetaTmpBucket, tmpID, partsMetadata[i], bucket, object); err != nil {
logger.LogIf(ctx, err)
return result, err
+16 -2
View File
@@ -33,6 +33,7 @@ import (
"github.com/dustin/go-humanize"
uuid2 "github.com/google/uuid"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/config/storageclass"
)
// Tests isObjectDangling function
@@ -563,6 +564,17 @@ func TestHealingDanglingObject(t *testing.T) {
resetGlobalHealState()
defer resetGlobalHealState()
// Set globalStoragClass.STANDARD to EC:4 for this test
saveSC := globalStorageClass
defer func() {
globalStorageClass = saveSC
}()
globalStorageClass = storageclass.Config{
Standard: storageclass.StorageClass{
Parity: 4,
},
}
nDisks := 16
fsDirs, err := getRandomDisks(nDisks)
if err != nil {
@@ -577,6 +589,8 @@ func TestHealingDanglingObject(t *testing.T) {
t.Fatal(err)
}
setObjectLayer(objLayer)
bucket := getRandomBucketName()
object := getRandomObjectName()
data := bytes.Repeat([]byte("a"), 128*1024)
@@ -1620,7 +1634,7 @@ func TestHealLastDataShard(t *testing.T) {
t.Fatal(err)
}
firstGr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, noLock, ObjectOptions{})
firstGr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, ObjectOptions{NoLock: true})
if err != nil {
t.Fatal(err)
}
@@ -1650,7 +1664,7 @@ func TestHealLastDataShard(t *testing.T) {
t.Fatal(err)
}
secondGr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, noLock, ObjectOptions{})
secondGr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, ObjectOptions{NoLock: true})
if err != nil {
t.Fatal(err)
}
+18 -5
View File
@@ -234,6 +234,7 @@ func (fi FileInfo) ToObjectInfo(bucket, object string, versioned bool) ObjectInf
}
}
objInfo.Checksum = fi.Checksum
objInfo.Inlined = fi.InlineData()
// Success.
return objInfo
}
@@ -444,20 +445,33 @@ func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, bucket, prefix
return evalDisks(disks, mErrs), err
}
func commonParity(parities []int) int {
func commonParity(parities []int, defaultParityCount int) int {
N := len(parities)
occMap := make(map[int]int)
for _, p := range parities {
occMap[p]++
}
var maxOcc, commonParity int
for parity, occ := range occMap {
if parity == -1 {
// Ignore non defined parity
continue
}
if occ >= maxOcc {
readQuorum := N - parity
if defaultParityCount > 0 && parity == 0 {
// In this case, parity == 0 implies that this object version is a
// delete marker
readQuorum = N/2 + 1
}
if occ < readQuorum {
// Ignore this parity since we don't have enough shards for read quorum
continue
}
if occ > maxOcc {
maxOcc = occ
commonParity = parity
}
@@ -510,8 +524,7 @@ func objectQuorumFromMeta(ctx context.Context, partsMetaData []FileInfo, errs []
}
parities := listObjectParities(partsMetaData, errs)
parityBlocks := commonParity(parities)
parityBlocks := commonParity(parities, defaultParityCount)
if parityBlocks < 0 {
return -1, -1, errErasureReadQuorum
}
+3 -2
View File
@@ -1137,8 +1137,9 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
}
}
if checksumType.IsSet() {
checksumType |= hash.ChecksumMultipart
cs := hash.NewChecksumFromData(checksumType, checksumCombined)
fi.Checksum = cs.AppendTo(nil)
fi.Checksum = cs.AppendTo(nil, len(fi.Parts))
if opts.EncryptFn != nil {
fi.Checksum = opts.EncryptFn("object-checksum", fi.Checksum)
}
@@ -1226,7 +1227,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
if disk != nil && disk.IsOnline() {
continue
}
er.addPartial(bucket, object, fi.VersionID, fi.Size)
er.addPartial(bucket, object, fi.VersionID)
break
}
+34 -25
View File
@@ -29,6 +29,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/klauspost/readahead"
"github.com/minio/madmin-go/v2"
@@ -183,7 +184,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
// GetObjectNInfo - returns object info and an object
// Read(Closer). When err != nil, the returned reader is always nil.
func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error) {
auditObjectErasureSet(ctx, object, &er)
// This is a special call attempted first to check for SOS-API calls.
@@ -203,25 +204,26 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
}()
// Acquire lock
if lockType != noLock {
if !opts.NoLock {
lock := er.NewNSLock(bucket, object)
switch lockType {
case writeLock:
lkctx, err := lock.GetLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.Unlock(lkctx) }
case readLock:
lkctx, err := lock.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.RUnlock(lkctx) }
}
// Release lock when the metadata is verified, and reader
// is ready to be read.
//
// This is possible to be lock free because
// - xl.meta for inlined objects has already read the data
// into memory, any mutation on xl.meta subsequently is
// inconsequential to the overall read operation.
// - xl.meta metadata is still verified for quorum under lock()
// however writing the response doesn't need to serialize
// concurrent writers
unlockOnDefer = true
nsUnlocker = func() { lock.RUnlock(lkctx) }
}
fi, metaArr, onlineDisks, err := er.getObjectFileInfo(ctx, bucket, object, opts, true)
@@ -265,6 +267,7 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
ObjInfo: objInfo,
}, toObjectErr(errMethodNotAllowed, bucket, object)
}
if objInfo.IsRemote() {
gr, err := getTransitionedObjectReader(ctx, bucket, object, rs, h, objInfo, opts)
if err != nil {
@@ -278,7 +281,10 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
if err != nil {
return nil, err
}
unlockOnDefer = false
if unlockOnDefer {
unlockOnDefer = fi.InlineData()
}
pr, pw := xioutil.WaitPipe()
go func() {
@@ -291,7 +297,11 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
pr.CloseWithError(nil)
}
if !unlockOnDefer {
return fn(pr, h, pipeCloser, nsUnlocker)
}
return fn(pr, h, pipeCloser)
}
func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, fi FileInfo, metaArr []FileInfo, onlineDisks []StorageAPI) error {
@@ -1081,7 +1091,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
}
fi.DataDir = mustGetUUID()
fi.Checksum = opts.WantChecksum.AppendTo(nil)
fi.Checksum = opts.WantChecksum.AppendTo(nil, 0)
if opts.EncryptFn != nil {
fi.Checksum = opts.EncryptFn("object-checksum", fi.Checksum)
}
@@ -1290,7 +1300,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
continue
}
er.addPartial(bucket, object, fi.VersionID, fi.Size)
er.addPartial(bucket, object, fi.VersionID)
break
}
@@ -1482,7 +1492,7 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
if errs[i] != nil && !isErrVersionNotFound(errs[i]) && !isErrObjectNotFound(errs[i]) {
// all other direct versionId references we should
// ensure no dangling file is left over.
er.addPartial(bucket, dobj.ObjectName, dobj.VersionID, -1)
er.addPartial(bucket, dobj.ObjectName, dobj.VersionID)
continue
}
@@ -1495,7 +1505,7 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
// all other direct versionId references we should
// ensure no dangling file is left over.
er.addPartial(bucket, dobj.ObjectName, dobj.VersionID, -1)
er.addPartial(bucket, dobj.ObjectName, dobj.VersionID)
break
}
}
@@ -1658,7 +1668,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
if disk != nil && disk.IsOnline() {
continue
}
er.addPartial(bucket, object, opts.VersionID, -1)
er.addPartial(bucket, object, opts.VersionID)
break
}
}()
@@ -1715,14 +1725,12 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
// Send the successful but partial upload/delete, however ignore
// if the channel is blocked by other items.
func (er erasureObjects) addPartial(bucket, object, versionID string, size int64) {
func (er erasureObjects) addPartial(bucket, object, versionID string) {
globalMRFState.addPartialOp(partialOperation{
bucket: bucket,
object: object,
versionID: versionID,
size: size,
setIndex: er.setIndex,
poolIndex: er.poolIndex,
queued: time.Now(),
})
}
@@ -1953,6 +1961,7 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
return toObjectErr(err, bucket, object)
}
}
traceFn := globalLifecycleSys.trace(fi.ToObjectInfo(bucket, object, opts.Versioned || opts.VersionSuspended))
destObj, err := genTransitionObjName(bucket)
if err != nil {
@@ -1988,7 +1997,7 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
if disk != nil && disk.IsOnline() {
continue
}
er.addPartial(bucket, object, opts.VersionID, -1)
er.addPartial(bucket, object, opts.VersionID)
break
}
@@ -2000,7 +2009,7 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
UserAgent: "Internal: [ILM-Transition]",
Host: globalLocalNodeName,
})
auditLogLifecycle(ctx, objInfo, ILMTransition)
auditLogLifecycle(ctx, objInfo, ILMTransition, traceFn)
return err
}
+6 -6
View File
@@ -532,7 +532,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
}
}
gr, err := xl.GetObjectNInfo(ctx, bucket, object, nil, nil, readLock, opts)
gr, err := xl.GetObjectNInfo(ctx, bucket, object, nil, nil, opts)
if err != nil {
if err != toObjectErr(errErasureReadQuorum, bucket, object) {
t.Errorf("Expected GetObject to fail with %v, but failed with %v", toObjectErr(errErasureReadQuorum, bucket, object), err)
@@ -577,7 +577,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
}
z.serverPools[0].erasureDisksMu.Unlock()
// Fetch object from store.
gr, err := xl.GetObjectNInfo(ctx, bucket, object, nil, nil, readLock, opts)
gr, err := xl.GetObjectNInfo(ctx, bucket, object, nil, nil, opts)
if err != nil {
if err != toObjectErr(errErasureReadQuorum, bucket, object) {
t.Errorf("Expected GetObject to fail with %v, but failed with %v", toObjectErr(errErasureReadQuorum, bucket, object), err)
@@ -831,7 +831,7 @@ func TestPutObjectSmallInlineData(t *testing.T) {
if err != nil {
t.Fatal(err)
}
gr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, readLock, ObjectOptions{})
gr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, ObjectOptions{})
if err != nil {
t.Fatalf("Expected GetObject to succeed, but failed with %v", err)
}
@@ -855,7 +855,7 @@ func TestPutObjectSmallInlineData(t *testing.T) {
if err != nil {
t.Fatal(err)
}
gr, err = obj.GetObjectNInfo(ctx, bucket, object, nil, nil, readLock, ObjectOptions{})
gr, err = obj.GetObjectNInfo(ctx, bucket, object, nil, nil, ObjectOptions{})
if err != nil {
t.Fatalf("Expected GetObject to succeed, but failed with %v", err)
}
@@ -1099,7 +1099,7 @@ func TestGetObjectInlineNotInline(t *testing.T) {
}
// Try to read the object and check its md5sum
gr, err := objLayer.GetObjectNInfo(ctx, "testbucket", "file", nil, nil, readLock, ObjectOptions{})
gr, err := objLayer.GetObjectNInfo(ctx, "testbucket", "file", nil, nil, ObjectOptions{})
if err != nil {
t.Fatalf("Expected GetObject to succeed, but failed with %v", err)
}
@@ -1193,7 +1193,7 @@ func TestGetObjectWithOutdatedDisks(t *testing.T) {
sets.erasureDisksMu.Lock()
xl.getDisks = func() []StorageAPI { return origErasureDisks }
sets.erasureDisksMu.Unlock()
gr, err := z.GetObjectNInfo(ctx, testCase.bucket, testCase.object, nil, nil, readLock, ObjectOptions{VersionID: got.VersionID})
gr, err := z.GetObjectNInfo(ctx, testCase.bucket, testCase.object, nil, nil, ObjectOptions{VersionID: got.VersionID})
if err != nil {
t.Fatalf("Expected GetObject to succeed, but failed with %v", err)
}
+1 -1
View File
@@ -838,10 +838,10 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
encodeDirObject(version.Name),
nil,
http.Header{},
noLock, // all mutations are blocked reads are safe without locks.
ObjectOptions{
VersionID: version.VersionID,
NoDecryption: true,
NoLock: true,
})
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
// object deleted by the application, nothing to do here we move on.
+1 -1
View File
@@ -550,10 +550,10 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
encodeDirObject(version.Name),
nil,
http.Header{},
noLock, // all mutations are blocked reads are safe without locks.
ObjectOptions{
VersionID: version.VersionID,
NoDecryption: true,
NoLock: true,
})
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
// object deleted by the application, nothing to do here we move on.
+27 -27
View File
@@ -432,30 +432,37 @@ func (z *erasureServerPools) getPoolInfoExistingWithOpts(ctx context.Context, bu
for _, pinfo := range poolObjInfos {
// skip all objects from suspended pools if asked by the
// caller.
if z.IsSuspended(pinfo.Index) && opts.SkipDecommissioned {
if opts.SkipDecommissioned && z.IsSuspended(pinfo.Index) {
continue
}
// Skip object if it's from pools participating in a rebalance operation.
if opts.SkipRebalancing && z.IsPoolRebalancing(pinfo.Index) {
continue
}
if pinfo.Err != nil && !isErrObjectNotFound(pinfo.Err) {
return pinfo, pinfo.Err
if pinfo.Err == nil {
// found a pool
return pinfo, nil
}
if isErrReadQuorum(pinfo.Err) {
// read quorum is returned when the object is visibly
// present but its unreadable, we simply ask the writes to
// schedule to this pool instead. If there is no quorum
// it will fail anyways, however if there is quorum available
// with enough disks online but sufficiently inconsistent to
// break parity threshold, allow them to be overwritten
// or allow new versions to be added.
return pinfo, nil
}
defPool = pinfo
if isErrObjectNotFound(pinfo.Err) {
if !isErrObjectNotFound(pinfo.Err) {
return pinfo, pinfo.Err
}
// No object exists or its a delete marker,
// check objInfo to confirm.
if pinfo.ObjInfo.DeleteMarker && pinfo.ObjInfo.Name != "" {
return pinfo, nil
}
// objInfo is not valid, truly the object doesn't
// exist proceed to next pool.
continue
}
return pinfo, nil
}
if opts.ReplicationRequest && opts.DeleteMarker && defPool.Index >= 0 {
// If the request is a delete marker replication request, return a default pool
@@ -766,7 +773,7 @@ func (z *erasureServerPools) MakeBucket(ctx context.Context, bucket string, opts
return nil
}
func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error) {
if err = checkGetObjArgs(ctx, bucket, object); err != nil {
return nil, err
}
@@ -774,7 +781,7 @@ func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object
object = encodeDirObject(object)
if z.SinglePool() {
return z.serverPools[0].GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
return z.serverPools[0].GetObjectNInfo(ctx, bucket, object, rs, h, opts)
}
var unlockOnDefer bool
@@ -786,24 +793,14 @@ func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object
}()
// Acquire lock
if lockType != noLock {
if !opts.NoLock {
lock := z.NewNSLock(bucket, object)
switch lockType {
case writeLock:
lkctx, err := lock.GetLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.Unlock(lkctx) }
case readLock:
lkctx, err := lock.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.RUnlock(lkctx) }
}
unlockOnDefer = true
}
@@ -831,14 +828,17 @@ func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object
return nil, PreConditionFailed{}
}
lockType = noLock // do not take locks at lower levels for GetObjectNInfo()
gr, err = z.serverPools[zIdx].GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
opts.NoLock = true
gr, err = z.serverPools[zIdx].GetObjectNInfo(ctx, bucket, object, rs, h, opts)
if err != nil {
return nil, err
}
if unlockOnDefer {
unlockOnDefer = false
unlockOnDefer = gr.ObjInfo.Inlined
}
if !unlockOnDefer {
return gr.WithCleanupFuncs(nsUnlocker), nil
}
return gr, nil
+2 -15
View File
@@ -190,7 +190,6 @@ func (s *erasureSets) connectDisks() {
var wg sync.WaitGroup
diskMap := s.getDiskMap()
setsJustConnected := make([]bool, s.setCount)
for _, endpoint := range s.endpoints.Endpoints {
cdisk := diskMap[endpoint]
if cdisk != nil && cdisk.IsOnline() {
@@ -203,8 +202,6 @@ func (s *erasureSets) connectDisks() {
// putting it back into the s.erasureDisks by re-placing the disk again.
_, setIndex, _ := cdisk.GetDiskLoc()
if setIndex != -1 {
// Recently disconnected disks must go to MRF
setsJustConnected[setIndex] = cdisk.LastConn().After(s.lastConnectDisksOpTime)
continue
}
}
@@ -260,21 +257,11 @@ func (s *erasureSets) connectDisks() {
s.erasureDisks[setIndex][diskIndex] = disk
}
disk.SetDiskLoc(s.poolIndex, setIndex, diskIndex)
setsJustConnected[setIndex] = true // disk just went online we treat it is as MRF event
s.erasureDisksMu.Unlock()
}(endpoint)
}
wg.Wait()
go func() {
for setIndex, justConnected := range setsJustConnected {
if !justConnected {
continue
}
globalMRFState.newSetReconnected(s.poolIndex, setIndex)
}
}()
}
// monitorAndConnectEndpoints this is a monitoring loop to keep track of disconnected
@@ -756,9 +743,9 @@ func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBucke
// --- Object Operations ---
// GetObjectNInfo - returns object info and locked object ReadCloser
func (s *erasureSets) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
func (s *erasureSets) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error) {
set := s.getHashedSet(object)
return set.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
return set.GetObjectNInfo(ctx, bucket, object, rs, h, opts)
}
// PutObject - writes an object to hashedSet based on the object name.
+1
View File
@@ -207,6 +207,7 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.
Scanning: info.Scanning,
State: diskErrToDriveState(err),
FreeInodes: info.FreeInodes,
UsedInodes: info.UsedInodes,
}
di.PoolIndex, di.SetIndex, di.DiskIndex = disks[index].GetDiskLoc()
if info.Healing {
+1 -1
View File
@@ -380,7 +380,7 @@ func saveFormatErasure(disk StorageAPI, format *formatErasureV3, healID string)
disk.SetDiskID(diskID)
if healID != "" {
ctx := context.Background()
ht := newHealingTracker(disk, healID)
ht := initHealingTracker(disk, healID)
return ht.save(ctx)
}
return nil
+505
View File
@@ -0,0 +1,505 @@
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"bytes"
"context"
"crypto/subtle"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/logger"
ftp "goftp.io/server/v2"
)
var _ ftp.Driver = &ftpDriver{}
// ftpDriver implements ftpDriver to store files in minio
type ftpDriver struct {
endpoint string
}
// NewFTPDriver implements ftp.Driver interface
func NewFTPDriver() ftp.Driver {
return &ftpDriver{endpoint: fmt.Sprintf("127.0.0.1:%s", globalMinioPort)}
}
func buildMinioPath(p string) string {
return strings.TrimPrefix(p, SlashSeparator)
}
func buildMinioDir(p string) string {
v := buildMinioPath(p)
if !strings.HasSuffix(v, SlashSeparator) {
return v + SlashSeparator
}
return v
}
type minioFileInfo struct {
p string
info minio.ObjectInfo
isDir bool
}
func (m *minioFileInfo) Name() string {
return m.p
}
func (m *minioFileInfo) Size() int64 {
return m.info.Size
}
func (m *minioFileInfo) Mode() os.FileMode {
if m.isDir {
return os.ModeDir
}
return os.ModePerm
}
func (m *minioFileInfo) ModTime() time.Time {
return m.info.LastModified
}
func (m *minioFileInfo) IsDir() bool {
return m.isDir
}
func (m *minioFileInfo) Sys() interface{} {
return nil
}
//msgp:ignore ftpMetrics
type ftpMetrics struct{}
var globalFtpMetrics ftpMetrics
func ftpTrace(s *ftp.Context, startTime time.Time, source, path string, err error) madmin.TraceInfo {
var errStr string
if err != nil {
errStr = err.Error()
}
return madmin.TraceInfo{
TraceType: madmin.TraceFTP,
Time: startTime,
NodeName: globalLocalNodeName,
FuncName: fmt.Sprintf("ftp USER=%s COMMAND=%s PARAM=%s ISLOGIN=%t, Source=%s", s.Sess.LoginUser(), s.Cmd, s.Param, s.Sess.IsLogin(), source),
Duration: time.Since(startTime),
Path: path,
Error: errStr,
}
}
func (m *ftpMetrics) log(s *ftp.Context, paths ...string) func(err error) {
startTime := time.Now()
source := getSource(2)
return func(err error) {
globalTrace.Publish(ftpTrace(s, startTime, source, strings.Join(paths, " "), err))
}
}
// Stat implements ftpDriver
func (driver *ftpDriver) Stat(ctx *ftp.Context, path string) (fi os.FileInfo, err error) {
stopFn := globalFtpMetrics.log(ctx, path)
defer stopFn(err)
if path == SlashSeparator {
return &minioFileInfo{
p: SlashSeparator,
isDir: true,
}, nil
}
bucket, object := path2BucketObject(path)
if bucket == "" {
return nil, errors.New("bucket name cannot be empty")
}
clnt, err := driver.getMinIOClient(ctx)
if err != nil {
return nil, err
}
if object == "" {
ok, err := clnt.BucketExists(context.Background(), bucket)
if err != nil {
return nil, err
}
if !ok {
return nil, os.ErrNotExist
}
return &minioFileInfo{
p: pathClean(bucket),
info: minio.ObjectInfo{Key: bucket},
isDir: true,
}, nil
}
objInfo, err := clnt.StatObject(context.Background(), bucket, object, minio.StatObjectOptions{})
if err != nil {
if minio.ToErrorResponse(err).Code == "NoSuchKey" {
// dummy return to satisfy LIST (stat -> list) behavior.
return &minioFileInfo{
p: pathClean(object),
info: minio.ObjectInfo{Key: object},
isDir: true,
}, nil
}
return nil, err
}
isDir := strings.HasSuffix(objInfo.Key, SlashSeparator)
return &minioFileInfo{
p: pathClean(object),
info: objInfo,
isDir: isDir,
}, nil
}
// ListDir implements ftpDriver
func (driver *ftpDriver) ListDir(ctx *ftp.Context, path string, callback func(os.FileInfo) error) (err error) {
stopFn := globalFtpMetrics.log(ctx, path)
defer stopFn(err)
clnt, err := driver.getMinIOClient(ctx)
if err != nil {
return err
}
cctx, cancel := context.WithCancel(context.Background())
defer cancel()
bucket, prefix := path2BucketObject(path)
if bucket == "" {
buckets, err := clnt.ListBuckets(cctx)
if err != nil {
return err
}
for _, bucket := range buckets {
info := minioFileInfo{
p: pathClean(bucket.Name),
info: minio.ObjectInfo{Key: retainSlash(bucket.Name), LastModified: bucket.CreationDate},
isDir: true,
}
if err := callback(&info); err != nil {
return err
}
}
return nil
}
prefix = retainSlash(prefix)
for object := range clnt.ListObjects(cctx, bucket, minio.ListObjectsOptions{
Prefix: prefix,
Recursive: false,
}) {
if object.Err != nil {
return object.Err
}
if object.Key == prefix {
continue
}
isDir := strings.HasSuffix(object.Key, SlashSeparator)
info := minioFileInfo{
p: pathClean(strings.TrimPrefix(object.Key, prefix)),
info: object,
isDir: isDir,
}
if err := callback(&info); err != nil {
return err
}
}
return nil
}
func (driver *ftpDriver) CheckPasswd(c *ftp.Context, username, password string) (ok bool, err error) {
stopFn := globalFtpMetrics.log(c, username)
defer stopFn(err)
if globalIAMSys.LDAPConfig.Enabled() {
ldapUserDN, groupDistNames, err := globalIAMSys.LDAPConfig.Bind(username, password)
if err != nil {
return false, err
}
ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, false, groupDistNames...)
if len(ldapPolicies) == 0 {
// no policy associated reject it.
return false, nil
}
return true, nil
}
ui, ok := globalIAMSys.GetUser(context.Background(), username)
if !ok {
return false, nil
}
return subtle.ConstantTimeCompare([]byte(ui.Credentials.SecretKey), []byte(password)) == 1, nil
}
func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error) {
ui, ok := globalIAMSys.GetUser(context.Background(), ctx.Sess.LoginUser())
if !ok && !globalIAMSys.LDAPConfig.Enabled() {
return nil, errNoSuchUser
}
if !ok && globalIAMSys.LDAPConfig.Enabled() {
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(ctx.Sess.LoginUser())
if err != nil {
return nil, err
}
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, false, targetGroups...)
if len(ldapPolicies) == 0 {
return nil, errAuthentication
}
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
if err != nil {
return nil, err
}
claims := make(map[string]interface{})
claims[expClaim] = UTCNow().Add(expiryDur).Unix()
claims[ldapUser] = targetUser
claims[ldapUserN] = ctx.Sess.LoginUser()
cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey)
if err != nil {
return nil, err
}
// Set the parent of the temporary access key, this is useful
// in obtaining service accounts by this cred.
cred.ParentUser = targetUser
// Set this value to LDAP groups, LDAP user can be part
// of large number of groups
cred.Groups = targetGroups
// Set the newly generated credentials, policyName is empty on purpose
// LDAP policies are applied automatically using their ldapUser, ldapGroups
// mapping.
updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "")
if err != nil {
return nil, err
}
// Call hook for site replication.
logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{
Type: madmin.SRIAMItemSTSAcc,
STSCredential: &madmin.SRSTSCredential{
AccessKey: cred.AccessKey,
SecretKey: cred.SecretKey,
SessionToken: cred.SessionToken,
ParentUser: cred.ParentUser,
},
UpdatedAt: updatedAt,
}))
return minio.New(driver.endpoint, &minio.Options{
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
Secure: globalIsTLS,
Transport: globalRemoteTargetTransport,
})
}
// ok == true - at this point
if ui.Credentials.IsTemp() {
// Temporary credentials are not allowed.
return nil, errAuthentication
}
return minio.New(driver.endpoint, &minio.Options{
Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""),
Secure: globalIsTLS,
Transport: globalRemoteTargetTransport,
})
}
// DeleteDir implements ftpDriver
func (driver *ftpDriver) DeleteDir(ctx *ftp.Context, path string) (err error) {
stopFn := globalFtpMetrics.log(ctx, path)
defer stopFn(err)
bucket, prefix := path2BucketObject(path)
if bucket == "" {
return errors.New("deleting all buckets not allowed")
}
clnt, err := driver.getMinIOClient(ctx)
if err != nil {
return err
}
cctx, cancel := context.WithCancel(context.Background())
defer cancel()
objectsCh := make(chan minio.ObjectInfo)
// Send object names that are needed to be removed to objectsCh
go func() {
defer close(objectsCh)
opts := minio.ListObjectsOptions{Prefix: prefix, Recursive: true}
for object := range clnt.ListObjects(cctx, bucket, opts) {
if object.Err != nil {
return
}
objectsCh <- object
}
}()
// Call RemoveObjects API
for err := range clnt.RemoveObjects(context.Background(), bucket, objectsCh, minio.RemoveObjectsOptions{}) {
if err.Err != nil {
return err.Err
}
}
return nil
}
// DeleteFile implements ftpDriver
func (driver *ftpDriver) DeleteFile(ctx *ftp.Context, path string) (err error) {
stopFn := globalFtpMetrics.log(ctx, path)
defer stopFn(err)
bucket, object := path2BucketObject(path)
if bucket == "" {
return errors.New("bucket name cannot be empty")
}
clnt, err := driver.getMinIOClient(ctx)
if err != nil {
return err
}
return clnt.RemoveObject(context.Background(), bucket, object, minio.RemoveObjectOptions{})
}
// Rename implements ftpDriver
func (driver *ftpDriver) Rename(ctx *ftp.Context, fromPath string, toPath string) (err error) {
stopFn := globalFtpMetrics.log(ctx, fromPath, toPath)
defer stopFn(err)
return NotImplemented{}
}
// MakeDir implements ftpDriver
func (driver *ftpDriver) MakeDir(ctx *ftp.Context, path string) (err error) {
stopFn := globalFtpMetrics.log(ctx, path)
defer stopFn(err)
bucket, prefix := path2BucketObject(path)
if bucket == "" {
return errors.New("bucket name cannot be empty")
}
clnt, err := driver.getMinIOClient(ctx)
if err != nil {
return err
}
dirPath := buildMinioDir(prefix)
_, err = clnt.PutObject(context.Background(), bucket, dirPath, bytes.NewReader([]byte("")), 0,
// Always send Content-MD5 to succeed with bucket with
// locking enabled. There is no performance hit since
// this is always an empty object
minio.PutObjectOptions{SendContentMd5: true},
)
return err
}
// GetFile implements ftpDriver
func (driver *ftpDriver) GetFile(ctx *ftp.Context, path string, offset int64) (n int64, rc io.ReadCloser, err error) {
stopFn := globalFtpMetrics.log(ctx, path)
defer stopFn(err)
bucket, object := path2BucketObject(path)
if bucket == "" {
return 0, nil, errors.New("bucket name cannot be empty")
}
clnt, err := driver.getMinIOClient(ctx)
if err != nil {
return 0, nil, err
}
opts := minio.GetObjectOptions{}
obj, err := clnt.GetObject(context.Background(), bucket, object, opts)
if err != nil {
return 0, nil, err
}
defer func() {
if err != nil && obj != nil {
obj.Close()
}
}()
_, err = obj.Seek(offset, io.SeekStart)
if err != nil {
return 0, nil, err
}
info, err := obj.Stat()
if err != nil {
return 0, nil, err
}
return info.Size - offset, obj, nil
}
// PutFile implements ftpDriver
func (driver *ftpDriver) PutFile(ctx *ftp.Context, path string, data io.Reader, offset int64) (n int64, err error) {
stopFn := globalFtpMetrics.log(ctx, path)
defer stopFn(err)
bucket, object := path2BucketObject(path)
if bucket == "" {
return 0, errors.New("bucket name cannot be empty")
}
if offset != -1 {
// FTP - APPEND not implemented
return 0, NotImplemented{}
}
clnt, err := driver.getMinIOClient(ctx)
if err != nil {
return 0, err
}
info, err := clnt.PutObject(context.Background(), bucket, object, data, -1, minio.PutObjectOptions{
ContentType: "application/octet-stream",
SendContentMd5: true,
})
return info.Size, err
}
+300
View File
@@ -0,0 +1,300 @@
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"context"
"crypto/subtle"
"fmt"
"io"
"net"
"strconv"
"strings"
"github.com/minio/cli"
"github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger"
"github.com/pkg/sftp"
ftp "goftp.io/server/v2"
"golang.org/x/crypto/ssh"
)
// minioLogger use an instance of this to log in a standard format
type minioLogger struct{}
// Print implement Logger
func (log *minioLogger) Print(sessionID string, message interface{}) {
if serverDebugLog {
logger.Info("%s %s", sessionID, message)
}
}
// Printf implement Logger
func (log *minioLogger) Printf(sessionID string, format string, v ...interface{}) {
if serverDebugLog {
if sessionID != "" {
logger.Info("%s %s", sessionID, fmt.Sprintf(format, v...))
} else {
logger.Info(format, v...)
}
}
}
// PrintCommand impelment Logger
func (log *minioLogger) PrintCommand(sessionID string, command string, params string) {
if serverDebugLog {
if command == "PASS" {
logger.Info("%s > PASS ****", sessionID)
} else {
logger.Info("%s > %s %s", sessionID, command, params)
}
}
}
// PrintResponse impelment Logger
func (log *minioLogger) PrintResponse(sessionID string, code int, message string) {
if serverDebugLog {
logger.Info("%s < %d %s", sessionID, code, message)
}
}
func startSFTPServer(c *cli.Context) {
args := c.StringSlice("sftp")
var (
port int
publicIP string
sshPrivateKey string
)
var err error
for _, arg := range args {
tokens := strings.SplitN(arg, "=", 2)
if len(tokens) != 2 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s", arg), "unable to start SFTP server")
}
switch tokens[0] {
case "address":
host, portStr, err := net.SplitHostPort(tokens[1])
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s (%v)", arg, err), "unable to start SFTP server")
}
port, err = strconv.Atoi(portStr)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s (%v)", arg, err), "unable to start SFTP server")
}
if port < 1 || port > 65535 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --sftp=%s, (port number must be between 1 to 65535)", arg), "unable to start SFTP server")
}
publicIP = host
case "ssh-private-key":
sshPrivateKey = tokens[1]
}
}
if port == 0 {
port = 8022 // Default SFTP port, since no port was given.
}
if sshPrivateKey == "" {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is mandatory for --sftp='ssh-private-key=path/to/id_ecdsa'"), "unable to start SFTP server")
}
privateBytes, err := ioutil.ReadFile(sshPrivateKey)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is not accessible: %v", err), "unable to start SFTP server")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, private key file is not parseable: %v", err), "unable to start SFTP server")
}
// An SSH server is represented by a ServerConfig, which holds
// certificate details and handles authentication of ServerConns.
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
ui, ok := globalIAMSys.GetUser(context.Background(), c.User())
if !ok {
return nil, errNoSuchUser
}
if subtle.ConstantTimeCompare([]byte(ui.Credentials.SecretKey), pass) == 1 {
return &ssh.Permissions{
CriticalOptions: map[string]string{
"accessKey": c.User(),
},
Extensions: make(map[string]string),
}, nil
}
return nil, errAuthentication
},
}
config.AddHostKey(private)
// Once a ServerConfig has been configured, connections can be accepted.
listener, err := net.Listen("tcp", net.JoinHostPort(publicIP, strconv.Itoa(port)))
if err != nil {
logger.Fatal(err, "unable to start listening on --sftp='port=%d'", port)
}
logger.Info(fmt.Sprintf("MinIO SFTP Server listening on %s", net.JoinHostPort(publicIP, strconv.Itoa(port))))
for {
nConn, err := listener.Accept()
if err != nil {
logger.LogIf(context.Background(), err)
continue
}
// Before use, a handshake must be performed on the incoming net.Conn.
sconn, chans, reqs, err := ssh.NewServerConn(nConn, config)
if err != nil {
logger.LogIf(context.Background(), err)
continue
}
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
// protocol intended. In the case of an SFTP session, this is "subsystem"
// with a payload string of "<length=4>sftp"
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
logger.Fatal(err, "unable to accept the connection requests channel")
}
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "subsystem" request.
go func(in <-chan *ssh.Request) {
for req := range in {
// We only reply to SSH packets that have `sftp` payload.
req.Reply(req.Type == "subsystem" && string(req.Payload[4:]) == "sftp", nil)
}
}(requests)
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions))
if err := server.Serve(); err == io.EOF {
server.Close()
} else if err != nil {
logger.Fatal(err, "unable to start SFTP server")
}
}
}
}
func startFTPServer(c *cli.Context) {
args := c.StringSlice("ftp")
var (
port int
publicIP string
portRange string
tlsPrivateKey string
tlsPublicCert string
)
var err error
for _, arg := range args {
tokens := strings.SplitN(arg, "=", 2)
if len(tokens) != 2 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --ftp=%s", arg), "unable to start FTP server")
}
switch tokens[0] {
case "address":
host, portStr, err := net.SplitHostPort(tokens[1])
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --ftp=%s (%v)", arg, err), "unable to start FTP server")
}
port, err = strconv.Atoi(portStr)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed to --ftp=%s (%v)", arg, err), "unable to start FTP server")
}
if port < 1 || port > 65535 {
logger.Fatal(fmt.Errorf("invalid arguments passed to --ftp=%s, (port number must be between 1 to 65535)", arg), "unable to start FTP server")
}
publicIP = host
case "passive-port-range":
portRange = tokens[1]
case "tls-private-key":
tlsPrivateKey = tokens[1]
case "tls-public-cert":
tlsPublicCert = tokens[1]
}
}
// Verify if only partial inputs are given for FTP(secure)
{
if tlsPrivateKey == "" && tlsPublicCert != "" {
logger.Fatal(fmt.Errorf("invalid TLS arguments provided missing private key --ftp=\"tls-private-key=path/to/private.key\""), "unable to start FTP server")
}
if tlsPrivateKey != "" && tlsPublicCert == "" {
logger.Fatal(fmt.Errorf("invalid TLS arguments provided missing public cert --ftp=\"tls-public-cert=path/to/public.crt\""), "unable to start FTP server")
}
if port == 0 {
port = 8021 // Default FTP port, since no port was given.
}
}
// If no TLS certs were provided, server is running in TLS for S3 API
// we automatically make FTP also run under TLS mode.
if globalIsTLS && tlsPrivateKey == "" && tlsPublicCert == "" {
tlsPrivateKey = getPrivateKeyFile()
tlsPublicCert = getPublicCertFile()
}
tls := tlsPrivateKey != "" && tlsPublicCert != ""
name := "MinIO FTP Server"
if tls {
name = "MinIO FTP(Secure) Server"
}
ftpServer, err := ftp.NewServer(&ftp.Options{
Name: name,
WelcomeMessage: fmt.Sprintf("Welcome to MinIO FTP Server Version='%s' License='GNU AGPLv3'", Version),
Driver: NewFTPDriver(),
Port: port,
Perm: ftp.NewSimplePerm("nobody", "nobody"),
TLS: tls,
KeyFile: tlsPrivateKey,
CertFile: tlsPublicCert,
ExplicitFTPS: tls,
Logger: &minioLogger{},
PassivePorts: portRange,
PublicIP: publicIP,
})
if err != nil {
logger.Fatal(err, "unable to initialize FTP server")
}
logger.Info(fmt.Sprintf("%s listening on %s", name, net.JoinHostPort(publicIP, strconv.Itoa(port))))
if err = ftpServer.ListenAndServe(); err != nil {
logger.Fatal(err, "unable to start FTP server")
}
}
-56
View File
@@ -36,7 +36,6 @@ import (
"github.com/minio/minio/internal/config/dns"
"github.com/minio/minio/internal/crypto"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/http/stats"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext"
)
@@ -138,7 +137,6 @@ func setRequestLimitHandler(h http.Handler) http.Handler {
const (
minioReservedBucket = "minio"
minioReservedBucketPath = SlashSeparator + minioReservedBucket
minioReservedBucketPathWithSlash = SlashSeparator + minioReservedBucket + SlashSeparator
loginPathPrefix = SlashSeparator + "login"
)
@@ -280,60 +278,6 @@ func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
return time.Time{}, ErrMissingDateHeader
}
// splitStr splits a string into n parts, empty strings are added
// if we are not able to reach n elements
func splitStr(path, sep string, n int) []string {
splits := strings.SplitN(path, sep, n)
// Add empty strings if we found elements less than nr
for i := n - len(splits); i > 0; i-- {
splits = append(splits, "")
}
return splits
}
func url2Bucket(p string) (bucket string) {
tokens := splitStr(p, SlashSeparator, 3)
return tokens[1]
}
// setHttpStatsHandler sets a http Stats handler to gather HTTP statistics
func setHTTPStatsHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Meters s3 connection stats.
meteredRequest := &stats.IncomingTrafficMeter{ReadCloser: r.Body}
meteredResponse := &stats.OutgoingTrafficMeter{ResponseWriter: w}
// Execute the request
r.Body = meteredRequest
h.ServeHTTP(meteredResponse, r)
if strings.HasPrefix(r.URL.Path, storageRESTPrefix) ||
strings.HasPrefix(r.URL.Path, peerRESTPrefix) ||
strings.HasPrefix(r.URL.Path, peerS3Prefix) ||
strings.HasPrefix(r.URL.Path, lockRESTPrefix) {
globalConnStats.incInputBytes(meteredRequest.BytesRead())
globalConnStats.incOutputBytes(meteredResponse.BytesWritten())
return
}
if strings.HasPrefix(r.URL.Path, minioReservedBucketPath) {
globalConnStats.incAdminInputBytes(meteredRequest.BytesRead())
globalConnStats.incAdminOutputBytes(meteredResponse.BytesWritten())
return
}
globalConnStats.incS3InputBytes(meteredRequest.BytesRead())
globalConnStats.incS3OutputBytes(meteredResponse.BytesWritten())
if r.URL != nil {
bucket := url2Bucket(r.URL.Path)
if bucket != "" && bucket != minioReservedBucket {
globalBucketConnStats.incS3InputBytes(bucket, meteredRequest.BytesRead())
globalBucketConnStats.incS3OutputBytes(bucket, meteredResponse.BytesWritten())
}
}
})
}
// Bad path components to be rejected by the path validity handler.
const (
dotdotComponent = ".."
+29 -27
View File
@@ -81,12 +81,6 @@ func getLocalBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.Bg
ScannedItemsCount: bgSeq.getScannedItemsCount(),
}
if globalMRFState.initialized() {
status.MRF = map[string]madmin.MRFStatus{
globalLocalNodeName: globalMRFState.getCurrentMRFRoundInfo(),
}
}
healDisksMap := map[string]struct{}{}
for _, ep := range getLocalDisksToHeal() {
healDisksMap[ep.String()] = struct{}{}
@@ -187,16 +181,16 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
}
var forwardTo string
// If we resume to the same bucket, forward to last known item.
if tracker.Bucket != "" {
if tracker.Bucket == bucket {
forwardTo = tracker.Object
if b := tracker.getBucket(); b != "" {
if b == bucket {
forwardTo = tracker.getObject()
} else {
// Reset to where last bucket ended if resuming.
tracker.resume()
}
}
tracker.Object = ""
tracker.Bucket = bucket
tracker.setObject("")
tracker.setBucket(bucket)
// Heal current bucket again in case if it is failed
// in the being of erasure set healing
if _, err := er.HealBucket(ctx, bucket, madmin.HealOpts{
@@ -208,7 +202,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
if serverDebugLog {
console.Debugf(color.Green("healDrive:")+" healing bucket %s content on %s erasure set\n",
bucket, humanize.Ordinal(tracker.SetIndex+1))
bucket, humanize.Ordinal(er.setIndex+1))
}
disks, _ := er.getOnlineDisksWithHealing()
@@ -251,20 +245,14 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
go func() {
for res := range results {
if res.entryDone {
tracker.Object = res.name
if time.Since(tracker.LastUpdate) > time.Minute {
tracker.setObject(res.name)
if time.Since(tracker.getLastUpdate()) > time.Minute {
logger.LogIf(ctx, tracker.update(ctx))
}
continue
}
if res.success {
tracker.ItemsHealed++
tracker.BytesDone += res.bytes
} else {
tracker.ItemsFailed++
tracker.BytesFailed += res.bytes
}
tracker.updateProgress(res.success, res.bytes)
}
}()
@@ -339,7 +327,9 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
// If not deleted, assume they failed.
result = healEntryFailure(uint64(version.Size))
if version.VersionID != "" {
if !isErrVersionNotFound(err) {
logger.LogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err))
}
} else {
logger.LogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, version.Name, err))
}
@@ -418,21 +408,32 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
logger.LogIf(ctx, tracker.update(ctx))
}
}
tracker.Object = ""
tracker.Bucket = ""
tracker.setObject("")
tracker.setBucket("")
return retErr
}
// healObject sends the given object/version to the background healing workers
// and only returns when healing of the object is done.
func healObject(bucket, object, versionID string, scan madmin.HealScanMode) {
func healBucket(bucket string, scan madmin.HealScanMode) error {
// Get background heal sequence to send elements to heal
globalHealStateLK.Lock()
bgSeq, ok := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
globalHealStateLK.Unlock()
if ok {
bgSeq.queueHealTask(healSource{
return bgSeq.queueHealTask(healSource{bucket: bucket}, madmin.HealItemBucket)
}
return nil
}
// healObject sends the given object/version to the background healing workers
func healObject(bucket, object, versionID string, scan madmin.HealScanMode) error {
// Get background heal sequence to send elements to heal
globalHealStateLK.Lock()
bgSeq, ok := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
globalHealStateLK.Unlock()
if ok {
return bgSeq.queueHealTask(healSource{
bucket: bucket,
object: object,
versionID: versionID,
@@ -443,4 +444,5 @@ func healObject(bucket, object, versionID string, scan madmin.HealScanMode) {
},
}, madmin.HealItemObject)
}
return nil
}
+9
View File
@@ -30,6 +30,7 @@ import (
"github.com/minio/minio/internal/config/api"
xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext"
)
type apiConfig struct {
@@ -272,6 +273,10 @@ func maxClients(f http.HandlerFunc) http.HandlerFunc {
globalHTTPStats.addRequestsInQueue(1)
if tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt); ok {
tc.FuncName = "s3.MaxClients"
}
deadlineTimer := time.NewTimer(deadline)
defer deadlineTimer.Stop()
@@ -288,6 +293,10 @@ func maxClients(f http.HandlerFunc) http.HandlerFunc {
globalHTTPStats.addRequestsInQueue(-1)
return
case <-r.Context().Done():
// When the client disconnects before getting the S3 handler
// status code response, set the status code to 499 so this request
// will be properly audited and traced.
w.WriteHeader(499)
globalHTTPStats.addRequestsInQueue(-1)
return
}
+37 -2
View File
@@ -34,6 +34,7 @@ import (
"github.com/minio/minio/internal/handlers"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext"
xnet "github.com/minio/pkg/net"
)
@@ -357,8 +358,42 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
f.ServeHTTP(w, r)
if sw, ok := w.(*xhttp.ResponseRecorder); ok {
globalHTTPStats.updateStats(api, r, sw)
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
if !ok {
return
}
if tc != nil {
if strings.HasPrefix(r.URL.Path, storageRESTPrefix) ||
strings.HasPrefix(r.URL.Path, peerRESTPrefix) ||
strings.HasPrefix(r.URL.Path, peerS3Prefix) ||
strings.HasPrefix(r.URL.Path, lockRESTPrefix) {
globalConnStats.incInputBytes(int64(tc.RequestRecorder.Size()))
globalConnStats.incOutputBytes(int64(tc.ResponseRecorder.Size()))
return
}
if strings.HasPrefix(r.URL.Path, minioReservedBucketPath) {
globalConnStats.incAdminInputBytes(int64(tc.RequestRecorder.Size()))
globalConnStats.incAdminOutputBytes(int64(tc.ResponseRecorder.Size()))
return
}
globalHTTPStats.updateStats(api, tc.ResponseRecorder)
globalConnStats.incS3InputBytes(int64(tc.RequestRecorder.Size()))
globalConnStats.incS3OutputBytes(int64(tc.ResponseRecorder.Size()))
resource, err := getResource(r.URL.Path, r.Host, globalDomainNames)
if err != nil {
logger.LogIf(r.Context(), fmt.Errorf("Unable to get the actual resource in the incoming request: %v", err))
return
}
bucket, _ := path2BucketObject(resource)
if bucket != "" && bucket != minioReservedBucket {
globalBucketConnStats.incS3InputBytes(bucket, int64(tc.RequestRecorder.Size()))
globalBucketConnStats.incS3OutputBytes(bucket, int64(tc.ResponseRecorder.Size()))
}
}
}
}
+1 -7
View File
@@ -19,7 +19,6 @@ package cmd
import (
"net/http"
"strings"
"sync"
"sync/atomic"
@@ -299,12 +298,7 @@ func (st *HTTPStats) toServerHTTPStats() ServerHTTPStats {
}
// Update statistics from http request and response data
func (st *HTTPStats) updateStats(api string, r *http.Request, w *xhttp.ResponseRecorder) {
// Ignore non S3 requests
if strings.HasSuffix(r.URL.Path, minioReservedBucketPathWithSlash) {
return
}
func (st *HTTPStats) updateStats(api string, w *xhttp.ResponseRecorder) {
st.totalS3Requests.Inc(api)
// Increment the prometheus http request response histogram with appropriate label
+13 -20
View File
@@ -71,40 +71,33 @@ func httpTracer(h http.Handler) http.Handler {
// http stats requests and audit if enabled.
respRecorder := xhttp.NewResponseRecorder(w)
if globalTrace.NumSubscribers(madmin.TraceS3|madmin.TraceInternal) == 0 {
h.ServeHTTP(respRecorder, r)
return
}
// Setup a http request body recorder
reqRecorder := &xhttp.RequestRecorder{Reader: r.Body}
r.Body = reqRecorder
// Create tracing data structure and associate it to the request context
tc := mcontext.TraceCtxt{
AmzReqID: r.Header.Get(xhttp.AmzRequestID),
RequestRecorder: reqRecorder,
ResponseRecorder: respRecorder,
}
ctx := context.WithValue(r.Context(), mcontext.ContextTraceKey, &tc)
r = r.WithContext(ctx)
// Setup a http request body recorder
reqRecorder := &xhttp.RequestRecorder{Reader: r.Body}
tc.RequestRecorder = reqRecorder
tc.ResponseRecorder = respRecorder
// Execute call.
r.Body = reqRecorder
r = r.WithContext(context.WithValue(r.Context(), mcontext.ContextTraceKey, &tc))
reqStartTime := time.Now().UTC()
h.ServeHTTP(respRecorder, r)
reqEndTime := time.Now().UTC()
if globalTrace.NumSubscribers(madmin.TraceS3|madmin.TraceInternal) == 0 {
// no subscribers nothing to trace.
return
}
tt := madmin.TraceInternal
if strings.HasPrefix(tc.FuncName, "s3.") {
tt = madmin.TraceS3
}
// No need to continue if no subscribers for actual type...
if globalTrace.NumSubscribers(tt) == 0 {
return
}
// Calculate input body size with headers
reqHeaders := r.Header.Clone()
reqHeaders.Set("Host", r.Host)
@@ -113,7 +106,7 @@ func httpTracer(h http.Handler) http.Handler {
} else {
reqHeaders.Set("Transfer-Encoding", strings.Join(r.TransferEncoding, ","))
}
inputBytes := reqRecorder.BodySize()
inputBytes := reqRecorder.Size()
for k, v := range reqHeaders {
inputBytes += len(k) + len(v)
}
+5
View File
@@ -1451,6 +1451,11 @@ func (sys *IAMSys) GetUser(ctx context.Context, accessKey string) (u UserIdentit
u, ok = sys.store.GetUser(accessKey)
}
if !ok {
if accessKey == globalActiveCred.AccessKey {
return newUserIdentity(globalActiveCred), true
}
}
return u, ok && u.Credentials.IsValid()
}
+31 -3
View File
@@ -20,6 +20,7 @@ package cmd
import (
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/minio/minio/internal/event"
@@ -141,8 +142,30 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
peer.Listen(listenCh, ctx.Done(), values)
}
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
defer keepAliveTicker.Stop()
var (
emptyEventTicker <-chan time.Time
keepAliveTicker <-chan time.Time
)
if p := values.Get("ping"); p != "" {
pingInterval, err := strconv.Atoi(p)
if err != nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidQueryParams), r.URL)
return
}
if pingInterval < 1 {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidQueryParams), r.URL)
return
}
t := time.NewTicker(time.Duration(pingInterval) * time.Second)
defer t.Stop()
emptyEventTicker = t.C
} else {
// Deprecated Apr 2023
t := time.NewTicker(500 * time.Millisecond)
defer t.Stop()
keepAliveTicker = t.C
}
enc := json.NewEncoder(w)
for {
@@ -155,7 +178,12 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
// Flush if nothing is queued
w.(http.Flusher).Flush()
}
case <-keepAliveTicker.C:
case <-emptyEventTicker:
if err := enc.Encode(struct{ Records []event.Event }{}); err != nil {
return
}
w.(http.Flusher).Flush()
case <-keepAliveTicker:
if _, err := w.Write([]byte(" ")); err != nil {
return
}
+14 -9
View File
@@ -18,6 +18,7 @@
package cmd
import (
"bytes"
"context"
"fmt"
"io"
@@ -126,6 +127,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
scanDir = func(current string) error {
// Skip forward, if requested...
var sb bytes.Buffer
forward := ""
if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, current) {
forward = strings.TrimPrefix(opts.ForwardTo, current)
@@ -160,6 +162,9 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
return nil
}
dirObjects := make(map[string]struct{})
// Avoid a bunch of cleanup when joining.
current = strings.Trim(current, SlashSeparator)
for i, entry := range entries {
if opts.Limit > 0 && objsReturned >= opts.Limit {
return nil
@@ -176,7 +181,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
entries[i] = ""
continue
}
if strings.HasSuffix(entry, slashSeparator) {
if hasSuffixByte(entry, SlashSeparatorChar) {
if strings.HasSuffix(entry, globalDirSuffixWithSlash) {
// Add without extension so it is sorted correctly.
entry = strings.TrimSuffix(entry, globalDirSuffixWithSlash) + slashSeparator
@@ -198,7 +203,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if HasSuffix(entry, xlStorageFormatFile) {
var meta metaCacheEntry
s.walkReadMu.Lock()
meta.metadata, err = s.readMetadata(ctx, pathJoin(volumeDir, current, entry))
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(&sb, volumeDir, current, entry))
s.walkReadMu.Unlock()
diskHealthCheckOK(ctx, err)
if err != nil {
@@ -212,7 +217,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
}
meta.name = strings.TrimSuffix(entry, xlStorageFormatFile)
meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
meta.name = pathJoin(current, meta.name)
meta.name = pathJoinBuf(&sb, current, meta.name)
meta.name = decodeDirObject(meta.name)
objReturned(meta.metadata)
@@ -223,7 +228,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if HasSuffix(entry, xlStorageFormatFileV1) {
var meta metaCacheEntry
s.walkReadMu.Lock()
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, current, entry))
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(&sb, volumeDir, current, entry))
s.walkReadMu.Unlock()
diskHealthCheckOK(ctx, err)
if err != nil {
@@ -234,7 +239,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
}
meta.name = strings.TrimSuffix(entry, xlStorageFormatFileV1)
meta.name = strings.TrimSuffix(meta.name, SlashSeparator)
meta.name = pathJoin(current, meta.name)
meta.name = pathJoinBuf(&sb, current, meta.name)
objReturned(meta.metadata)
out <- meta
@@ -267,7 +272,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
if contextCanceled(ctx) {
return ctx.Err()
}
meta := metaCacheEntry{name: pathJoin(current, entry)}
meta := metaCacheEntry{name: pathJoinBuf(&sb, current, entry)}
// If directory entry on stack before this, pop it now.
for len(dirStack) > 0 && dirStack[len(dirStack)-1] < meta.name {
@@ -291,7 +296,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
}
s.walkReadMu.Lock()
meta.metadata, err = s.readMetadata(ctx, pathJoin(volumeDir, meta.name, xlStorageFormatFile))
meta.metadata, err = s.readMetadata(ctx, pathJoinBuf(&sb, volumeDir, meta.name, xlStorageFormatFile))
s.walkReadMu.Unlock()
diskHealthCheckOK(ctx, err)
switch {
@@ -304,7 +309,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
out <- meta
case osIsNotExist(err), isSysErrIsDir(err):
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
meta.metadata, err = xioutil.ReadFile(pathJoinBuf(&sb, volumeDir, meta.name, xlStorageFormatFileV1))
diskHealthCheckOK(ctx, err)
if err == nil {
// It was an object
@@ -317,7 +322,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
// NOT an object, append to stack (with slash)
// If dirObject, but no metadata (which is unexpected) we skip it.
if !isDirObj {
if !isDirEmpty(pathJoin(volumeDir, meta.name+slashSeparator)) {
if !isDirEmpty(pathJoinBuf(&sb, volumeDir, meta.name)) {
dirStack = append(dirStack, meta.name+slashSeparator)
}
}
+17
View File
@@ -155,6 +155,7 @@ const (
waitingTotal MetricName = "waiting_total"
incomingTotal MetricName = "incoming_total"
objectTotal MetricName = "object_total"
versionTotal MetricName = "version_total"
offlineTotal MetricName = "offline_total"
onlineTotal MetricName = "online_total"
openTotal MetricName = "open_total"
@@ -509,6 +510,16 @@ func getBucketUsageObjectsTotalMD() MetricDescription {
}
}
func getBucketUsageVersionsTotalMD() MetricDescription {
return MetricDescription{
Namespace: bucketMetricNamespace,
Subsystem: usageSubsystem,
Name: versionTotal,
Help: "Total number of versions",
Type: gaugeMetric,
}
}
func getBucketRepLatencyMD() MetricDescription {
return MetricDescription{
Namespace: bucketMetricNamespace,
@@ -1995,6 +2006,12 @@ func getBucketUsageMetrics() *MetricsGroup {
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getBucketUsageVersionsTotalMD(),
Value: float64(usage.VersionsCount),
VariableLabels: map[string]string{"bucket": bucket},
})
metrics = append(metrics, Metric{
Description: getBucketRepReceivedBytesMD(),
Value: float64(stats.ReplicaSize),
+24 -155
View File
@@ -20,16 +20,13 @@ package cmd
import (
"context"
"sync"
"sync/atomic"
"time"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/logger"
)
const (
mrfInfoResetInterval = 10 * time.Second
mrfOpsQueueSize = 10000
mrfOpsQueueSize = 100000
)
// partialOperation is a successful upload/delete of an object
@@ -38,35 +35,17 @@ type partialOperation struct {
bucket string
object string
versionID string
size int64
setIndex int
poolIndex int
}
type setInfo struct {
index, pool int
queued time.Time
}
// mrfState sncapsulates all the information
// related to the global background MRF.
type mrfState struct {
ready int32 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
_ int32 // For 64 bits alignment
ctx context.Context
objectAPI ObjectLayer
mu sync.Mutex
opCh chan partialOperation
pendingOps map[partialOperation]setInfo
setReconnectEvent chan setInfo
itemsHealed uint64
bytesHealed uint64
pendingItems uint64
pendingBytes uint64
triggeredAt time.Time
}
// Initialize healing MRF subsystem
@@ -75,24 +54,15 @@ func (m *mrfState) init(ctx context.Context, objAPI ObjectLayer) {
defer m.mu.Unlock()
m.ctx = ctx
m.objectAPI = objAPI
m.opCh = make(chan partialOperation, mrfOpsQueueSize)
m.pendingOps = make(map[partialOperation]setInfo)
m.setReconnectEvent = make(chan setInfo)
m.objectAPI = objAPI
go globalMRFState.maintainMRFList()
go globalMRFState.healRoutine()
atomic.StoreInt32(&m.ready, 1)
}
func (m *mrfState) initialized() bool {
return atomic.LoadInt32(&m.ready) != 0
}
// Add a partial S3 operation (put/delete) when one or more disks are offline.
func (m *mrfState) addPartialOp(op partialOperation) {
if !m.initialized() {
if m == nil {
return
}
@@ -102,140 +72,39 @@ func (m *mrfState) addPartialOp(op partialOperation) {
}
}
// Receive the new set (disk) reconnection event
func (m *mrfState) newSetReconnected(pool, set int) {
if !m.initialized() {
return
}
idler := time.NewTimer(100 * time.Millisecond)
defer idler.Stop()
select {
case m.setReconnectEvent <- setInfo{index: set, pool: pool}:
case <-idler.C:
}
}
// Get current MRF stats of the last MRF activity
func (m *mrfState) getCurrentMRFRoundInfo() madmin.MRFStatus {
m.mu.Lock()
triggeredAt := m.triggeredAt
itemsHealed := m.itemsHealed
bytesHealed := m.bytesHealed
pendingItems := m.pendingItems
pendingBytes := m.pendingBytes
m.mu.Unlock()
if pendingItems == 0 {
return madmin.MRFStatus{}
}
return madmin.MRFStatus{
Started: triggeredAt,
ItemsHealed: itemsHealed,
BytesHealed: bytesHealed,
TotalItems: itemsHealed + pendingItems,
TotalBytes: bytesHealed + pendingBytes,
}
}
// maintainMRFList gathers the list of successful partial uploads
// from all underlying er.sets and puts them in a global map which
// should not have more than 10000 entries.
func (m *mrfState) maintainMRFList() {
for fOp := range m.opCh {
m.mu.Lock()
if len(m.pendingOps) > mrfOpsQueueSize {
m.mu.Unlock()
continue
}
m.pendingOps[fOp] = setInfo{index: fOp.setIndex, pool: fOp.poolIndex}
m.pendingItems++
if fOp.size > 0 {
m.pendingBytes += uint64(fOp.size)
}
m.mu.Unlock()
}
}
// Reset current MRF stats
func (m *mrfState) resetMRFInfoIfNoPendingOps() {
m.mu.Lock()
defer m.mu.Unlock()
if m.pendingItems > 0 {
return
}
m.itemsHealed = 0
m.bytesHealed = 0
m.pendingItems = 0
m.pendingBytes = 0
m.triggeredAt = time.Time{}
}
var healSleeper = newDynamicSleeper(5, time.Second, false)
// healRoutine listens to new disks reconnection events and
// issues healing requests for queued objects belonging to the
// corresponding erasure set
func (m *mrfState) healRoutine() {
idler := time.NewTimer(mrfInfoResetInterval)
defer idler.Stop()
mrfHealingOpts := madmin.HealOpts{
ScanMode: madmin.HealNormalScan,
Remove: healDeleteDangling,
}
for {
select {
case <-m.ctx.Done():
return
case <-idler.C:
m.resetMRFInfoIfNoPendingOps()
idler.Reset(mrfInfoResetInterval)
case setInfo := <-m.setReconnectEvent:
// Get the list of objects related the er.set
// to which the connected disk belongs.
var mrfOperations []partialOperation
m.mu.Lock()
for k, v := range m.pendingOps {
if v == setInfo {
mrfOperations = append(mrfOperations, k)
}
}
m.mu.Unlock()
if len(mrfOperations) == 0 {
continue
case u, ok := <-m.opCh:
if !ok {
return
}
m.mu.Lock()
m.triggeredAt = time.Now().UTC()
m.mu.Unlock()
// Heal objects
for _, u := range mrfOperations {
_, err := m.objectAPI.HealObject(m.ctx, u.bucket, u.object, u.versionID, mrfHealingOpts)
m.mu.Lock()
if err == nil {
m.itemsHealed++
m.bytesHealed += uint64(u.size)
}
m.pendingItems--
m.pendingBytes -= uint64(u.size)
delete(m.pendingOps, u)
m.mu.Unlock()
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
// Log healing error if any
logger.LogIf(m.ctx, err)
}
now := time.Now()
if now.Sub(u.queued) < time.Second {
// let recently failed networks to reconnect
// making MRF wait for 1s before retrying,
// i.e 4 reconnect attempts.
time.Sleep(1 * time.Second)
}
waitForLowHTTPReq()
// wait on timer per heal
wait := healSleeper.Timer(context.Background())
if u.object == "" {
healBucket(u.bucket, madmin.HealNormalScan)
} else {
healObject(u.bucket, u.object, u.versionID, madmin.HealNormalScan)
}
wait()
}
}
}
+3
View File
@@ -193,6 +193,9 @@ type ObjectInfo struct {
// Checksums added on upload.
// Encoded, maybe encrypted.
Checksum []byte
// Inlined
Inlined bool
}
// ArchiveInfo returns any saved zip archive meta information.
+6
View File
@@ -674,6 +674,12 @@ func isErrBucketNotFound(err error) bool {
return errors.As(err, &bkNotFound)
}
// isErrReadQuorum check if the error type is InsufficentReadQuorum
func isErrReadQuorum(err error) bool {
var rquorum InsufficientReadQuorum
return errors.As(err, &rquorum)
}
// isErrObjectNotFound - Check if error type is ObjectNotFound.
func isErrObjectNotFound(err error) bool {
var objNotFound ObjectNotFound
+4 -13
View File
@@ -179,15 +179,6 @@ func (o *ObjectOptions) PutReplicationState() (r ReplicationState) {
return
}
// LockType represents required locking for ObjectLayer operations
type LockType int
const (
noLock LockType = iota
readLock
writeLock
)
// ObjectLayer implements primitives for object API layer.
type ObjectLayer interface {
// Locking operations on object.
@@ -214,12 +205,12 @@ type ObjectLayer interface {
// Object operations.
// GetObjectNInfo returns a GetObjectReader that satisfies the
// ReadCloser interface. The Close method unlocks the object
// after reading, so it must always be called after usage.
// ReadCloser interface. The Close method runs any cleanup
// functions, so it must always be called after reading till EOF
//
// IMPORTANTLY, when implementations return err != nil, this
// function MUST NOT return a non-nil ReadCloser.
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (reader *GetObjectReader, err error)
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (reader *GetObjectReader, err error)
GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
CopyObject(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error)
@@ -272,7 +263,7 @@ func GetObject(ctx context.Context, api ObjectLayer, bucket, object string, star
}
Range := &HTTPRangeSpec{Start: startOffset, End: startOffset + length}
reader, err := api.GetObjectNInfo(ctx, bucket, object, Range, header, readLock, opts)
reader, err := api.GetObjectNInfo(ctx, bucket, object, Range, header, opts)
if err != nil {
return err
}
+3
View File
@@ -89,6 +89,9 @@ func getOpts(ctx context.Context, r *http.Request, bucket, object string) (Objec
if err != nil {
return opts, err
}
if isMaxPartID(partNumber) {
return opts, errInvalidMaxParts
}
if partNumber <= 0 {
return opts, errInvalidArgument
}
+93 -1
View File
@@ -209,6 +209,9 @@ func checkObjectNameForLengthAndSlash(bucket, object string) error {
// SlashSeparator - slash separator.
const SlashSeparator = "/"
// SlashSeparatorChar - slash separator.
const SlashSeparatorChar = '/'
// retainSlash - retains slash from a path.
func retainSlash(s string) string {
if s == "" {
@@ -231,13 +234,102 @@ func pathsJoinPrefix(prefix string, elem ...string) (paths []string) {
func pathJoin(elem ...string) string {
trailingSlash := ""
if len(elem) > 0 {
if HasSuffix(elem[len(elem)-1], SlashSeparator) {
if hasSuffixByte(elem[len(elem)-1], SlashSeparatorChar) {
trailingSlash = SlashSeparator
}
}
return path.Join(elem...) + trailingSlash
}
// pathJoinBuf - like path.Join() but retains trailing SlashSeparator of the last element.
// Provide a string builder to reduce allocation.
func pathJoinBuf(dst *bytes.Buffer, elem ...string) string {
trailingSlash := len(elem) > 0 && hasSuffixByte(elem[len(elem)-1], SlashSeparatorChar)
dst.Reset()
added := 0
for _, e := range elem {
if added > 0 || e != "" {
if added > 0 {
dst.WriteRune(SlashSeparatorChar)
}
dst.WriteString(e)
added += len(e)
}
}
if pathNeedsClean(dst.Bytes()) {
s := path.Clean(dst.String())
if trailingSlash {
return s + SlashSeparator
}
return s
}
if trailingSlash {
dst.WriteRune(SlashSeparatorChar)
}
return dst.String()
}
// hasSuffixByte returns true if the last byte of s is 'suffix'
func hasSuffixByte(s string, suffix byte) bool {
return len(s) > 0 && s[len(s)-1] == suffix
}
// pathNeedsClean returns whether path.Clean may change the path.
// Will detect all cases that will be cleaned,
// but may produce false positives on non-trivial paths.
func pathNeedsClean(path []byte) bool {
if len(path) == 0 {
return true
}
rooted := path[0] == '/'
n := len(path)
r, w := 0, 0
if rooted {
r, w = 1, 1
}
for r < n {
switch {
case path[r] > 127:
// Non ascii.
return true
case path[r] == '/':
// multiple / elements
return true
case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
// . element - assume it has to be cleaned.
return true
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
// .. element: remove to last / - assume it has to be cleaned.
return true
default:
// real path element.
// add slash if needed
if rooted && w != 1 || !rooted && w != 0 {
w++
}
// copy element
for ; r < n && path[r] != '/'; r++ {
w++
}
// allow one slash, not at end
if r < n-1 && path[r] == '/' {
r++
}
}
}
// Turn empty string into "."
if w == 0 {
return true
}
return false
}
// mustGetUUID - get a random UUID.
func mustGetUUID() string {
u, err := uuid.NewRandom()
+67
View File
@@ -740,3 +740,70 @@ func TestS2CompressReader(t *testing.T) {
})
}
}
func Test_pathNeedsClean(t *testing.T) {
type pathTest struct {
path, result string
}
cleantests := []pathTest{
// Already clean
{"", "."},
{"abc", "abc"},
{"abc/def", "abc/def"},
{"a/b/c", "a/b/c"},
{".", "."},
{"..", ".."},
{"../..", "../.."},
{"../../abc", "../../abc"},
{"/abc", "/abc"},
{"/abc/def", "/abc/def"},
{"/", "/"},
// Remove trailing slash
{"abc/", "abc"},
{"abc/def/", "abc/def"},
{"a/b/c/", "a/b/c"},
{"./", "."},
{"../", ".."},
{"../../", "../.."},
{"/abc/", "/abc"},
// Remove doubled slash
{"abc//def//ghi", "abc/def/ghi"},
{"//abc", "/abc"},
{"///abc", "/abc"},
{"//abc//", "/abc"},
{"abc//", "abc"},
// Remove . elements
{"abc/./def", "abc/def"},
{"/./abc/def", "/abc/def"},
{"abc/.", "abc"},
// Remove .. elements
{"abc/def/ghi/../jkl", "abc/def/jkl"},
{"abc/def/../ghi/../jkl", "abc/jkl"},
{"abc/def/..", "abc"},
{"abc/def/../..", "."},
{"/abc/def/../..", "/"},
{"abc/def/../../..", ".."},
{"/abc/def/../../..", "/"},
{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
// Combinations
{"abc/./../def", "def"},
{"abc//./../def", "def"},
{"abc/../../././../def", "../../def"},
}
for _, test := range cleantests {
want := test.path != test.result
got := pathNeedsClean([]byte(test.path))
if !got {
t.Logf("no clean: %q", test.path)
}
if want && !got {
t.Errorf("input: %q, want %v, got %v", test.path, want, got)
}
}
}
+8 -6
View File
@@ -367,15 +367,17 @@ func deleteObjectVersions(ctx context.Context, o ObjectLayer, bucket string, toD
continue
}
dobj := deletedObjs[i]
// Send audit for the lifecycle delete operation
auditLogLifecycle(
ctx,
ObjectInfo{
oi := ObjectInfo{
Bucket: bucket,
Name: dobj.ObjectName,
VersionID: dobj.VersionID,
},
ILMExpiry)
}
traceFn := globalLifecycleSys.trace(oi)
// Send audit for the lifecycle delete operation
auditLogLifecycle(
ctx,
oi,
ILMExpiry, traceFn)
sendEvent(eventArgs{
EventName: event.ObjectRemovedDelete,
+21 -14
View File
@@ -238,7 +238,8 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
Start: offset,
End: -1,
}
return getObjectNInfo(ctx, bucket, object, rs, r.Header, noLock, opts)
opts.NoLock = true
return getObjectNInfo(ctx, bucket, object, rs, r.Header, opts)
},
actualSize,
)
@@ -402,7 +403,7 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
}
var proxy proxyResult
gr, err := getObjectNInfo(ctx, bucket, object, rs, r.Header, readLock, opts)
gr, err := getObjectNInfo(ctx, bucket, object, rs, r.Header, opts)
if err != nil {
var (
reader *GetObjectReader
@@ -1098,19 +1099,12 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return checkCopyObjectPreconditions(ctx, w, r, o)
}
getOpts.CheckPrecondFn = checkCopyPrecondFn
// FIXME: a possible race exists between a parallel
// GetObject v/s CopyObject with metadata updates, ideally
// we should be holding write lock here but it is not
// possible due to other constraints such as knowing
// the type of source content etc.
lock := noLock
if !cpSrcDstSame {
lock = readLock
if cpSrcDstSame {
getOpts.NoLock = true
}
var rs *HTTPRangeSpec
gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, lock, getOpts)
gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, getOpts)
if err != nil {
if isErrPreconditionFailed(err) {
return
@@ -2084,11 +2078,24 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
getObjectInfo = api.CacheAPI().GetObjectInfo
}
// Extract request metadata
metadata, err := extractMetadata(ctx, r)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
metadata[xhttp.AmzStorageClass] = sc
putObjectTar := func(reader io.Reader, info os.FileInfo, object string) error {
size := info.Size()
metadata := map[string]string{
xhttp.AmzStorageClass: sc,
// Copy metadata and make space for a bit more
metaCopy := make(map[string]string, len(metadata)+5)
for k, v := range metadata {
metaCopy[k] = v
}
// Shadow now...
metadata := metaCopy
actualSize := size
var idxCb func() []byte
+5 -5
View File
@@ -1268,7 +1268,7 @@ func testAPIPutObjectStreamSigV4Handler(obj ObjectLayer, instanceType, bucketNam
t.Fatalf("Test %d: %s: ContentEncoding is set to \"%s\" which is unexpected, expected \"%s\"", i+1, instanceType, objInfo.ContentEncoding, expectedContentEncoding)
}
buffer := new(bytes.Buffer)
r, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
r, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, opts)
if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
}
@@ -1559,7 +1559,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
if testCase.expectedRespStatus == http.StatusOK {
buffer := new(bytes.Buffer)
// Fetch the object to check whether the content is same as the one uploaded via PutObject.
gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, opts)
if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i, instanceType, err)
}
@@ -1608,7 +1608,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
if testCase.expectedRespStatus == http.StatusOK {
buffer := new(bytes.Buffer)
// Fetch the object to check whether the content is same as the one uploaded via PutObject.
gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, opts)
if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i, instanceType, err)
}
@@ -1769,7 +1769,7 @@ func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketNam
}
var buf bytes.Buffer
r, err := obj.GetObjectNInfo(context.Background(), bucketName, testObject, nil, nil, readLock, ObjectOptions{})
r, err := obj.GetObjectNInfo(context.Background(), bucketName, testObject, nil, nil, ObjectOptions{})
if err != nil {
t.Fatalf("Test: %s reading completed file failed: <ERROR> %v", instanceType, err)
}
@@ -2485,7 +2485,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
// Note that this goes directly to the file system,
// so encryption/compression may interfere at some point.
buffers[0].Reset()
r, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.newObjectName, nil, nil, readLock, opts)
r, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.newObjectName, nil, nil, opts)
if err != nil {
t.Fatalf("Test %d: %s reading completed file failed: <ERROR> %v", i, instanceType, err)
}
+3 -2
View File
@@ -46,8 +46,9 @@ func getLambdaEventData(bucket, object string, cred auth.Credentials, r *http.Re
secure = globalMinioEndpointURL.Scheme == "https"
}
duration := time.Since(cred.Expiration)
if cred.Expiration.IsZero() {
duration := time.Until(cred.Expiration)
if cred.Expiration.IsZero() || duration > time.Hour {
// Always limit to 1 hour.
duration = time.Hour
}
+1 -1
View File
@@ -353,7 +353,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
return false
}
getOpts.CheckPrecondFn = checkCopyPartPrecondFn
gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, readLock, getOpts)
gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, getOpts)
if err != nil {
if isErrPreconditionFailed(err) {
return
+27 -13
View File
@@ -137,16 +137,7 @@ func (sys *S3PeerSys) ListBuckets(ctx context.Context, opts BucketOptions) (resu
func (sys *S3PeerSys) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (binfo BucketInfo, err error) {
g := errgroup.WithNErrs(len(sys.peerClients))
bucketInfos := make([]BucketInfo, len(sys.peerClients)+1)
bucketInfo, err := getBucketInfoLocal(ctx, bucket, opts)
if err != nil {
return BucketInfo{}, err
}
errs := []error{nil}
bucketInfos[0] = bucketInfo
bucketInfos := make([]BucketInfo, len(sys.peerClients))
for idx, client := range sys.peerClients {
idx := idx
client := client
@@ -158,19 +149,29 @@ func (sys *S3PeerSys) GetBucketInfo(ctx context.Context, bucket string, opts Buc
if err != nil {
return err
}
bucketInfos[idx+1] = bucketInfo
bucketInfos[idx] = bucketInfo
return nil
}, idx)
}
errs = append(errs, g.Wait()...)
errs := g.Wait()
bucketInfo, err := getBucketInfoLocal(ctx, bucket, opts)
errs = append(errs, err)
bucketInfos = append(bucketInfos, bucketInfo)
quorum := (len(sys.allPeerClients) / 2)
if err = reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, quorum); err != nil {
return BucketInfo{}, toObjectErr(err, bucket)
}
return bucketInfo, nil
for i, err := range errs {
if err == nil {
return bucketInfos[i], nil
}
}
return BucketInfo{}, toObjectErr(errVolumeNotFound, bucket)
}
func (client *peerS3Client) ListBuckets(ctx context.Context, opts BucketOptions) ([]BucketInfo, error) {
@@ -224,6 +225,19 @@ func (sys *S3PeerSys) MakeBucket(ctx context.Context, bucket string, opts MakeBu
quorum := (len(sys.allPeerClients) / 2) + 1
err := reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, quorum)
// Perform MRF on missing buckets for temporary errors.
for _, err := range errs {
if err == nil {
continue
}
if errors.Is(err, errPeerOffline) || errors.Is(err, errDiskNotFound) ||
isNetworkError(err) {
globalMRFState.addPartialOp(partialOperation{
bucket: bucket,
})
}
}
return toObjectErr(err, bucket)
}
-2
View File
@@ -58,8 +58,6 @@ var globalHandlers = []mux.MiddlewareFunc{
setCrossDomainPolicy,
// Limits all body and header sizes to a maximum fixed limit
setRequestLimitHandler,
// Network statistics
setHTTPStatsHandler,
// Validate all the incoming requests.
setRequestValidityHandler,
// set x-amz-request-id header.
+2 -2
View File
@@ -188,7 +188,7 @@ func (api objectAPIHandlers) getObjectInArchiveFileHandler(ctx context.Context,
end = zipObjInfo.Size
}
rs := &HTTPRangeSpec{Start: file.Offset, End: end}
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, zipPath, rs, nil, readLock, opts)
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, zipPath, rs, nil, opts)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
@@ -331,7 +331,7 @@ func getFilesListFromZIPObject(ctx context.Context, objectAPI ObjectLayer, bucke
var objSize int64
for {
rs := &HTTPRangeSpec{IsSuffixLength: true, Start: int64(-size)}
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, rs, nil, readLock, opts)
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, rs, nil, opts)
if err != nil {
return nil, ObjectInfo{}, err
}
+26 -7
View File
@@ -105,6 +105,14 @@ var ServerFlags = []cli.Flag{
Value: 10 * time.Minute,
EnvVar: "MINIO_CONN_WRITE_DEADLINE",
},
cli.StringSliceFlag{
Name: "ftp",
Usage: "enable and configure an FTP(Secure) server",
},
cli.StringSliceFlag{
Name: "sftp",
Usage: "enable and configure an SFTP server",
},
}
var gatewayCmd = cli.Command{
@@ -145,22 +153,23 @@ FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXAMPLES:
1. Start minio server on "/home/shared" directory.
1. Start MinIO server on "/home/shared" directory.
{{.Prompt}} {{.HelpName}} /home/shared
2. Start single node server with 64 local drives "/mnt/data1" to "/mnt/data64".
{{.Prompt}} {{.HelpName}} /mnt/data{1...64}
3. Start distributed minio server on an 32 node setup with 32 drives each, run following command on all the nodes
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_USER{{.AssignmentOperator}}minio
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_PASSWORD{{.AssignmentOperator}}miniostorage
3. Start distributed MinIO server on an 32 node setup with 32 drives each, run following command on all the nodes
{{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32}
4. Start distributed minio server in an expanded setup, run the following command on all the nodes
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_USER{{.AssignmentOperator}}minio
{{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_PASSWORD{{.AssignmentOperator}}miniostorage
4. Start distributed MinIO server in an expanded setup, run the following command on all the nodes
{{.Prompt}} {{.HelpName}} http://node{1...16}.example.com/mnt/export{1...32} \
http://node{17...64}.example.com/mnt/export{1...64}
5. Start distributed MinIO server, with FTP and SFTP servers on all interfaces via port 8021, 8022 respectively
{{.Prompt}} {{.HelpName}} http://node{1...4}.example.com/mnt/export{1...4} \
--ftp="address=:8021" --ftp="passive-port-range=30000-40000" \
--sftp="address=:8022" --sftp="ssh-private-key=${HOME}/.ssh/id_rsa"
`,
}
@@ -667,6 +676,16 @@ func serverMain(ctx *cli.Context) {
logger.FatalIf(newConsoleServerFn().Serve(), "Unable to initialize console server")
}()
}
// if we see FTP args, start FTP if possible
if len(ctx.StringSlice("ftp")) > 0 {
go startFTPServer(ctx)
}
// If we see SFTP args, start SFTP if possible
if len(ctx.StringSlice("sftp")) > 0 {
go startSFTPServer(ctx)
}
}()
// Background all other operations such as initializing bucket metadata etc.
+5 -4
View File
@@ -127,16 +127,14 @@ func printServerCommonMsg(apiEndpoints []string) {
apiEndpointStr := strings.Join(apiEndpoints, " ")
// Colorize the message and print.
logger.Info(color.Blue("API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
logger.Info(color.Blue("S3-API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
if color.IsTerminal() && (!globalCLIContext.Anonymous && !globalCLIContext.JSON) {
logger.Info(color.Blue("RootUser: ") + color.Bold(fmt.Sprintf("%s ", cred.AccessKey)))
logger.Info(color.Blue("RootPass: ") + color.Bold(fmt.Sprintf("%s ", cred.SecretKey)))
logger.Info(color.Blue("RootPass: ") + color.Bold(fmt.Sprintf("%s \n", cred.SecretKey)))
if region != "" {
logger.Info(color.Blue("Region: ") + color.Bold(fmt.Sprintf(getFormatStr(len(region), 2), region)))
}
}
printEventNotifiers()
printLambdaTargets()
if globalBrowserEnabled {
consoleEndpointStr := strings.Join(stripStandardPorts(getConsoleEndpoints(), globalMinioConsoleHost), " ")
@@ -146,6 +144,9 @@ func printServerCommonMsg(apiEndpoints []string) {
logger.Info(color.Blue("RootPass: ") + color.Bold(fmt.Sprintf("%s ", cred.SecretKey)))
}
}
printEventNotifiers()
printLambdaTargets()
}
// Prints startup message for Object API access, prints link to our SDK documentation.
+442
View File
@@ -0,0 +1,442 @@
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
"time"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/logger"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type sftpDriver struct {
permissions *ssh.Permissions
endpoint string
}
//msgp:ignore sftpMetrics
type sftpMetrics struct{}
var globalSftpMetrics sftpMetrics
func sftpTrace(s *sftp.Request, startTime time.Time, source string, user string, err error) madmin.TraceInfo {
var errStr string
if err != nil {
errStr = err.Error()
}
return madmin.TraceInfo{
TraceType: madmin.TraceFTP,
Time: startTime,
NodeName: globalLocalNodeName,
FuncName: fmt.Sprintf("sftp USER=%s COMMAND=%s PARAM=%s, Source=%s", user, s.Method, s.Filepath, source),
Duration: time.Since(startTime),
Path: s.Filepath,
Error: errStr,
}
}
func (m *sftpMetrics) log(s *sftp.Request, user string) func(err error) {
startTime := time.Now()
source := getSource(2)
return func(err error) {
globalTrace.Publish(sftpTrace(s, startTime, source, user, err))
}
}
// NewSFTPDriver initializes sftp.Handlers implementation of following interfaces
//
// - sftp.Fileread
// - sftp.Filewrite
// - sftp.Filelist
// - sftp.Filecmd
func NewSFTPDriver(perms *ssh.Permissions) sftp.Handlers {
handler := &sftpDriver{endpoint: fmt.Sprintf("127.0.0.1:%s", globalMinioPort), permissions: perms}
return sftp.Handlers{
FileGet: handler,
FilePut: handler,
FileCmd: handler,
FileList: handler,
}
}
func (f *sftpDriver) getMinIOClient() (*minio.Client, error) {
ui, ok := globalIAMSys.GetUser(context.Background(), f.AccessKey())
if !ok && !globalIAMSys.LDAPConfig.Enabled() {
return nil, errNoSuchUser
}
if !ok && globalIAMSys.LDAPConfig.Enabled() {
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(f.AccessKey())
if err != nil {
return nil, err
}
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, false, targetGroups...)
if len(ldapPolicies) == 0 {
return nil, errAuthentication
}
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
if err != nil {
return nil, err
}
claims := make(map[string]interface{})
claims[expClaim] = UTCNow().Add(expiryDur).Unix()
claims[ldapUser] = targetUser
claims[ldapUserN] = f.AccessKey()
cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey)
if err != nil {
return nil, err
}
// Set the parent of the temporary access key, this is useful
// in obtaining service accounts by this cred.
cred.ParentUser = targetUser
// Set this value to LDAP groups, LDAP user can be part
// of large number of groups
cred.Groups = targetGroups
// Set the newly generated credentials, policyName is empty on purpose
// LDAP policies are applied automatically using their ldapUser, ldapGroups
// mapping.
updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "")
if err != nil {
return nil, err
}
// Call hook for site replication.
logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{
Type: madmin.SRIAMItemSTSAcc,
STSCredential: &madmin.SRSTSCredential{
AccessKey: cred.AccessKey,
SecretKey: cred.SecretKey,
SessionToken: cred.SessionToken,
ParentUser: cred.ParentUser,
},
UpdatedAt: updatedAt,
}))
return minio.New(f.endpoint, &minio.Options{
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
Secure: globalIsTLS,
Transport: globalRemoteTargetTransport,
})
}
// ok == true - at this point
if ui.Credentials.IsTemp() {
// Temporary credentials are not allowed.
return nil, errAuthentication
}
return minio.New(f.endpoint, &minio.Options{
Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""),
Secure: globalIsTLS,
Transport: globalRemoteTargetTransport,
})
}
func (f *sftpDriver) AccessKey() string {
return f.permissions.CriticalOptions["accessKey"]
}
func (f *sftpDriver) Fileread(r *sftp.Request) (ra io.ReaderAt, err error) {
stopFn := globalSftpMetrics.log(r, f.AccessKey())
defer stopFn(err)
flags := r.Pflags()
if !flags.Read {
// sanity check
return nil, os.ErrInvalid
}
bucket, object := path2BucketObject(r.Filepath)
if bucket == "" {
return nil, errors.New("bucket name cannot be empty")
}
clnt, err := f.getMinIOClient()
if err != nil {
return nil, err
}
obj, err := clnt.GetObject(context.Background(), bucket, object, minio.GetObjectOptions{})
if err != nil {
return nil, err
}
_, err = obj.Stat()
if err != nil {
return nil, err
}
return obj, nil
}
type writerAt struct {
w *io.PipeWriter
wg *sync.WaitGroup
}
func (w *writerAt) Close() error {
err := w.w.Close()
w.wg.Wait()
return err
}
func (w *writerAt) WriteAt(b []byte, offset int64) (n int, err error) {
return w.w.Write(b)
}
func (f *sftpDriver) Filewrite(r *sftp.Request) (w io.WriterAt, err error) {
stopFn := globalSftpMetrics.log(r, f.AccessKey())
defer stopFn(err)
flags := r.Pflags()
if !flags.Write {
// sanity check
return nil, os.ErrInvalid
}
bucket, object := path2BucketObject(r.Filepath)
if bucket == "" {
return nil, errors.New("bucket name cannot be empty")
}
clnt, err := f.getMinIOClient()
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
wa := &writerAt{w: pw, wg: &sync.WaitGroup{}}
wa.wg.Add(1)
go func() {
_, err := clnt.PutObject(r.Context(), bucket, object, pr, -1, minio.PutObjectOptions{SendContentMd5: true})
pr.CloseWithError(err)
wa.wg.Done()
}()
return wa, nil
}
func (f *sftpDriver) Filecmd(r *sftp.Request) (err error) {
stopFn := globalSftpMetrics.log(r, f.AccessKey())
defer stopFn(err)
clnt, err := f.getMinIOClient()
if err != nil {
return err
}
switch r.Method {
case "Setstat", "Rename", "Link", "Symlink":
return NotImplemented{}
case "Rmdir":
bucket, prefix := path2BucketObject(r.Filepath)
if bucket == "" {
return errors.New("deleting all buckets not allowed")
}
cctx, cancel := context.WithCancel(context.Background())
defer cancel()
objectsCh := make(chan minio.ObjectInfo)
// Send object names that are needed to be removed to objectsCh
go func() {
defer close(objectsCh)
opts := minio.ListObjectsOptions{Prefix: prefix, Recursive: true}
for object := range clnt.ListObjects(cctx, bucket, opts) {
if object.Err != nil {
return
}
objectsCh <- object
}
}()
// Call RemoveObjects API
for err := range clnt.RemoveObjects(context.Background(), bucket, objectsCh, minio.RemoveObjectsOptions{}) {
if err.Err != nil {
return err.Err
}
}
case "Remove":
bucket, object := path2BucketObject(r.Filepath)
if bucket == "" {
return errors.New("bucket name cannot be empty")
}
return clnt.RemoveObject(context.Background(), bucket, object, minio.RemoveObjectOptions{})
case "Mkdir":
bucket, prefix := path2BucketObject(r.Filepath)
if bucket == "" {
return errors.New("bucket name cannot be empty")
}
dirPath := buildMinioDir(prefix)
_, err = clnt.PutObject(context.Background(), bucket, dirPath, bytes.NewReader([]byte("")), 0,
// Always send Content-MD5 to succeed with bucket with
// locking enabled. There is no performance hit since
// this is always an empty object
minio.PutObjectOptions{SendContentMd5: true},
)
return err
}
return NotImplemented{}
}
type listerAt []os.FileInfo
// Modeled after strings.Reader's ReadAt() implementation
func (f listerAt) ListAt(ls []os.FileInfo, offset int64) (int, error) {
var n int
if offset >= int64(len(f)) {
return 0, io.EOF
}
n = copy(ls, f[offset:])
if n < len(ls) {
return n, io.EOF
}
return n, nil
}
func (f *sftpDriver) Filelist(r *sftp.Request) (la sftp.ListerAt, err error) {
stopFn := globalSftpMetrics.log(r, f.AccessKey())
defer stopFn(err)
clnt, err := f.getMinIOClient()
if err != nil {
return nil, err
}
switch r.Method {
case "List":
var files []os.FileInfo
bucket, prefix := path2BucketObject(r.Filepath)
if bucket == "" {
buckets, err := clnt.ListBuckets(r.Context())
if err != nil {
return nil, err
}
for _, bucket := range buckets {
files = append(files, &minioFileInfo{
p: bucket.Name,
info: minio.ObjectInfo{Key: bucket.Name, LastModified: bucket.CreationDate},
isDir: true,
})
}
return listerAt(files), nil
}
prefix = retainSlash(prefix)
for object := range clnt.ListObjects(r.Context(), bucket, minio.ListObjectsOptions{
Prefix: prefix,
Recursive: false,
}) {
if object.Err != nil {
return nil, object.Err
}
if object.Key == prefix {
continue
}
isDir := strings.HasSuffix(object.Key, SlashSeparator)
files = append(files, &minioFileInfo{
p: pathClean(strings.TrimPrefix(object.Key, prefix)),
info: object,
isDir: isDir,
})
}
return listerAt(files), nil
case "Stat":
if r.Filepath == SlashSeparator {
return listerAt{&minioFileInfo{
p: r.Filepath,
isDir: true,
}}, nil
}
bucket, object := path2BucketObject(r.Filepath)
if bucket == "" {
return nil, errors.New("bucket name cannot be empty")
}
if object == "" {
ok, err := clnt.BucketExists(context.Background(), bucket)
if err != nil {
return nil, err
}
if !ok {
return nil, os.ErrNotExist
}
return listerAt{&minioFileInfo{
p: pathClean(bucket),
info: minio.ObjectInfo{Key: bucket},
isDir: true,
}}, nil
}
objInfo, err := clnt.StatObject(context.Background(), bucket, object, minio.StatObjectOptions{})
if err != nil {
if minio.ToErrorResponse(err).Code == "NoSuchKey" {
// dummy return to satisfy LIST (stat -> list) behavior.
return listerAt{&minioFileInfo{
p: pathClean(object),
info: minio.ObjectInfo{Key: object},
isDir: true,
}}, nil
}
return nil, err
}
isDir := strings.HasSuffix(objInfo.Key, SlashSeparator)
return listerAt{&minioFileInfo{
p: pathClean(object),
info: objInfo,
isDir: isDir,
}}, nil
}
return nil, NotImplemented{}
}
+3
View File
@@ -112,3 +112,6 @@ var errIAMNotInitialized = errors.New("IAM sub-system is being initialized, plea
// error returned when upload id not found
var errUploadIDNotFound = errors.New("Specified Upload ID is not found")
// error returned when PartNumber is greater than the maximum allowed 10000 parts
var errInvalidMaxParts = errors.New("Part number is greater than the maximum allowed 10000 parts")
+2 -1
View File
@@ -1583,8 +1583,9 @@ func (x *xlMetaV2) AddVersion(fi FileInfo) error {
if len(k) > len(ReservedMetadataPrefixLower) && strings.EqualFold(k[:len(ReservedMetadataPrefixLower)], ReservedMetadataPrefixLower) {
// Skip tierFVID, tierFVMarker keys; it's used
// only for creating free-version.
// Skip xMinIOHealing, it's used only in RenameData
switch k {
case tierFVIDKey, tierFVMarkerKey:
case tierFVIDKey, tierFVMarkerKey, xMinIOHealing:
continue
}
+48
View File
@@ -1010,3 +1010,51 @@ func Test_mergeXLV2Versions2(t *testing.T) {
})
}
}
func TestXMinIOHealingSkip(t *testing.T) {
xl := xlMetaV2{}
failOnErr := func(err error) {
t.Helper()
if err != nil {
t.Fatalf("Test failed with %v", err)
}
}
fi := FileInfo{
Volume: "volume",
Name: "object-name",
VersionID: "756100c6-b393-4981-928a-d49bbc164741",
IsLatest: true,
Deleted: false,
ModTime: time.Now(),
Size: 1 << 10,
Mode: 0,
Erasure: ErasureInfo{
Algorithm: ReedSolomon.String(),
DataBlocks: 4,
ParityBlocks: 2,
BlockSize: 10000,
Index: 1,
Distribution: []int{1, 2, 3, 4, 5, 6, 7, 8},
Checksums: []ChecksumInfo{{
PartNumber: 1,
Algorithm: HighwayHash256S,
Hash: nil,
}},
},
NumVersions: 1,
}
fi.SetHealing()
failOnErr(xl.AddVersion(fi))
var err error
fi, err = xl.ToFileInfo(fi.Volume, fi.Name, fi.VersionID, false)
if err != nil {
t.Fatalf("xl.ToFileInfo failed with %v", err)
}
if fi.Healing() {
t.Fatal("Expected fi.Healing to be false")
}
}
+19 -32
View File
@@ -365,10 +365,10 @@ func (s *xlStorage) Healing() *healingTracker {
if err != nil {
return nil
}
var h healingTracker
h := newHealingTracker()
_, err = h.UnmarshalMsg(b)
logger.LogIf(GlobalContext, err)
return &h
return h
}
// checkODirectDiskSupport asks the disk to write some data
@@ -435,11 +435,19 @@ func (s *xlStorage) readMetadata(ctx context.Context, itemPath string) ([]byte,
func (s *xlStorage) NSScanner(ctx context.Context, cache dataUsageCache, updates chan<- dataUsageEntry, scanMode madmin.HealScanMode) (dataUsageCache, error) {
atomic.AddInt32(&s.scanning, 1)
defer atomic.AddInt32(&s.scanning, -1)
var err error
stopFn := globalScannerMetrics.log(scannerMetricScanBucketDrive, s.diskPath, cache.Info.Name)
defer func() {
res := make(map[string]string)
if err != nil {
res["err"] = err.Error()
}
stopFn(res)
}()
// Updates must be closed before we return.
defer close(updates)
var lc *lifecycle.Lifecycle
var err error
// Check if the current bucket has a configured lifecycle policy
if globalLifecycleSys != nil {
@@ -1671,35 +1679,10 @@ func (s *xlStorage) openFileDirect(path string, mode int) (f *os.File, err error
}
func (s *xlStorage) openFileSync(filePath string, mode int) (f *os.File, err error) {
// Create top level directories if they don't exist.
// with mode 0777 mkdir honors system umask.
if err = mkdirAll(pathutil.Dir(filePath), 0o777); err != nil {
return nil, osErrToFileErr(err)
}
w, err := OpenFile(filePath, mode|writeMode, 0o666)
if err != nil {
// File path cannot be verified since one of the parents is a file.
switch {
case isSysErrIsDir(err):
return nil, errIsNotRegular
case osIsPermission(err):
return nil, errFileAccessDenied
case isSysErrNotDir(err):
return nil, errFileAccessDenied
case isSysErrIO(err):
return nil, errFaultyDisk
case isSysErrTooManyFiles(err):
return nil, errTooManyOpenFiles
default:
return nil, err
}
}
return w, nil
return s.openFile(filePath, mode|writeMode)
}
func (s *xlStorage) openFileNoSync(filePath string, mode int) (f *os.File, err error) {
func (s *xlStorage) openFile(filePath string, mode int) (f *os.File, err error) {
// Create top level directories if they don't exist.
// with mode 0777 mkdir honors system umask.
if err = mkdirAll(pathutil.Dir(filePath), 0o777); err != nil {
@@ -1950,7 +1933,7 @@ func (s *xlStorage) writeAll(ctx context.Context, volume string, path string, b
}
w, err = s.openFileSync(filePath, flags)
} else {
w, err = s.openFileNoSync(filePath, flags)
w, err = s.openFile(filePath, flags)
}
if err != nil {
return err
@@ -2357,7 +2340,11 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
// suspended or disabled on this bucket. RenameData will replace
// the 'null' version. We add a free-version to track its tiered
// content for asynchronous deletion.
if fi.VersionID == "" && !fi.IsRestoreObjReq() {
//
// Note: RestoreObject and HealObject requests don't end up replacing the
// null version and therefore don't require the free-version to track
// anything
if fi.VersionID == "" && !fi.IsRestoreObjReq() && !fi.Healing() {
// Note: Restore object request reuses PutObject/Multipart
// upload to copy back its data from the remote tier. This
// doesn't replace the existing version, so we don't need to add
+169
View File
@@ -0,0 +1,169 @@
# MinIO FTP/SFTP Server
MinIO natively supports FTP/SFTP protocol, this allows any ftp/sftp client to upload and download files.
Currently supported `FTP/SFTP` operations are as follows:
| ftp-client commands | supported |
|:-------------------:|:----------|
| get | yes |
| put | yes |
| ls | yes |
| mkdir | yes |
| rmdir | yes |
| delete | yes |
| append | no |
| rename | no |
MinIO supports following FTP/SFTP based protocols to access and manage data.
- Secure File Transfer Protocol (SFTP) Defined by the Internet Engineering Task Force (IETF) as an
extended version of SSH 2.0, allowing file transfer over SSH and for use with Transport Layer
Security (TLS) and VPN applications.
- File Transfer Protocol over SSL/TLS (FTPS) Encrypted FTP communication via TLS certificates.
- File Transfer Protocol (FTP) Defined by RFC114 originally, and replaced by RFC765 and RFC959
unencrypted FTP communication (Not-recommended)
## Scope
- All IAM Credentials are allowed access excluding rotating credentials, rotating credentials
are not allowed to login via FTP/SFTP ports, you must use S3 API port for if you are using
rotating credentials.
- Access to bucket(s) and object(s) are governed via IAM policies associated with the incoming
login credentials.
- Allows authentication and access for all
- Built-in IDP users and their respective service accounts
- LDAP/AD users and their respective service accounts
- OpenID/OIDC service accounts
- On versioned buckets, FTP/SFTP only operates on latest objects, if you need to retrieve
an older version you must use an `S3 API client` such as [`mc`](https://github.com/minio/mc).
- All features currently used by your buckets will work as is without any changes
- SSE (Server Side Encryption)
- Replication (Server Side Replication)
## Prerequisites
- It is assumed you have users created and configured with relevant access policies, to start with
use basic "readwrite" canned policy to test all the operations before you finalize on what level
of restrictions are needed for a user.
- No "admin:*" operations are needed for FTP/SFTP access to the bucket(s) and object(s), so you may
skip them for restrictions.
## Usage
Start MinIO in a distributed setup, with 'ftp/sftp' enabled.
```
minio server http://server{1...4}/disk{1...4}
--ftp="address=:8021" --ftp="passive-port-range=30000-40000" \
--sftp="address=:8022" --sftp="ssh-private-key=/home/miniouser/.ssh/id_rsa"
...
...
```
Following example shows connecting via ftp client using `minioadmin` credentials, and list a bucket named `runner`:
```
ftp localhost -P 8021
Connected to localhost.
220 Welcome to MinIO FTP Server
Name (localhost:user): minioadmin
331 User name ok, password required
Password:
230 Password ok, continue
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls runner/
229 Entering Extended Passive Mode (|||39155|)
150 Opening ASCII mode data connection for file list
drwxrwxrwx 1 nobody nobody 0 Jan 1 00:00 chunkdocs/
drwxrwxrwx 1 nobody nobody 0 Jan 1 00:00 testdir/
...
```
Following example shows how to list an object and download it locally via `ftp` client:
```
ftp> ls runner/chunkdocs/metadata
229 Entering Extended Passive Mode (|||44269|)
150 Opening ASCII mode data connection for file list
-rwxrwxrwx 1 nobody nobody 45 Apr 1 06:13 chunkdocs/metadata
226 Closing data connection, sent 75 bytes
ftp> get
(remote-file) runner/chunkdocs/metadata
(local-file) test
local: test remote: runner/chunkdocs/metadata
229 Entering Extended Passive Mode (|||37785|)
150 Data transfer starting 45 bytes
45 3.58 KiB/s
226 Closing data connection, sent 45 bytes
45 bytes received in 00:00 (3.55 KiB/s)
...
```
Following example shows connecting via sftp client using `minioadmin` credentials, and list a bucket named `runner`:
```
sftp -P 8022 minioadmin@localhost
minioadmin@localhost's password:
Connected to localhost.
sftp> ls runner/
chunkdocs testdir
```
Following example shows how to download an object locally via `sftp` client:
```
sftp> get runner/chunkdocs/metadata metadata
Fetching /runner/chunkdocs/metadata to metadata
metadata 100% 226 16.6KB/s 00:00
sftp>
```
## Advanced options
### Change default FTP port
Default port '8021' can be changed via
```
--ftp="address=:3021"
```
### Change FTP passive port range
By default FTP requests OS to give a free port automatically, however you may want to restrict
this to specific ports in certain restricted environments via
```
--ftp="passive-port-range=30000-40000"
```
### Change default SFTP port
Default port '8022' can be changed via
```
--sftp="address=:3022"
```
### TLS (FTP)
Unlike SFTP server, FTP server is insecure by default. To operate under TLS mode, you need to provide certificates via
```
--ftp="tls-private-key=path/to/private.key" --ftp="tls-public-cert=path/to/public.crt"
```
> NOTE: if MinIO distributed setup is already configured to run under TLS, FTP will automatically use the relevant
> certs from the server certificate chain, this is mainly to add simplicity of setup. However if you wish to terminate
> TLS certificates via a different domain for your FTP servers you may choose the above command line options.
@@ -2,7 +2,7 @@ version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: quay.io/minio/minio:RELEASE.2023-03-24T21-41-23Z
image: quay.io/minio/minio:RELEASE.2023-04-13T03-08-07Z
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
expose:
- "9000"
+26 -21
View File
@@ -32,7 +32,7 @@ require (
github.com/hashicorp/golang-lru v0.5.4
github.com/inconshreveable/mousetrap v1.1.0
github.com/json-iterator/go v1.1.12
github.com/klauspost/compress v1.16.3
github.com/klauspost/compress v1.16.4
github.com/klauspost/cpuid/v2 v2.2.4
github.com/klauspost/filepathx v1.1.1
github.com/klauspost/pgzip v1.2.5
@@ -40,15 +40,15 @@ require (
github.com/klauspost/reedsolomon v1.11.7
github.com/lib/pq v1.10.7
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/miekg/dns v1.1.52
github.com/miekg/dns v1.1.53
github.com/minio/cli v1.24.2
github.com/minio/console v0.26.3
github.com/minio/csvparser v1.0.0
github.com/minio/dperf v0.4.2
github.com/minio/highwayhash v1.0.2
github.com/minio/kes-go v0.1.0
github.com/minio/madmin-go/v2 v2.0.18
github.com/minio/minio-go/v7 v7.0.50
github.com/minio/madmin-go/v2 v2.0.20
github.com/minio/minio-go/v7 v7.0.52
github.com/minio/mux v1.9.0
github.com/minio/pkg v1.6.6-0.20230330040824-5db111e5f63c
github.com/minio/selfupdate v0.6.0
@@ -66,6 +66,7 @@ require (
github.com/philhofer/fwd v1.1.2
github.com/pierrec/lz4 v2.6.1+incompatible
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.10.1
github.com/prometheus/client_golang v1.14.0
github.com/prometheus/client_model v0.3.0
github.com/prometheus/common v0.42.0
@@ -74,7 +75,7 @@ require (
github.com/rs/cors v1.8.3
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417
github.com/secure-io/sio-go v0.3.1
github.com/shirou/gopsutil/v3 v3.23.2
github.com/shirou/gopsutil/v3 v3.23.3
github.com/tidwall/gjson v1.14.4
github.com/tinylib/msgp v1.1.8
github.com/valyala/bytebufferpool v1.0.0
@@ -84,11 +85,12 @@ require (
go.etcd.io/etcd/client/v3 v3.5.7
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.7.0
golang.org/x/oauth2 v0.6.0
golang.org/x/sys v0.6.0
goftp.io/server/v2 v2.0.0
golang.org/x/crypto v0.8.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sys v0.7.0
golang.org/x/time v0.3.0
google.golang.org/api v0.114.0
google.golang.org/api v0.117.0
gopkg.in/yaml.v2 v2.4.0
)
@@ -98,7 +100,7 @@ require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.19.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
cloud.google.com/go/iam v1.0.0 // indirect
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/apache/thrift v0.18.1 // indirect
@@ -138,7 +140,8 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 // indirect
github.com/google/s2a-go v0.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
@@ -156,6 +159,7 @@ require (
github.com/jessevdk/go-flags v1.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/juju/ratelimit v1.0.2 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
@@ -173,7 +177,7 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/minio/colorjson v1.0.4 // indirect
github.com/minio/filepath v1.0.0 // indirect
github.com/minio/mc v0.0.0-20230323200304-81453d7c8fcc // indirect
github.com/minio/mc v0.0.0-20230411170328-83336dfab325 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/websocket v1.6.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -198,10 +202,11 @@ require (
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/prometheus/prom2json v1.3.2 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rivo/tview v0.0.0-20230325211341-281d14d896d7 // indirect
github.com/rivo/tview v0.0.0-20230406072732-e22ce9588bb4 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rjeczalik/notify v0.9.3 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/shoenig/go-m1cpu v0.1.5 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
@@ -211,18 +216,18 @@ require (
github.com/xdg/stringprep v1.0.3 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
go.mongodb.org/mongo-driver v1.11.4 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.8.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/h2non/filetype.v1 v1.0.5 // indirect
+65 -41
View File
@@ -112,8 +112,8 @@ cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J
cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
cloud.google.com/go/iam v1.0.0 h1:hlQJMovyJJwYjZcTohUH4o1L8Z8kYz+E+W/zktiLCBc=
cloud.google.com/go/iam v1.0.0/go.mod h1:ikbQ4f1r91wTmBmmOtBCOtuEOei6taatNXytzB7Cxew=
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
@@ -539,9 +539,11 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk=
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.0 h1:3Qm0liEiCErViKERO2Su5wp+9PfMRiuS6XB5FvpKnYQ=
github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -637,6 +639,7 @@ github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1R
github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@@ -660,8 +663,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
@@ -679,6 +682,7 @@ github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -755,8 +759,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/minio/cli v1.24.2 h1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=
github.com/minio/cli v1.24.2/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
github.com/minio/colorjson v1.0.4 h1:sNJYTb2uNswdqmGARg9wrogCX8+GRZzEacYbJT86e00=
@@ -774,15 +778,18 @@ github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLT
github.com/minio/kes-go v0.1.0 h1:h201DyOYP5sTqajkxFGxmXz/kPbT8HQNX1uh3Yx2PFc=
github.com/minio/kes-go v0.1.0/go.mod h1:VorHLaIYis9/MxAHAtXN4d8PUMNKhIxTIlvFt0hBOEo=
github.com/minio/madmin-go v1.6.6/go.mod h1:ATvkBOLiP3av4D++2v1UEHC/QzsGtgXD5kYvvRYzdKs=
github.com/minio/madmin-go/v2 v2.0.18 h1:5kME44CIL7RlNm99hg6EU0K5AiIX0RtVHFu0nMEWmNE=
github.com/minio/madmin-go/v2 v2.0.18/go.mod h1:8bL1RMNkblIENFSgGYjeHrzUx9PxROb7OqfNuMU9ivE=
github.com/minio/mc v0.0.0-20230323200304-81453d7c8fcc h1:hhSnDwYb8clwMC/BZkhpoqicaPAlSIBqieqRZwHHg9E=
github.com/minio/mc v0.0.0-20230323200304-81453d7c8fcc/go.mod h1:JbACfcKKvBZF4Pvipki9rtNDXBPPBCQtkVcs6GwdniQ=
github.com/minio/madmin-go/v2 v2.0.19 h1:XznxdMVCTyr0A88JrZFhdxWY8KLfJcrs0TTmFiE9cc8=
github.com/minio/madmin-go/v2 v2.0.19/go.mod h1:8bL1RMNkblIENFSgGYjeHrzUx9PxROb7OqfNuMU9ivE=
github.com/minio/madmin-go/v2 v2.0.20 h1:EO2IIQsnaVM3ki/ONcW0Ry4UpDn5aa+/S1kLkHuDBs8=
github.com/minio/madmin-go/v2 v2.0.20/go.mod h1:8bL1RMNkblIENFSgGYjeHrzUx9PxROb7OqfNuMU9ivE=
github.com/minio/mc v0.0.0-20230411170328-83336dfab325 h1:da5I7G0Va6UnqoxzPHus7zmqo2pEQnltI09/XKNvHP4=
github.com/minio/mc v0.0.0-20230411170328-83336dfab325/go.mod h1:v9AeUV4eaMpKCuNz0tk/MSGxvNs3duYvWUIxzpacxtc=
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/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/minio-go/v7 v7.0.41/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/minio-go/v7 v7.0.50 h1:4IL4V8m/kI90ZL6GupCARZVrBv8/XrcKcJhaJ3iz68k=
github.com/minio/minio-go/v7 v7.0.50/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU=
github.com/minio/minio-go/v7 v7.0.52 h1:8XhG36F6oKQUDDSuz6dY3rioMzovKjW40W6ANuN0Dps=
github.com/minio/minio-go/v7 v7.0.52/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU=
github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA=
github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
github.com/minio/pkg v1.5.4/go.mod h1:2MOaRFdmFKULD+uOLc3qHLGTQTuxCNPKNPfLBTxC8CA=
@@ -790,6 +797,7 @@ github.com/minio/pkg v1.6.6-0.20230330040824-5db111e5f63c h1:Ukw0+d0T/+9lserJfod
github.com/minio/pkg v1.6.6-0.20230330040824-5db111e5f63c/go.mod h1:0iX1IuJGSCnMvIvrEJauk1GgQSX9JdU6Kh0P3EQRGkI=
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/minio/simdjson-go v0.4.5 h1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A=
@@ -891,6 +899,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
@@ -940,8 +949,8 @@ github.com/rabbitmq/amqp091-go v1.8.0 h1:GBFy5PpLQ5jSVVSYv8ecHGqeX7UTLYR4ItQbDCs
github.com/rabbitmq/amqp091-go v1.8.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/tview v0.0.0-20230325211341-281d14d896d7 h1:8sjyhdBA9HmxHGUeAM/43uSHfyFhFodbxekw2mYhpo0=
github.com/rivo/tview v0.0.0-20230325211341-281d14d896d7/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
github.com/rivo/tview v0.0.0-20230406072732-e22ce9588bb4 h1:zX+lRcFRPX1jn8A11jxT0dEQhkmUM7pec+9NLK8MiTQ=
github.com/rivo/tview v0.0.0-20230406072732-e22ce9588bb4/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -972,8 +981,13 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A=
github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -987,6 +1001,7 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -1084,8 +1099,8 @@ go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y=
go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1104,19 +1119,23 @@ go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
goftp.io/server/v2 v2.0.0 h1:FF8JKXXKDxAeO1uXEZz7G+IZwCDhl19dpVIlDtp3QAg=
goftp.io/server/v2 v2.0.0/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -1136,8 +1155,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1175,8 +1194,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1188,6 +1207,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1220,6 +1240,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@@ -1236,8 +1257,8 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1262,8 +1283,8 @@ golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7Lm
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1355,6 +1376,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1384,16 +1406,17 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1407,8 +1430,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1478,8 +1501,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1537,8 +1560,8 @@ google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaE
google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/api v0.117.0 h1:JsXRperckxXjnPl42ku4+KQRhWFiW6XjcZlOEHoxG8M=
google.golang.org/api v0.117.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1646,8 +1669,8 @@ google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53B
google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55/go.mod h1:45EK0dUbEZ2NHjCeAd2LXmyjAgGUGrpGROgjhC3ADck=
google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5 h1:Kd6tRRHXw8z4TlPlWi+NaK10gsePL6GdZBQChptOLGA=
google.golang.org/genproto v0.0.0-20230327215041-6ac7f18bb9d5/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1714,6 +1737,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/h2non/filetype.v1 v1.0.5 h1:CC1jjJjoEhNVbMhXYalmGBhOBK2V70Q1N850wt/98/Y=
gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
Binary file not shown.
+2 -2
View File
@@ -1,8 +1,8 @@
apiVersion: v1
description: Multi-Cloud Object Storage
name: minio
version: 5.0.7
appVersion: RELEASE.2023-02-10T18-48-39Z
version: 5.0.8
appVersion: RELEASE.2023-04-13T03-08-07Z
keywords:
- minio
- storage
@@ -73,7 +73,9 @@ createUser() {
# set policy for user
if [ ! -z $POLICY -a $POLICY != " " ] ; then
echo "Adding policy '$POLICY' for '$USER'"
set +e ; # policy already attach errors out, allow it.
${MC} admin policy attach myminio $POLICY --user=$USER
set -e
else
echo "User '$USER' has no policy attached."
fi
+49 -49
View File
@@ -3,7 +3,7 @@
Expand the name of the chart.
*/}}
{{- define "minio.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
@@ -12,102 +12,102 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
If release name contains chart name it will be used as a full name.
*/}}
{{- define "minio.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "minio.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Return the appropriate apiVersion for networkpolicy.
*/}}
{{- define "minio.networkPolicy.apiVersion" -}}
{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.Version -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare ">=1.7-0, <1.16-0" .Capabilities.KubeVersion.Version -}}
{{- print "networking.k8s.io/v1beta1" -}}
{{- else if semverCompare "^1.16-0" .Capabilities.KubeVersion.Version -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.Version -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare ">=1.7-0, <1.16-0" .Capabilities.KubeVersion.Version -}}
{{- print "networking.k8s.io/v1beta1" -}}
{{- else if semverCompare "^1.16-0" .Capabilities.KubeVersion.Version -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- end -}}
{{/*
Return the appropriate apiVersion for deployment.
*/}}
{{- define "minio.deployment.apiVersion" -}}
{{- if semverCompare "<1.9-0" .Capabilities.KubeVersion.Version -}}
{{- print "apps/v1beta2" -}}
{{- else -}}
{{- print "apps/v1" -}}
{{- end -}}
{{- if semverCompare "<1.9-0" .Capabilities.KubeVersion.Version -}}
{{- print "apps/v1beta2" -}}
{{- else -}}
{{- print "apps/v1" -}}
{{- end -}}
{{- end -}}
{{/*
Return the appropriate apiVersion for statefulset.
*/}}
{{- define "minio.statefulset.apiVersion" -}}
{{- if semverCompare "<1.16-0" .Capabilities.KubeVersion.Version -}}
{{- print "apps/v1beta2" -}}
{{- else -}}
{{- print "apps/v1" -}}
{{- end -}}
{{- if semverCompare "<1.16-0" .Capabilities.KubeVersion.Version -}}
{{- print "apps/v1beta2" -}}
{{- else -}}
{{- print "apps/v1" -}}
{{- end -}}
{{- end -}}
{{/*
Return the appropriate apiVersion for ingress.
*/}}
{{- define "minio.ingress.apiVersion" -}}
{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "networking.k8s.io/v1beta1" -}}
{{- else -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "networking.k8s.io/v1beta1" -}}
{{- else -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- end -}}
{{/*
Return the appropriate apiVersion for console ingress.
*/}}
{{- define "minio.consoleIngress.apiVersion" -}}
{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "networking.k8s.io/v1beta1" -}}
{{- else -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "networking.k8s.io/v1beta1" -}}
{{- else -}}
{{- print "networking.k8s.io/v1" -}}
{{- end -}}
{{- end -}}
{{/*
Determine secret name.
*/}}
{{- define "minio.secretName" -}}
{{- if .Values.existingSecret -}}
{{- .Values.existingSecret }}
{{- else -}}
{{- include "minio.fullname" . -}}
{{- end -}}
{{- if .Values.existingSecret -}}
{{- .Values.existingSecret }}
{{- else -}}
{{- include "minio.fullname" . -}}
{{- end -}}
{{- end -}}
{{/*
Determine name for scc role and rolebinding
*/}}
{{- define "minio.sccRoleName" -}}
{{- printf "%s-%s" "scc" (include "minio.fullname" .) | trunc 63 | trimSuffix "-" -}}
{{- printf "%s-%s" "scc" (include "minio.fullname" .) | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
+13 -14
View File
@@ -2,7 +2,6 @@ apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "minio.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
@@ -10,24 +9,24 @@ metadata:
heritage: {{ .Release.Service }}
data:
initialize: |-
{{ include (print $.Template.BasePath "/_helper_create_bucket.txt") . | indent 4 }}
{{- include (print $.Template.BasePath "/_helper_create_bucket.txt") . | nindent 4 }}
add-user: |-
{{ include (print $.Template.BasePath "/_helper_create_user.txt") . | indent 4 }}
{{- include (print $.Template.BasePath "/_helper_create_user.txt") . | nindent 4 }}
add-policy: |-
{{ include (print $.Template.BasePath "/_helper_create_policy.txt") . | indent 4 }}
{{- range $idx, $policy := .Values.policies }}
{{- include (print $.Template.BasePath "/_helper_create_policy.txt") . | nindent 4 }}
{{- range $idx, $policy := .Values.policies }}
# Policy: {{ $policy.name }}
policy_{{ $idx }}.json: |-
{{ include (print $.Template.BasePath "/_helper_policy.tpl") . | indent 4 }}
{{ end }}
{{- range $idx, $svc := .Values.svcaccts }}
{{- if $svc.policy }}
{{- include (print $.Template.BasePath "/_helper_policy.tpl") . | nindent 4 }}
{{ end }}
{{- range $idx, $svc := .Values.svcaccts }}
{{- if $svc.policy }}
# SVC: {{ $svc.accessKey }}
svc_policy_{{ $idx }}.json: |-
{{ include (print $.Template.BasePath "/_helper_policy.tpl") .policy | indent 4 }}
{{- end }}
{{ end }}
{{- include (print $.Template.BasePath "/_helper_policy.tpl") .policy | nindent 4 }}
{{- end }}
{{- end }}
add-svcacct: |-
{{ include (print $.Template.BasePath "/_helper_create_svcacct.txt") . | indent 4 }}
{{- include (print $.Template.BasePath "/_helper_create_svcacct.txt") . | nindent 4 }}
custom-command: |-
{{ include (print $.Template.BasePath "/_helper_custom_command.txt") . | indent 4 }}
{{- include (print $.Template.BasePath "/_helper_custom_command.txt") . | nindent 4 }}
+10 -13
View File
@@ -6,25 +6,22 @@ apiVersion: {{ template "minio.consoleIngress.apiVersion" . }}
kind: Ingress
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- with .Values.consoleIngress.labels }}
{{ toYaml . | indent 4 }}
{{- end }}
{{- with .Values.consoleIngress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
{{- with .Values.consoleIngress.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.consoleIngress.annotations }}
annotations: {{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.consoleIngress.ingressClassName }}
{{- if .Values.consoleIngress.ingressClassName }}
ingressClassName: {{ .Values.consoleIngress.ingressClassName }}
{{- end }}
{{- if .Values.consoleIngress.tls }}
{{- end }}
{{- if .Values.consoleIngress.tls }}
tls:
{{- range .Values.consoleIngress.tls }}
- hosts:
@@ -33,7 +30,7 @@ spec:
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
{{- end }}
rules:
{{- range .Values.consoleIngress.hosts }}
- http:
+16 -21
View File
@@ -1,48 +1,43 @@
{{ $scheme := "http" }}
{{- if .Values.tls.enabled }}
{{ $scheme = "https" }}
{{ end }}
{{ $scheme := .Values.tls.enabled | ternary "https" "http" }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "minio.fullname" . }}-console
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.consoleService.annotations }}
annotations:
{{ toYaml .Values.consoleService.annotations | indent 4 }}
{{- end }}
{{- if .Values.consoleService.annotations }}
annotations: {{- toYaml .Values.consoleService.annotations | nindent 4 }}
{{- end }}
spec:
{{- if (or (eq .Values.consoleService.type "ClusterIP" "") (empty .Values.consoleService.type)) }}
{{- if (or (eq .Values.consoleService.type "ClusterIP" "") (empty .Values.consoleService.type)) }}
type: ClusterIP
{{- if not (empty .Values.consoleService.clusterIP) }}
clusterIP: {{ .Values.consoleService.clusterIP }}
{{end}}
{{- else if eq .Values.consoleService.type "LoadBalancer" }}
{{- end }}
{{- else if eq .Values.consoleService.type "LoadBalancer" }}
type: {{ .Values.consoleService.type }}
loadBalancerIP: {{ default "" .Values.consoleService.loadBalancerIP }}
{{- else }}
{{- else }}
type: {{ .Values.consoleService.type }}
{{- end }}
{{- end }}
ports:
- name: {{ $scheme }}
port: {{ .Values.consoleService.port }}
protocol: TCP
{{- if (and (eq .Values.consoleService.type "NodePort") ( .Values.consoleService.nodePort)) }}
{{- if (and (eq .Values.consoleService.type "NodePort") ( .Values.consoleService.nodePort)) }}
nodePort: {{ .Values.consoleService.nodePort }}
{{- else }}
{{- else }}
targetPort: {{ .Values.minioConsolePort }}
{{- end}}
{{- if .Values.consoleService.externalIPs }}
{{- end }}
{{- if .Values.consoleService.externalIPs }}
externalIPs:
{{- range $i , $ip := .Values.consoleService.externalIPs }}
{{- range $i , $ip := .Values.consoleService.externalIPs }}
- {{ $ip }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
selector:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
+35 -44
View File
@@ -1,26 +1,21 @@
{{- if eq .Values.mode "standalone" }}
{{ $scheme := "http" }}
{{- if .Values.tls.enabled }}
{{ $scheme = "https" }}
{{ end }}
{{ $scheme := .Values.tls.enabled | ternary "https" "http" }}
{{ $bucketRoot := or ($.Values.bucketRoot) ($.Values.mountPath) }}
apiVersion: {{ template "minio.deployment.apiVersion" . }}
kind: Deployment
metadata:
name: {{ template "minio.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.additionalLabels }}
{{ toYaml .Values.additionalLabels | trimSuffix "\n" | indent 4 }}
{{- end }}
{{- if .Values.additionalAnnotations }}
annotations:
{{ toYaml .Values.additionalAnnotations | trimSuffix "\n" | indent 4 }}
{{- end }}
{{- if .Values.additionalLabels }}
{{- toYaml .Values.additionalLabels | nindent 4 }}
{{- end }}
{{- if .Values.additionalAnnotations }}
annotations: {{- toYaml .Values.additionalAnnotations | nindent 4 }}
{{- end }}
spec:
strategy:
type: {{ .Values.DeploymentUpdate.type }}
@@ -28,7 +23,7 @@ spec:
rollingUpdate:
maxSurge: {{ .Values.DeploymentUpdate.maxSurge }}
maxUnavailable: {{ .Values.DeploymentUpdate.maxUnavailable }}
{{- end}}
{{- end }}
replicas: 1
selector:
matchLabels:
@@ -40,17 +35,17 @@ spec:
labels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
{{- if .Values.podLabels }}
{{ toYaml .Values.podLabels | indent 8 }}
{{- end }}
{{- if .Values.podLabels }}
{{- toYaml .Values.podLabels | nindent 8 }}
{{- end }}
annotations:
{{- if not .Values.ignoreChartChecksums }}
{{- if not .Values.ignoreChartChecksums }}
checksum/secrets: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.podAnnotations }}
{{ toYaml .Values.podAnnotations | trimSuffix "\n" | indent 8 }}
{{- end }}
{{- end }}
{{- if .Values.podAnnotations }}
{{- toYaml .Values.podAnnotations | trimSuffix "\n" | indent 8 }}
{{- end }}
spec:
{{- if .Values.priorityClassName }}
priorityClassName: "{{ .Values.priorityClassName }}"
@@ -58,7 +53,7 @@ spec:
{{- if .Values.runtimeClassName }}
runtimeClassName: "{{ .Values.runtimeClassName }}"
{{- end }}
{{- if and .Values.securityContext.enabled .Values.persistence.enabled }}
{{- if and .Values.securityContext.enabled .Values.persistence.enabled }}
securityContext:
runAsUser: {{ .Values.securityContext.runAsUser }}
runAsGroup: {{ .Values.securityContext.runAsGroup }}
@@ -66,10 +61,10 @@ spec:
{{- if and (ge .Capabilities.KubeVersion.Major "1") (ge .Capabilities.KubeVersion.Minor "20") }}
fsGroupChangePolicy: {{ .Values.securityContext.fsGroupChangePolicy }}
{{- end }}
{{- end }}
{{ if .Values.serviceAccount.create }}
{{- end }}
{{ if .Values.serviceAccount.create }}
serviceAccountName: {{ .Values.serviceAccount.name }}
{{- end }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
@@ -114,11 +109,11 @@ spec:
{{- if .Values.extraSecret }}
- name: MINIO_CONFIG_ENV_FILE
value: "/tmp/minio-config-env/config.env"
{{- end}}
{{- end }}
{{- if .Values.metrics.serviceMonitor.public }}
- name: MINIO_PROMETHEUS_AUTH_TYPE
value: "public"
{{- end}}
{{- end }}
{{- if .Values.oidc.enabled }}
- name: MINIO_IDENTITY_OPENID_CONFIG_URL
value: {{ .Values.oidc.configUrl }}
@@ -136,7 +131,7 @@ spec:
value: {{ .Values.oidc.redirectUri }}
- name: MINIO_IDENTITY_OPENID_COMMENT
value: {{ .Values.oidc.comment }}
{{- end}}
{{- end }}
{{- if .Values.etcd.endpoints }}
- name: MINIO_ETCD_ENDPOINTS
value: {{ join "," .Values.etcd.endpoints | quote }}
@@ -160,9 +155,8 @@ spec:
{{- range $key, $val := .Values.environment }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end}}
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- end }}
resources: {{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.extraContainers }}
{{- if eq (typeOf .) "string" }}
{{- tpl . $ | nindent 8 }}
@@ -170,19 +164,16 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
{{- end }}
{{- include "minio.imagePullSecrets" . | indent 6 }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
{{- end }}
{{- include "minio.imagePullSecrets" . | indent 6 }}
{{- with .Values.affinity }}
affinity: {{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations: {{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: export
{{- if .Values.persistence.enabled }}
+10 -13
View File
@@ -6,25 +6,22 @@ apiVersion: {{ template "minio.ingress.apiVersion" . }}
kind: Ingress
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- with .Values.ingress.labels }}
{{ toYaml . | indent 4 }}
{{- end }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
{{- with .Values.ingress.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.ingress.annotations }}
annotations: {{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.ingressClassName }}
{{- if .Values.ingress.ingressClassName }}
ingressClassName: {{ .Values.ingress.ingressClassName }}
{{- end }}
{{- if .Values.ingress.tls }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
@@ -33,7 +30,7 @@ spec:
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- http:
-1
View File
@@ -3,7 +3,6 @@ kind: NetworkPolicy
apiVersion: {{ template "minio.networkPolicy.apiVersion" . }}
metadata:
name: {{ template "minio.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
@@ -7,7 +7,6 @@ apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: minio
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
spec:
+16 -26
View File
@@ -3,7 +3,6 @@ apiVersion: batch/v1
kind: Job
metadata:
name: {{ template "minio.fullname" . }}-post-job
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}-post-job
chart: {{ template "minio.chart" . }}
@@ -25,23 +24,19 @@ spec:
{{- toYaml .Values.podLabels | nindent 8 }}
{{- end }}
{{- if .Values.postJob.podAnnotations }}
annotations:
{{- toYaml .Values.postJob.podAnnotations | nindent 8 }}
annotations: {{- toYaml .Values.postJob.podAnnotations | nindent 8 }}
{{- end }}
spec:
restartPolicy: OnFailure
{{- include "minio.imagePullSecrets" . | nindent 6 }}
{{- include "minio.imagePullSecrets" . | indent 6 }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{- toYaml .Values.postJob.nodeSelector | nindent 8 }}
nodeSelector: {{- toYaml .Values.postJob.nodeSelector | nindent 8 }}
{{- end }}
{{- with .Values.postJob.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
affinity: {{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.postJob.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
tolerations: {{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.postJob.securityContext.enabled }}
securityContext:
@@ -82,7 +77,7 @@ spec:
items:
- key: {{ .Values.tls.publicCrt }}
path: CAs/public.crt
{{ end }}
{{- end }}
containers:
{{- if .Values.buckets }}
- name: minio-make-bucket
@@ -110,9 +105,8 @@ spec:
{{- if .Values.tls.enabled }}
- name: cert-secret-volume-mc
mountPath: {{ .Values.configPathmc }}certs
{{ end }}
resources:
{{- toYaml .Values.makeBucketJob.resources | nindent 12 }}
{{- end }}
resources: {{- toYaml .Values.makeBucketJob.resources | nindent 12 }}
{{- end }}
{{- if .Values.users }}
- name: minio-make-user
@@ -140,9 +134,8 @@ spec:
{{- if .Values.tls.enabled }}
- name: cert-secret-volume-mc
mountPath: {{ .Values.configPathmc }}certs
{{ end }}
resources:
{{- toYaml .Values.makeUserJob.resources | nindent 12 }}
{{- end }}
resources: {{- toYaml .Values.makeUserJob.resources | nindent 12 }}
{{- end }}
{{- if .Values.policies }}
- name: minio-make-policy
@@ -170,9 +163,8 @@ spec:
{{- if .Values.tls.enabled }}
- name: cert-secret-volume-mc
mountPath: {{ .Values.configPathmc }}certs
{{ end }}
resources:
{{- toYaml .Values.makePolicyJob.resources | nindent 12 }}
{{- end }}
resources: {{- toYaml .Values.makePolicyJob.resources | nindent 12 }}
{{- end }}
{{- if .Values.customCommands }}
- name: minio-custom-command
@@ -200,9 +192,8 @@ spec:
{{- if .Values.tls.enabled }}
- name: cert-secret-volume-mc
mountPath: {{ .Values.configPathmc }}certs
{{ end }}
resources:
{{- toYaml .Values.customCommandJob.resources | nindent 12 }}
{{- end }}
resources: {{- toYaml .Values.customCommandJob.resources | nindent 12 }}
{{- end }}
{{- if .Values.svcaccts }}
- name: minio-make-svcacct
@@ -230,8 +221,7 @@ spec:
{{- if .Values.tls.enabled }}
- name: cert-secret-volume-mc
mountPath: {{ .Values.configPathmc }}certs
{{ end }}
resources:
{{- toYaml .Values.makeServiceAccountJob.resources | nindent 12 }}
{{- end }}
resources: {{- toYaml .Values.makeServiceAccountJob.resources | nindent 12 }}
{{- end }}
{{- end }}
+10 -13
View File
@@ -4,32 +4,29 @@ apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ template "minio.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.persistence.annotations }}
annotations:
{{ toYaml .Values.persistence.annotations | trimSuffix "\n" | indent 4 }}
{{- end }}
{{- if .Values.persistence.annotations }}
annotations: {{- toYaml .Values.persistence.annotations | nindent 4 }}
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- if .Values.persistence.storageClass }}
{{- if (eq "-" .Values.persistence.storageClass) }}
{{- if .Values.persistence.storageClass }}
{{- if (eq "-" .Values.persistence.storageClass) }}
storageClassName: ""
{{- else }}
{{- else }}
storageClassName: "{{ .Values.persistence.storageClass }}"
{{- end }}
{{- end }}
{{- if .Values.persistence.VolumeName }}
{{- end }}
{{- end }}
{{- if .Values.persistence.VolumeName }}
volumeName: "{{ .Values.persistence.VolumeName }}"
{{- end }}
{{- end }}
{{- end }}
{{- end }}
-1
View File
@@ -3,7 +3,6 @@ apiVersion: v1
kind: Secret
metadata:
name: {{ template "minio.secretName" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
+16 -21
View File
@@ -1,49 +1,44 @@
{{ $scheme := "http" }}
{{- if .Values.tls.enabled }}
{{ $scheme = "https" }}
{{ end }}
{{ $scheme := .Values.tls.enabled | ternary "https" "http" }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "minio.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
monitoring: "true"
{{- if .Values.service.annotations }}
annotations:
{{ toYaml .Values.service.annotations | indent 4 }}
{{- end }}
{{- if .Values.service.annotations }}
annotations: {{- toYaml .Values.service.annotations | nindent 4 }}
{{- end }}
spec:
{{- if (or (eq .Values.service.type "ClusterIP" "") (empty .Values.service.type)) }}
{{- if (or (eq .Values.service.type "ClusterIP" "") (empty .Values.service.type)) }}
type: ClusterIP
{{- if not (empty .Values.service.clusterIP) }}
clusterIP: {{ .Values.service.clusterIP }}
{{end}}
{{- else if eq .Values.service.type "LoadBalancer" }}
{{- end }}
{{- else if eq .Values.service.type "LoadBalancer" }}
type: {{ .Values.service.type }}
loadBalancerIP: {{ default "" .Values.service.loadBalancerIP }}
{{- else }}
{{- else }}
type: {{ .Values.service.type }}
{{- end }}
{{- end }}
ports:
- name: {{ $scheme }}
port: {{ .Values.service.port }}
protocol: TCP
{{- if (and (eq .Values.service.type "NodePort") ( .Values.service.nodePort)) }}
{{- if (and (eq .Values.service.type "NodePort") ( .Values.service.nodePort)) }}
nodePort: {{ .Values.service.nodePort }}
{{- else }}
{{- else }}
targetPort: {{ .Values.minioAPIPort }}
{{- end}}
{{- if .Values.service.externalIPs }}
{{- end }}
{{- if .Values.service.externalIPs }}
externalIPs:
{{- range $i , $ip := .Values.service.externalIPs }}
{{- range $i , $ip := .Values.service.externalIPs }}
- {{ $ip }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
selector:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
+1 -2
View File
@@ -1,7 +1,6 @@
{{- if .Values.serviceAccount.create -}}
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccount.name | quote }}
namespace: {{ .Release.Namespace | quote }}
{{- end -}}
+10 -15
View File
@@ -1,12 +1,10 @@
{{- if and .Values.metrics.serviceMonitor.enabled .Values.metrics.serviceMonitor.includeNode}}
{{- if and .Values.metrics.serviceMonitor.enabled .Values.metrics.serviceMonitor.includeNode }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ template "minio.fullname" . }}
{{- if .Values.metrics.serviceMonitor.namespace }}
namespace: {{ .Values.metrics.serviceMonitor.namespace }}
{{ else }}
namespace: {{ .Release.Namespace | quote }}
{{- end }}
labels:
app: {{ template "minio.name" . }}
@@ -14,12 +12,11 @@ metadata:
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.metrics.serviceMonitor.additionalLabels }}
{{ toYaml .Values.metrics.serviceMonitor.additionalLabels | indent 4 }}
{{- toYaml .Values.metrics.serviceMonitor.additionalLabels | nindent 4 }}
{{- end }}
{{- if .Values.metrics.serviceMonitor.annotations }}
annotations: {{- toYaml .Values.metrics.serviceMonitor.annotations | nindent 4 }}
{{- end }}
{{- if .Values.metrics.serviceMonitor.annotations }}
annotations:
{{ toYaml .Values.metrics.serviceMonitor.annotations | trimSuffix "\n" | indent 4 }}
{{- end }}
spec:
endpoints:
{{- if .Values.tls.enabled }}
@@ -31,7 +28,7 @@ spec:
name: {{ .Values.tls.certSecret }}
key: {{ .Values.tls.publicCrt }}
serverName: {{ template "minio.fullname" . }}
{{ else }}
{{- else }}
- port: http
scheme: http
{{- end }}
@@ -43,7 +40,7 @@ spec:
scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }}
{{- end }}
{{- if .Values.metrics.serviceMonitor.relabelConfigs }}
{{ toYaml .Values.metrics.serviceMonitor.relabelConfigs | indent 6 }}
{{- toYaml .Values.metrics.serviceMonitor.relabelConfigs | nindent 6 }}
{{- end }}
{{- if not .Values.metrics.serviceMonitor.public }}
bearerTokenSecret:
@@ -67,8 +64,6 @@ metadata:
name: {{ template "minio.fullname" . }}-cluster
{{- if .Values.metrics.serviceMonitor.namespace }}
namespace: {{ .Values.metrics.serviceMonitor.namespace }}
{{ else }}
namespace: {{ .Release.Namespace | quote }}
{{- end }}
labels:
app: {{ template "minio.name" . }}
@@ -76,7 +71,7 @@ metadata:
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.metrics.serviceMonitor.additionalLabels }}
{{ toYaml .Values.metrics.serviceMonitor.additionalLabels | indent 4 }}
{{- toYaml .Values.metrics.serviceMonitor.additionalLabels | nindent 4 }}
{{- end }}
spec:
jobName: {{ template "minio.fullname" . }}
@@ -93,11 +88,11 @@ spec:
path: /minio/v2/metrics/cluster
{{- if .Values.tls.enabled }}
scheme: https
{{ else }}
{{- else }}
scheme: http
{{- end }}
{{- if .Values.metrics.serviceMonitor.relabelConfigsCluster }}
{{ toYaml .Values.metrics.serviceMonitor.relabelConfigsCluster | indent 2 }}
{{- toYaml .Values.metrics.serviceMonitor.relabelConfigsCluster | nindent 2 }}
{{- end }}
targets:
staticConfig:
+40 -52
View File
@@ -3,10 +3,7 @@
{{ $nodeCount := .Values.replicas | int }}
{{ $replicas := mul $poolCount $nodeCount }}
{{ $drivesPerNode := .Values.drivesPerNode | int }}
{{ $scheme := "http" }}
{{- if .Values.tls.enabled }}
{{ $scheme = "https" }}
{{ end }}
{{ $scheme := .Values.tls.enabled | ternary "https" "http" }}
{{ $mountPath := .Values.mountPath }}
{{ $bucketRoot := or ($.Values.bucketRoot) ($.Values.mountPath) }}
{{ $subPath := .Values.persistence.subPath }}
@@ -18,12 +15,11 @@ apiVersion: v1
kind: Service
metadata:
name: {{ template "minio.fullname" . }}-svc
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
publishNotReadyAddresses: true
clusterIP: None
@@ -40,19 +36,17 @@ apiVersion: {{ template "minio.statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
name: {{ template "minio.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
app: {{ template "minio.name" . }}
chart: {{ template "minio.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.additionalLabels }}
{{ toYaml .Values.additionalLabels | trimSuffix "\n" | indent 4 }}
{{- end }}
{{- if .Values.additionalAnnotations }}
annotations:
{{ toYaml .Values.additionalAnnotations | trimSuffix "\n" | indent 4 }}
{{- end }}
{{- if .Values.additionalLabels }}
{{- toYaml .Values.additionalLabels | nindent 4 }}
{{- end }}
{{- if .Values.additionalAnnotations }}
annotations: {{- toYaml .Values.additionalAnnotations | nindent 4 }}
{{- end }}
spec:
updateStrategy:
type: {{ .Values.StatefulSetUpdate.updateStrategy }}
@@ -69,17 +63,17 @@ spec:
labels:
app: {{ template "minio.name" . }}
release: {{ .Release.Name }}
{{- if .Values.podLabels }}
{{ toYaml .Values.podLabels | indent 8 }}
{{- end }}
{{- if .Values.podLabels }}
{{- toYaml .Values.podLabels | nindent 8 }}
{{- end }}
annotations:
{{- if not .Values.ignoreChartChecksums }}
{{- if not .Values.ignoreChartChecksums }}
checksum/secrets: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.podAnnotations }}
{{ toYaml .Values.podAnnotations | trimSuffix "\n" | indent 8 }}
{{- end }}
{{- end }}
{{- if .Values.podAnnotations }}
{{- toYaml .Values.podAnnotations | nindent 8 }}
{{- end }}
spec:
{{- if .Values.priorityClassName }}
priorityClassName: "{{ .Values.priorityClassName }}"
@@ -87,7 +81,7 @@ spec:
{{- if .Values.runtimeClassName }}
runtimeClassName: "{{ .Values.runtimeClassName }}"
{{- end }}
{{- if and .Values.securityContext.enabled .Values.persistence.enabled }}
{{- if and .Values.securityContext.enabled .Values.persistence.enabled }}
securityContext:
runAsUser: {{ .Values.securityContext.runAsUser }}
runAsGroup: {{ .Values.securityContext.runAsGroup }}
@@ -95,18 +89,19 @@ spec:
{{- if and (ge .Capabilities.KubeVersion.Major "1") (ge .Capabilities.KubeVersion.Minor "20") }}
fsGroupChangePolicy: {{ .Values.securityContext.fsGroupChangePolicy }}
{{- end }}
{{- end }}
{{ if .Values.serviceAccount.create }}
{{- end }}
{{- if .Values.serviceAccount.create }}
serviceAccountName: {{ .Values.serviceAccount.name }}
{{- end }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: [ "/bin/sh",
command: [
"/bin/sh",
"-ce",
"/usr/bin/docker-entrypoint.sh minio server {{- range $i := until $poolCount }}{{ $factor := mul $i $nodeCount }}{{ $endIndex := add $factor $nodeCount }}{{ $beginIndex := mul $i $nodeCount }} {{ $scheme }}://{{ template `minio.fullname` $ }}-{{ `{` }}{{ $beginIndex }}...{{ sub $endIndex 1 }}{{ `}`}}.{{ template `minio.fullname` $ }}-svc.{{ $.Release.Namespace }}.svc.{{ $.Values.clusterDomain }}{{if (gt $drivesPerNode 1)}}{{ $bucketRoot }}-{{ `{` }}0...{{ sub $drivesPerNode 1 }}{{ `}` }}{{else}}{{ $bucketRoot }}{{end}}{{- end}} -S {{ .Values.certsPath }} --address :{{ .Values.minioAPIPort }} --console-address :{{ .Values.minioConsolePort }} {{- template `minio.extraArgs` . }}" ]
"/usr/bin/docker-entrypoint.sh minio server {{- range $i := until $poolCount }}{{ $factor := mul $i $nodeCount }}{{ $endIndex := add $factor $nodeCount }}{{ $beginIndex := mul $i $nodeCount }} {{ $scheme }}://{{ template `minio.fullname` $ }}-{{ `{` }}{{ $beginIndex }}...{{ sub $endIndex 1 }}{{ `}`}}.{{ template `minio.fullname` $ }}-svc.{{ $.Release.Namespace }}.svc.{{ $.Values.clusterDomain }}{{if (gt $drivesPerNode 1)}}{{ $bucketRoot }}-{{ `{` }}0...{{ sub $drivesPerNode 1 }}{{ `}` }}{{ else }}{{ $bucketRoot }}{{end }}{{- end }} -S {{ .Values.certsPath }} --address :{{ .Values.minioAPIPort }} --console-address :{{ .Values.minioConsolePort }} {{- template `minio.extraArgs` . }}"
]
volumeMounts:
{{- if $penabled }}
{{- if (gt $drivesPerNode 1) }}
@@ -152,11 +147,11 @@ spec:
{{- if .Values.extraSecret }}
- name: MINIO_CONFIG_ENV_FILE
value: "/tmp/minio-config-env/config.env"
{{- end}}
{{- end }}
{{- if .Values.metrics.serviceMonitor.public }}
- name: MINIO_PROMETHEUS_AUTH_TYPE
value: "public"
{{- end}}
{{- end }}
{{- if .Values.oidc.enabled }}
- name: MINIO_IDENTITY_OPENID_CONFIG_URL
value: {{ .Values.oidc.configUrl }}
@@ -174,13 +169,12 @@ spec:
value: {{ .Values.oidc.redirectUri }}
- name: MINIO_IDENTITY_OPENID_COMMENT
value: {{ .Values.oidc.comment }}
{{- end}}
{{- end }}
{{- range $key, $val := .Values.environment }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end}}
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- end }}
resources: {{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.extraContainers }}
{{- if eq (typeOf .) "string" }}
{{- tpl . $ | nindent 8 }}
@@ -189,22 +183,18 @@ spec:
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{ toYaml . | indent 8 }}
nodeSelector: {{- toYaml . | nindent 8 }}
{{- end }}
{{- include "minio.imagePullSecrets" . | indent 6 }}
{{- include "minio.imagePullSecrets" . | indent 6 }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
affinity: {{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
tolerations: {{- toYaml . | nindent 8 }}
{{- end }}
{{- if and (gt $replicas 1) (ge .Capabilities.KubeVersion.Major "1") (ge .Capabilities.KubeVersion.Minor "19") }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{ toYaml . | indent 8 }}
topologySpreadConstraints: {{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
volumes:
@@ -218,17 +208,16 @@ spec:
{{- end }}
{{- include "minio.tlsKeysVolume" . | indent 8 }}
{{- if .Values.extraVolumes }}
{{ toYaml .Values.extraVolumes | nindent 8 }}
{{- toYaml .Values.extraVolumes | nindent 8 }}
{{- end }}
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.enabled }}
volumeClaimTemplates:
{{- if gt $drivesPerNode 1 }}
{{- range $diskId := until $drivesPerNode}}
- metadata:
name: export-{{ $diskId }}
{{- if $.Values.persistence.annotations }}
annotations:
{{ toYaml $.Values.persistence.annotations | trimSuffix "\n" | indent 10 }}
annotations: {{- toYaml $.Values.persistence.annotations | nindent 10 }}
{{- end }}
spec:
accessModes: [ {{ $accessMode | quote }} ]
@@ -243,8 +232,7 @@ spec:
- metadata:
name: export
{{- if $.Values.persistence.annotations }}
annotations:
{{ toYaml $.Values.persistence.annotations | trimSuffix "\n" | indent 10 }}
annotations: {{- toYaml $.Values.persistence.annotations | nindent 10 }}
{{- end }}
spec:
accessModes: [ {{ $accessMode | quote }} ]
@@ -255,5 +243,5 @@ spec:
requests:
storage: {{ $psize }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
+2 -2
View File
@@ -14,7 +14,7 @@ clusterDomain: cluster.local
##
image:
repository: quay.io/minio/minio
tag: RELEASE.2023-02-10T18-48-39Z
tag: RELEASE.2023-04-13T03-08-07Z
pullPolicy: IfNotPresent
imagePullSecrets: []
@@ -25,7 +25,7 @@ imagePullSecrets: []
##
mcImage:
repository: quay.io/minio/mc
tag: RELEASE.2023-01-28T20-29-38Z
tag: RELEASE.2023-04-12T02-21-51Z
pullPolicy: IfNotPresent
## minio mode, i.e. standalone or distributed
+101 -79
View File
@@ -1,9 +1,31 @@
apiVersion: v1
entries:
minio:
- apiVersion: v1
appVersion: RELEASE.2023-04-13T03-08-07Z
created: "2023-04-13T14:39:33.904722194-07:00"
description: Multi-Cloud Object Storage
digest: 034d68f85799f6693836975797f85a91842cf2d003a6c4ff401bd4ea4c946af6
home: https://min.io
icon: https://min.io/resources/img/logo/MINIO_wordmark.png
keywords:
- minio
- storage
- object-storage
- s3
- cluster
maintainers:
- email: dev@minio.io
name: MinIO, Inc
name: minio
sources:
- https://github.com/minio/minio
urls:
- https://charts.min.io/helm-releases/minio-5.0.8.tgz
version: 5.0.8
- apiVersion: v1
appVersion: RELEASE.2023-02-10T18-48-39Z
created: "2023-02-13T16:07:05.314663249+05:30"
created: "2023-04-13T14:39:33.902972411-07:00"
description: Multi-Cloud Object Storage
digest: 3f935a310e1b5b873052629b66005c160356ca7b2bd394cb07b34dbaf9905e3f
home: https://min.io
@@ -25,7 +47,7 @@ entries:
version: 5.0.7
- apiVersion: v1
appVersion: RELEASE.2023-02-10T18-48-39Z
created: "2023-02-13T16:07:05.310251414+05:30"
created: "2023-04-13T14:39:33.899768149-07:00"
description: Multi-Cloud Object Storage
digest: 82ef858ce483c2d736444792986cb36bd0fb4fc90a80b97fe30d7b2f2034d24a
home: https://min.io
@@ -47,7 +69,7 @@ entries:
version: 5.0.6
- apiVersion: v1
appVersion: RELEASE.2023-01-31T02-24-19Z
created: "2023-02-13T16:07:05.306878973+05:30"
created: "2023-04-13T14:39:33.898010335-07:00"
description: Multi-Cloud Object Storage
digest: fefeea10e4e525e45f82fb80a03900d34605ec432dd92f56d94eaf4fb1b98c41
home: https://min.io
@@ -69,7 +91,7 @@ entries:
version: 5.0.5
- apiVersion: v1
appVersion: RELEASE.2022-12-12T19-27-27Z
created: "2023-02-13T16:07:05.303936931+05:30"
created: "2023-04-13T14:39:33.896365088-07:00"
description: Multi-Cloud Object Storage
digest: 6b305783c98b0b97ffab079ff4430094fd0ca6e98e82bb8153cb93033a1bf40f
home: https://min.io
@@ -91,7 +113,7 @@ entries:
version: 5.0.4
- apiVersion: v1
appVersion: RELEASE.2022-12-12T19-27-27Z
created: "2023-02-13T16:07:05.300970047+05:30"
created: "2023-04-13T14:39:33.894702003-07:00"
description: Multi-Cloud Object Storage
digest: bac89157c53b324aece263c294aa49f5c9b64f426b4b06c9bca3d72e77e244f2
home: https://min.io
@@ -113,7 +135,7 @@ entries:
version: 5.0.3
- apiVersion: v1
appVersion: RELEASE.2022-12-12T19-27-27Z
created: "2023-02-13T16:07:05.297908435+05:30"
created: "2023-04-13T14:39:33.89270488-07:00"
description: Multi-Cloud Object Storage
digest: 935ce4f09366231b11d414d626f887fa6fa6024dd30a42e81e810ca1438d5904
home: https://min.io
@@ -135,7 +157,7 @@ entries:
version: 5.0.2
- apiVersion: v1
appVersion: RELEASE.2022-11-11T03-44-20Z
created: "2023-02-13T16:07:05.293712618+05:30"
created: "2023-04-13T14:39:33.89095156-07:00"
description: Multi-Cloud Object Storage
digest: 3e952c5d737980b8ccdfb819021eafb4b4e8da226f764a1dc3de1ba63ceb1ffa
home: https://min.io
@@ -157,7 +179,7 @@ entries:
version: 5.0.1
- apiVersion: v1
appVersion: RELEASE.2022-10-24T18-35-07Z
created: "2023-02-13T16:07:05.29069869+05:30"
created: "2023-04-13T14:39:33.88770083-07:00"
description: Multi-Cloud Object Storage
digest: 6215c800d84fd4c40e4fb4142645fc1c6a039c251776a3cc8c11a24b9e3b59c7
home: https://min.io
@@ -179,7 +201,7 @@ entries:
version: 5.0.0
- apiVersion: v1
appVersion: RELEASE.2022-10-24T18-35-07Z
created: "2023-02-13T16:07:05.287706406+05:30"
created: "2023-04-13T14:39:33.885874401-07:00"
description: Multi-Cloud Object Storage
digest: 2d3d884490ea1127742f938bc9382844bae713caae08b3308f766f3c9000659a
home: https://min.io
@@ -201,7 +223,7 @@ entries:
version: 4.1.0
- apiVersion: v1
appVersion: RELEASE.2022-09-17T00-09-45Z
created: "2023-02-13T16:07:05.260150201+05:30"
created: "2023-04-13T14:39:33.870938962-07:00"
description: Multi-Cloud Object Storage
digest: 6f16f2dbfed91ab81a7fae60b6ea32f554365bd27bf5fda55b64a0fa264f4252
home: https://min.io
@@ -223,7 +245,7 @@ entries:
version: 4.0.15
- apiVersion: v1
appVersion: RELEASE.2022-09-01T23-53-36Z
created: "2023-02-13T16:07:05.256767815+05:30"
created: "2023-04-13T14:39:33.869292215-07:00"
description: Multi-Cloud Object Storage
digest: 35d89d8f49d53ea929466fb88ee26123431326033f1387e6b2d536a629c0a398
home: https://min.io
@@ -245,7 +267,7 @@ entries:
version: 4.0.14
- apiVersion: v1
appVersion: RELEASE.2022-08-22T23-53-06Z
created: "2023-02-13T16:07:05.253636559+05:30"
created: "2023-04-13T14:39:33.867593706-07:00"
description: Multi-Cloud Object Storage
digest: 5b86937ca88d9f6046141fdc2b1cc54760435ed92d289cd0a115fa7148781d4e
home: https://min.io
@@ -267,7 +289,7 @@ entries:
version: 4.0.13
- apiVersion: v1
appVersion: RELEASE.2022-08-13T21-54-44Z
created: "2023-02-13T16:07:05.250600219+05:30"
created: "2023-04-13T14:39:33.86487566-07:00"
description: Multi-Cloud Object Storage
digest: 2d9c227c0f46ea8bdef4d760c212156fd4c6623ddc5406779c569fe925527787
home: https://min.io
@@ -289,7 +311,7 @@ entries:
version: 4.0.12
- apiVersion: v1
appVersion: RELEASE.2022-08-05T23-27-09Z
created: "2023-02-13T16:07:05.24764702+05:30"
created: "2023-04-13T14:39:33.863292598-07:00"
description: Multi-Cloud Object Storage
digest: 6caaffcb636e040cd7e8bc4883a1674a673757f4781c32d53b5ec0f41fea3944
home: https://min.io
@@ -311,7 +333,7 @@ entries:
version: 4.0.11
- apiVersion: v1
appVersion: RELEASE.2022-08-02T23-59-16Z
created: "2023-02-13T16:07:05.243432562+05:30"
created: "2023-04-13T14:39:33.861701816-07:00"
description: Multi-Cloud Object Storage
digest: 841d87788fb094d6a7d8a91e91821fe1e847bc952e054c781fc93742d112e18a
home: https://min.io
@@ -333,7 +355,7 @@ entries:
version: 4.0.10
- apiVersion: v1
appVersion: RELEASE.2022-08-02T23-59-16Z
created: "2023-02-13T16:07:05.284607994+05:30"
created: "2023-04-13T14:39:33.884239166-07:00"
description: Multi-Cloud Object Storage
digest: 6f1a78382df3215deac07495a5e7de7009a1153b4cf6cb565630652a69aec4cf
home: https://min.io
@@ -355,7 +377,7 @@ entries:
version: 4.0.9
- apiVersion: v1
appVersion: RELEASE.2022-07-29T19-40-48Z
created: "2023-02-13T16:07:05.281734578+05:30"
created: "2023-04-13T14:39:33.882599235-07:00"
description: Multi-Cloud Object Storage
digest: d11db37963636922cb778b6bc0ad2ca4724cb391ea7b785995ada52467d7dd83
home: https://min.io
@@ -377,7 +399,7 @@ entries:
version: 4.0.8
- apiVersion: v1
appVersion: RELEASE.2022-07-26T00-53-03Z
created: "2023-02-13T16:07:05.278329233+05:30"
created: "2023-04-13T14:39:33.881089691-07:00"
description: Multi-Cloud Object Storage
digest: ca775e08c84331bb5029d4d29867d30c16e2c62e897788eb432212a756e91e4e
home: https://min.io
@@ -399,7 +421,7 @@ entries:
version: 4.0.7
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-02-13T16:07:05.275016183+05:30"
created: "2023-04-13T14:39:33.879531106-07:00"
description: Multi-Cloud Object Storage
digest: 06542b8f3d149d5908b15de9a8d6f8cf304af0213830be56dc315785d14f9ccd
home: https://min.io
@@ -421,7 +443,7 @@ entries:
version: 4.0.6
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-02-13T16:07:05.272312786+05:30"
created: "2023-04-13T14:39:33.877099765-07:00"
description: Multi-Cloud Object Storage
digest: dd2676362f067454a496cdd293609d0c904b08f521625af49f95402a024ba1f5
home: https://min.io
@@ -443,7 +465,7 @@ entries:
version: 4.0.5
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-02-13T16:07:05.269633639+05:30"
created: "2023-04-13T14:39:33.875614067-07:00"
description: Multi-Cloud Object Storage
digest: bab9ef192d4eda4c572ad0ce0cf551736c847f582d1837d6833ee10543c23167
home: https://min.io
@@ -465,7 +487,7 @@ entries:
version: 4.0.4
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-02-13T16:07:05.266852082+05:30"
created: "2023-04-13T14:39:33.874105602-07:00"
description: Multi-Cloud Object Storage
digest: c770bb9841c76576e4e8573f78b0ec33e0d729504c9667e67ad62d48df5ed64c
home: https://min.io
@@ -487,7 +509,7 @@ entries:
version: 4.0.3
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2023-02-13T16:07:05.264019141+05:30"
created: "2023-04-13T14:39:33.872532023-07:00"
description: Multi-Cloud Object Storage
digest: 95835f4199d963e2a23a2493610b348e6f2ff8b71c1a648c4a3b84af9b7a83eb
home: https://min.io
@@ -509,7 +531,7 @@ entries:
version: 4.0.2
- apiVersion: v1
appVersion: RELEASE.2022-04-30T22-23-53Z
created: "2023-02-13T16:07:05.24049945+05:30"
created: "2023-04-13T14:39:33.860146689-07:00"
description: Multi-Cloud Object Storage
digest: 55a088c403b056e1f055a97426aa11759c3d6cbad38face170fe6cbbec7d568f
home: https://min.io
@@ -531,7 +553,7 @@ entries:
version: 4.0.1
- apiVersion: v1
appVersion: RELEASE.2022-04-26T01-20-24Z
created: "2023-02-13T16:07:05.237726036+05:30"
created: "2023-04-13T14:39:33.85863375-07:00"
description: Multi-Cloud Object Storage
digest: f541237e24336ec3f7f45ae0d523fef694e3a2f9ef648c5b11c15734db6ba2b2
home: https://min.io
@@ -553,7 +575,7 @@ entries:
version: 4.0.0
- apiVersion: v1
appVersion: RELEASE.2022-04-16T04-26-02Z
created: "2023-02-13T16:07:05.23502228+05:30"
created: "2023-04-13T14:39:33.857146288-07:00"
description: Multi-Cloud Object Storage
digest: edc0c3dd6d5246a06b74ba16bb4aff80a6d7225dc9aecf064fd89a8af371b9c1
home: https://min.io
@@ -575,7 +597,7 @@ entries:
version: 3.6.6
- apiVersion: v1
appVersion: RELEASE.2022-04-12T06-55-35Z
created: "2023-02-13T16:07:05.232278897+05:30"
created: "2023-04-13T14:39:33.855161049-07:00"
description: Multi-Cloud Object Storage
digest: 211e89f6b9eb0b9a3583abaa127be60e1f9717a098e6b2858cb9dc1cc50c1650
home: https://min.io
@@ -597,7 +619,7 @@ entries:
version: 3.6.5
- apiVersion: v1
appVersion: RELEASE.2022-04-09T15-09-52Z
created: "2023-02-13T16:07:05.229480491+05:30"
created: "2023-04-13T14:39:33.853131766-07:00"
description: Multi-Cloud Object Storage
digest: 534a879d73b370a18b554b93d0930e1c115419619c4ce4ec7dbaae632acacf06
home: https://min.io
@@ -619,7 +641,7 @@ entries:
version: 3.6.4
- apiVersion: v1
appVersion: RELEASE.2022-03-24T00-43-44Z
created: "2023-02-13T16:07:05.225464477+05:30"
created: "2023-04-13T14:39:33.851651471-07:00"
description: Multi-Cloud Object Storage
digest: 99508b20eb0083a567dcccaf9a6c237e09575ed1d70cd2e8333f89c472d13d75
home: https://min.io
@@ -641,7 +663,7 @@ entries:
version: 3.6.3
- apiVersion: v1
appVersion: RELEASE.2022-03-17T06-34-49Z
created: "2023-02-13T16:07:05.223267703+05:30"
created: "2023-04-13T14:39:33.850155733-07:00"
description: Multi-Cloud Object Storage
digest: b4cd25611ca322b1d23d23112fdfa6b068fd91eefe0b0663b88ff87ea4282495
home: https://min.io
@@ -663,7 +685,7 @@ entries:
version: 3.6.2
- apiVersion: v1
appVersion: RELEASE.2022-03-14T18-25-24Z
created: "2023-02-13T16:07:05.221366808+05:30"
created: "2023-04-13T14:39:33.848658622-07:00"
description: Multi-Cloud Object Storage
digest: d75b88162bfe54740a233bcecf87328bba2ae23d170bec3a35c828bc6fdc224c
home: https://min.io
@@ -685,7 +707,7 @@ entries:
version: 3.6.1
- apiVersion: v1
appVersion: RELEASE.2022-03-11T23-57-45Z
created: "2023-02-13T16:07:05.219604685+05:30"
created: "2023-04-13T14:39:33.8471424-07:00"
description: Multi-Cloud Object Storage
digest: 22e53a1184a21a679bc7d8b94e955777f3506340fc29da5ab0cb6d729bdbde8d
home: https://min.io
@@ -707,7 +729,7 @@ entries:
version: 3.6.0
- apiVersion: v1
appVersion: RELEASE.2022-03-03T21-21-16Z
created: "2023-02-13T16:07:05.217806866+05:30"
created: "2023-04-13T14:39:33.845572238-07:00"
description: Multi-Cloud Object Storage
digest: 6fda968d3fdfd60470c0055a4e1a3bd8e5aee9ad0af5ba2fb7b7b926fdc9e4a0
home: https://min.io
@@ -729,7 +751,7 @@ entries:
version: 3.5.9
- apiVersion: v1
appVersion: RELEASE.2022-02-26T02-54-46Z
created: "2023-02-13T16:07:05.216065792+05:30"
created: "2023-04-13T14:39:33.843283699-07:00"
description: Multi-Cloud Object Storage
digest: 8e015369048a3a82bbd53ad36696786f18561c6b25d14eee9e2c93a7336cef46
home: https://min.io
@@ -751,7 +773,7 @@ entries:
version: 3.5.8
- apiVersion: v1
appVersion: RELEASE.2022-02-18T01-50-10Z
created: "2023-02-13T16:07:05.214380811+05:30"
created: "2023-04-13T14:39:33.841793213-07:00"
description: Multi-Cloud Object Storage
digest: cb3543fe748e5f0d59b3ccf4ab9af8e10b731405ae445d1f5715e30013632373
home: https://min.io
@@ -773,7 +795,7 @@ entries:
version: 3.5.7
- apiVersion: v1
appVersion: RELEASE.2022-02-18T01-50-10Z
created: "2023-02-13T16:07:05.213073341+05:30"
created: "2023-04-13T14:39:33.840320255-07:00"
description: Multi-Cloud Object Storage
digest: f2e359fa5eefffc59abb3d14a8fa94b11ddeaa99f6cd8dd5f40f4e04121000d6
home: https://min.io
@@ -795,7 +817,7 @@ entries:
version: 3.5.6
- apiVersion: v1
appVersion: RELEASE.2022-02-16T00-35-27Z
created: "2023-02-13T16:07:05.211775229+05:30"
created: "2023-04-13T14:39:33.838848475-07:00"
description: Multi-Cloud Object Storage
digest: 529d56cca9d83a3d0e5672e63b6e87b5bcbe10a6b45f7a55ba998cceb32f9c81
home: https://min.io
@@ -817,7 +839,7 @@ entries:
version: 3.5.5
- apiVersion: v1
appVersion: RELEASE.2022-02-12T00-51-25Z
created: "2023-02-13T16:07:05.210542911+05:30"
created: "2023-04-13T14:39:33.837463455-07:00"
description: Multi-Cloud Object Storage
digest: 3d530598f8ece67bec5b7f990d206584893987c713502f9228e4ee24b5535414
home: https://min.io
@@ -839,7 +861,7 @@ entries:
version: 3.5.4
- apiVersion: v1
appVersion: RELEASE.2022-02-12T00-51-25Z
created: "2023-02-13T16:07:05.209264623+05:30"
created: "2023-04-13T14:39:33.836037131-07:00"
description: Multi-Cloud Object Storage
digest: 53937031348b29615f07fc4869b2d668391d8ba9084630a497abd7a7dea9dfb0
home: https://min.io
@@ -861,7 +883,7 @@ entries:
version: 3.5.3
- apiVersion: v1
appVersion: RELEASE.2022-02-07T08-17-33Z
created: "2023-02-13T16:07:05.207795313+05:30"
created: "2023-04-13T14:39:33.834131809-07:00"
description: Multi-Cloud Object Storage
digest: 68d643414ff0d565716c5715034fcbf1af262e041915a5c02eb51ec1a65c1ea0
home: https://min.io
@@ -883,7 +905,7 @@ entries:
version: 3.5.2
- apiVersion: v1
appVersion: RELEASE.2022-02-01T18-00-14Z
created: "2023-02-13T16:07:05.206469654+05:30"
created: "2023-04-13T14:39:33.832190048-07:00"
description: Multi-Cloud Object Storage
digest: a3e855ed0f31233b989fffd775a29d6fbfa0590089010ff16783fd7f142ef6e7
home: https://min.io
@@ -905,7 +927,7 @@ entries:
version: 3.5.1
- apiVersion: v1
appVersion: RELEASE.2022-02-01T18-00-14Z
created: "2023-02-13T16:07:05.205352117+05:30"
created: "2023-04-13T14:39:33.829251194-07:00"
description: Multi-Cloud Object Storage
digest: b1b0ae3c54b4260a698753e11d7781bb8ddc67b7e3fbf0af82796e4cd4ef92a3
home: https://min.io
@@ -927,7 +949,7 @@ entries:
version: 3.5.0
- apiVersion: v1
appVersion: RELEASE.2022-01-28T02-28-16Z
created: "2023-02-13T16:07:05.204249186+05:30"
created: "2023-04-13T14:39:33.827349955-07:00"
description: Multi-Cloud Object Storage
digest: fecf25d2d3fb208c6f894fed642a60780a570b7f6d0adddde846af7236dc80aa
home: https://min.io
@@ -949,7 +971,7 @@ entries:
version: 3.4.8
- apiVersion: v1
appVersion: RELEASE.2022-01-25T19-56-04Z
created: "2023-02-13T16:07:05.203126945+05:30"
created: "2023-04-13T14:39:33.825179668-07:00"
description: Multi-Cloud Object Storage
digest: c78008caa5ce98f64c887630f59d0cbd481cb3f19a7d4e9d3e81bf4e1e45cadc
home: https://min.io
@@ -971,7 +993,7 @@ entries:
version: 3.4.7
- apiVersion: v1
appVersion: RELEASE.2022-01-08T03-11-54Z
created: "2023-02-13T16:07:05.202010303+05:30"
created: "2023-04-13T14:39:33.823029641-07:00"
description: Multi-Cloud Object Storage
digest: 8f2e2691bf897f74ff094dd370ec56ba9d417e5e8926710c14c2ba346330238d
home: https://min.io
@@ -993,7 +1015,7 @@ entries:
version: 3.4.6
- apiVersion: v1
appVersion: RELEASE.2022-01-04T07-41-07Z
created: "2023-02-13T16:07:05.200824734+05:30"
created: "2023-04-13T14:39:33.821363456-07:00"
description: Multi-Cloud Object Storage
digest: bacd140f0016fab35f516bde787da6449b3a960c071fad9e4b6563118033ac84
home: https://min.io
@@ -1015,7 +1037,7 @@ entries:
version: 3.4.5
- apiVersion: v1
appVersion: RELEASE.2021-12-29T06-49-06Z
created: "2023-02-13T16:07:05.199028566+05:30"
created: "2023-04-13T14:39:33.81968069-07:00"
description: Multi-Cloud Object Storage
digest: 48a453ea5ffeef25933904caefd9470bfb26224dfc2d1096bd0031467ba53007
home: https://min.io
@@ -1037,7 +1059,7 @@ entries:
version: 3.4.4
- apiVersion: v1
appVersion: RELEASE.2021-12-20T22-07-16Z
created: "2023-02-13T16:07:05.197899552+05:30"
created: "2023-04-13T14:39:33.817648739-07:00"
description: Multi-Cloud Object Storage
digest: 47ef4a930713b98f9438ceca913c6e700f85bb25dba5624b056486254b5f0c60
home: https://min.io
@@ -1059,7 +1081,7 @@ entries:
version: 3.4.3
- apiVersion: v1
appVersion: RELEASE.2021-12-20T22-07-16Z
created: "2023-02-13T16:07:05.19670699+05:30"
created: "2023-04-13T14:39:33.816178628-07:00"
description: Multi-Cloud Object Storage
digest: d6763f7e2ea66810bd55eb225579a9c3b968f9ae1256f45fd469362e55d846ff
home: https://min.io
@@ -1081,7 +1103,7 @@ entries:
version: 3.4.2
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-02-13T16:07:05.195579255+05:30"
created: "2023-04-13T14:39:33.813912605-07:00"
description: Multi-Cloud Object Storage
digest: 2fb822c87216ba3fc2ae51a54a0a3e239aa560d86542991504a841cc2a2b9a37
home: https://min.io
@@ -1103,7 +1125,7 @@ entries:
version: 3.4.1
- apiVersion: v1
appVersion: RELEASE.2021-12-18T04-42-33Z
created: "2023-02-13T16:07:05.19443234+05:30"
created: "2023-04-13T14:39:33.812658948-07:00"
description: Multi-Cloud Object Storage
digest: fa8ba1aeb1a15316c6be8403416a5e6b5e6139b7166592087e7bddc9e6db5453
home: https://min.io
@@ -1125,7 +1147,7 @@ entries:
version: 3.4.0
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-02-13T16:07:05.193267056+05:30"
created: "2023-04-13T14:39:33.811338087-07:00"
description: Multi-Cloud Object Storage
digest: b9b0af9ca50b8d00868e1f1b989dca275829d9110af6de91bb9b3a398341e894
home: https://min.io
@@ -1147,7 +1169,7 @@ entries:
version: 3.3.4
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-02-13T16:07:05.191695909+05:30"
created: "2023-04-13T14:39:33.809986868-07:00"
description: Multi-Cloud Object Storage
digest: f8b22a5b8fe95a7ddf61b825e17d11c9345fb10e4c126b0d78381608aa300a08
home: https://min.io
@@ -1169,7 +1191,7 @@ entries:
version: 3.3.3
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2023-02-13T16:07:05.189974413+05:30"
created: "2023-04-13T14:39:33.80878071-07:00"
description: Multi-Cloud Object Storage
digest: c48d474f269427abe5ab446f00687d0625b3d1adfc5c73bdb4b21ca9e42853fb
home: https://min.io
@@ -1191,7 +1213,7 @@ entries:
version: 3.3.2
- apiVersion: v1
appVersion: RELEASE.2021-11-24T23-19-33Z
created: "2023-02-13T16:07:05.188215082+05:30"
created: "2023-04-13T14:39:33.807416726-07:00"
description: Multi-Cloud Object Storage
digest: 7c3da39d9b0090cbf5efedf0cc163a1e2df05becc5152c3add8e837384690bc4
home: https://min.io
@@ -1213,7 +1235,7 @@ entries:
version: 3.3.1
- apiVersion: v1
appVersion: RELEASE.2021-11-24T23-19-33Z
created: "2023-02-13T16:07:05.186446002+05:30"
created: "2023-04-13T14:39:33.806163505-07:00"
description: Multi-Cloud Object Storage
digest: 50d6590b4cc779c40f81cc13b1586fbe508aa7f3230036c760bfc5f4154fbce4
home: https://min.io
@@ -1235,7 +1257,7 @@ entries:
version: 3.3.0
- apiVersion: v1
appVersion: RELEASE.2021-10-13T00-23-17Z
created: "2023-02-13T16:07:05.184663736+05:30"
created: "2023-04-13T14:39:33.804903629-07:00"
description: Multi-Cloud Object Storage
digest: 5b797b7208cd904c11a76cd72938c8652160cb5fcd7f09fa41e4e703e6d64054
home: https://min.io
@@ -1257,7 +1279,7 @@ entries:
version: 3.2.0
- apiVersion: v1
appVersion: RELEASE.2021-10-10T16-53-30Z
created: "2023-02-13T16:07:05.182907313+05:30"
created: "2023-04-13T14:39:33.802626491-07:00"
description: Multi-Cloud Object Storage
digest: e084ac4bb095f071e59f8f08bd092e4ab2404c1ddadacfdce7dbe248f1bafff8
home: https://min.io
@@ -1279,7 +1301,7 @@ entries:
version: 3.1.9
- apiVersion: v1
appVersion: RELEASE.2021-10-06T23-36-31Z
created: "2023-02-13T16:07:05.181104969+05:30"
created: "2023-04-13T14:39:33.801365971-07:00"
description: Multi-Cloud Object Storage
digest: 2890430a8d9487d1fa5508c26776e4881d0086b2c052aa6bdc65c0e4423b9159
home: https://min.io
@@ -1301,7 +1323,7 @@ entries:
version: 3.1.8
- apiVersion: v1
appVersion: RELEASE.2021-10-02T16-31-05Z
created: "2023-02-13T16:07:05.179044431+05:30"
created: "2023-04-13T14:39:33.800136574-07:00"
description: Multi-Cloud Object Storage
digest: 01a92196af6c47e3a01e1c68d7cf693a8bc487cba810c2cecff155071e4d6a11
home: https://min.io
@@ -1323,7 +1345,7 @@ entries:
version: 3.1.7
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2023-02-13T16:07:05.177402147+05:30"
created: "2023-04-13T14:39:33.798923934-07:00"
description: Multi-Cloud Object Storage
digest: e779d73f80b75f33b9c9d995ab10fa455c9c57ee575ebc54e06725a64cd04310
home: https://min.io
@@ -1345,7 +1367,7 @@ entries:
version: 3.1.6
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2023-02-13T16:07:05.176014131+05:30"
created: "2023-04-13T14:39:33.797532313-07:00"
description: Multi-Cloud Object Storage
digest: 19de4bbc8a400f0c2a94c5e85fc25c9bfc666e773fb3e368dd621d5a57dd1c2a
home: https://min.io
@@ -1367,7 +1389,7 @@ entries:
version: 3.1.5
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2023-02-13T16:07:05.174916165+05:30"
created: "2023-04-13T14:39:33.796286874-07:00"
description: Multi-Cloud Object Storage
digest: f789d93a171296dd01af0105a5ce067c663597afbb2432faeda293b752b355c0
home: https://min.io
@@ -1389,7 +1411,7 @@ entries:
version: 3.1.4
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-02-13T16:07:05.173842859+05:30"
created: "2023-04-13T14:39:33.794957696-07:00"
description: Multi-Cloud Object Storage
digest: e2eb34d31560b012ef6581f0ff6004ea4376c968cbe0daed2d8f3a614a892afb
home: https://min.io
@@ -1411,7 +1433,7 @@ entries:
version: 3.1.3
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-02-13T16:07:05.172742202+05:30"
created: "2023-04-13T14:39:33.793014019-07:00"
description: Multi-Cloud Object Storage
digest: 8d7e0cc46b3583abd71b97dc0c071f98321101f90eca17348f1e9e0831be64cd
home: https://min.io
@@ -1433,7 +1455,7 @@ entries:
version: 3.1.2
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-02-13T16:07:05.171626414+05:30"
created: "2023-04-13T14:39:33.790939383-07:00"
description: Multi-Cloud Object Storage
digest: 50dcbf366b1b21f4a6fc429d0b884c0c7ff481d0fb95c5e9b3ae157c348dd124
home: https://min.io
@@ -1455,7 +1477,7 @@ entries:
version: 3.1.1
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2023-02-13T16:07:05.170357547+05:30"
created: "2023-04-13T14:39:33.788074463-07:00"
description: Multi-Cloud Object Storage
digest: 6c01af55d2e2e5f716eabf6fef3a92a8464d0674529e9bacab292e5478a73b7a
home: https://min.io
@@ -1477,7 +1499,7 @@ entries:
version: 3.1.0
- apiVersion: v1
appVersion: RELEASE.2021-09-03T03-56-13Z
created: "2023-02-13T16:07:05.168351721+05:30"
created: "2023-04-13T14:39:33.786791063-07:00"
description: Multi-Cloud Object Storage
digest: 18e10be4d0458bc590ca9abf753227e0c70f60511495387b8d4fb15a4daf932e
home: https://min.io
@@ -1499,7 +1521,7 @@ entries:
version: 3.0.2
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2023-02-13T16:07:05.167096215+05:30"
created: "2023-04-13T14:39:33.785648827-07:00"
description: Multi-Cloud Object Storage
digest: f5b6e7f6272a9e71aef3b75555f6f756a39eef65cb78873f26451dba79b19906
home: https://min.io
@@ -1521,7 +1543,7 @@ entries:
version: 3.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2023-02-13T16:07:05.165813385+05:30"
created: "2023-04-13T14:39:33.78451068-07:00"
description: Multi-Cloud Object Storage
digest: 6d2ee1336c412affaaf209fdb80215be2a6ebb23ab2443adbaffef9e7df13fab
home: https://min.io
@@ -1543,7 +1565,7 @@ entries:
version: 3.0.0
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2023-02-13T16:07:05.164494238+05:30"
created: "2023-04-13T14:39:33.783363751-07:00"
description: Multi-Cloud Object Storage
digest: 0a004aaf5bb61deed6a5c88256d1695ebe2f9ff1553874a93e4acfd75e8d339b
home: https://min.io
@@ -1563,7 +1585,7 @@ entries:
version: 2.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2023-02-13T16:07:05.163195346+05:30"
created: "2023-04-13T14:39:33.782197288-07:00"
description: Multi-Cloud Object Storage
digest: fcd944e837ee481307de6aa3d387ea18c234f995a84c15abb211aab4a4054afc
home: https://min.io
@@ -1583,7 +1605,7 @@ entries:
version: 2.0.0
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2023-02-13T16:07:05.161797765+05:30"
created: "2023-04-13T14:39:33.780959636-07:00"
description: Multi-Cloud Object Storage
digest: 7b6c033d43a856479eb493ab8ca05b230f77c3e42e209e8f298fac6af1a9796f
home: https://min.io
@@ -1603,7 +1625,7 @@ entries:
version: 1.0.5
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2023-02-13T16:07:05.159565648+05:30"
created: "2023-04-13T14:39:33.778433355-07:00"
description: Multi-Cloud Object Storage
digest: abd221245ace16c8e0c6c851cf262d1474a5219dcbf25c4b2e7b77142f9c59ed
home: https://min.io
@@ -1623,7 +1645,7 @@ entries:
version: 1.0.4
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2023-02-13T16:07:05.156671779+05:30"
created: "2023-04-13T14:39:33.776484146-07:00"
description: Multi-Cloud Object Storage
digest: 922a333f5413d1042f7aa81929f43767f6ffca9b260c46713f04ce1dda86d57d
home: https://min.io
@@ -1643,7 +1665,7 @@ entries:
version: 1.0.3
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2023-02-13T16:07:05.155053059+05:30"
created: "2023-04-13T14:39:33.774527099-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: 10e22773506bbfb1c66442937956534cf4057b94f06a977db78b8cd223588388
home: https://min.io
@@ -1663,7 +1685,7 @@ entries:
version: 1.0.2
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2023-02-13T16:07:05.153511576+05:30"
created: "2023-04-13T14:39:33.772578779-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: ef86ab6df23d6942705da9ef70991b649638c51bc310587d37a425268ba4a06c
home: https://min.io
@@ -1683,7 +1705,7 @@ entries:
version: 1.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-17T20-53-08Z
created: "2023-02-13T16:07:05.151860845+05:30"
created: "2023-04-13T14:39:33.770495939-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: 1add7608692cbf39aaf9b1252530e566f7b2f306a14e390b0f49b97a20f2b188
home: https://min.io
@@ -1701,4 +1723,4 @@ entries:
urls:
- https://charts.min.io/helm-releases/minio-1.0.0.tgz
version: 1.0.0
generated: "2023-02-13T16:07:05.14973705+05:30"
generated: "2023-04-13T14:39:33.768251143-07:00"
+6 -2
View File
@@ -186,10 +186,14 @@ func (m *Monitor) DeleteBucket(bucket string) {
// DeleteBucketThrottle deletes monitoring for a bucket's target
func (m *Monitor) DeleteBucketThrottle(bucket, arn string) {
m.tlock.Lock()
delete(m.bucketThrottle, bucket)
if _, ok := m.bucketThrottle[bucket]; ok {
delete(m.bucketThrottle[bucket], arn)
}
m.tlock.Unlock()
m.mlock.Lock()
delete(m.activeBuckets, bucket)
if _, ok := m.activeBuckets[bucket]; ok {
delete(m.activeBuckets[bucket], arn)
}
m.mlock.Unlock()
}
+18
View File
@@ -602,6 +602,17 @@ var renamedSubsys = map[string]string{
// Add future sub-system renames
}
const ( // deprecated keys
apiReplicationWorkers = "replication_workers"
apiReplicationFailedWorkers = "replication_failed_workers"
)
// map of subsystem to deleted keys
var deletedSubSysKeys = map[string][]string{
APISubSys: {apiReplicationWorkers, apiReplicationFailedWorkers},
// Add future sub-system deleted keys
}
// Merge - merges a new config with all the
// missing values for default configs,
// returns a config.
@@ -632,9 +643,16 @@ func (c Config) Merge() Config {
}
subSys = rnSubSys
}
// Delete deprecated keys for subsystem if any
if keys, ok := deletedSubSysKeys[subSys]; ok {
for _, key := range keys {
ckvs.Delete(key)
}
}
cp[subSys][tgt] = ckvs
}
}
return cp
}
+4 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc.
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
@@ -28,6 +28,7 @@ import (
"net/url"
"os"
"path/filepath"
"time"
"github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/logger"
@@ -268,9 +269,9 @@ func (target *KafkaTarget) Close() error {
// Check if atleast one broker in cluster is active
func (k KafkaArgs) pingBrokers() bool {
d := net.Dialer{Timeout: 60 * time.Second}
for _, broker := range k.Brokers {
_, dErr := net.Dial("tcp", broker.String())
if dErr == nil {
if _, err := d.Dial("tcp", broker.String()); err == nil {
return true
}
}
+1
View File
@@ -78,6 +78,7 @@ func newBufPool(sz int) httputil.BufferPool {
},
}}
}
// ServeHTTP forwards HTTP traffic using the configured transport
func (f *Forwarder) ServeHTTP(w http.ResponseWriter, inReq *http.Request) {
outReq := new(http.Request)
+22 -2
View File
@@ -22,6 +22,7 @@ import (
"crypto/sha1"
"encoding/base64"
"encoding/binary"
"fmt"
"hash"
"hash/crc32"
"net/http"
@@ -53,6 +54,8 @@ const (
ChecksumCRC32C
// ChecksumInvalid indicates an invalid checksum.
ChecksumInvalid
// ChecksumMultipart indicates the checksum is from a multipart upload.
ChecksumMultipart
// ChecksumNone indicates no checksum.
ChecksumNone ChecksumType = 0
@@ -194,8 +197,17 @@ func ReadCheckSums(b []byte) map[string]string {
if length == 0 || len(b) < length {
break
}
res[typ.String()] = base64.StdEncoding.EncodeToString(b[:length])
cs := base64.StdEncoding.EncodeToString(b[:length])
b = b[length:]
if typ.Is(ChecksumMultipart) {
t, n := binary.Uvarint(b)
if n < 0 {
break
}
cs = fmt.Sprintf("%s-%d", cs, t)
b = b[n:]
}
res[typ.String()] = cs
}
if len(res) == 0 {
res = nil
@@ -225,8 +237,9 @@ func NewChecksumString(alg, value string) *Checksum {
}
// AppendTo will append the checksum to b.
// 'parts' is used when checksum has ChecksumMultipart set.
// ReadCheckSums reads the values back.
func (c *Checksum) AppendTo(b []byte) []byte {
func (c *Checksum) AppendTo(b []byte, parts int) []byte {
if c == nil {
return nil
}
@@ -238,6 +251,13 @@ func (c *Checksum) AppendTo(b []byte) []byte {
}
b = append(b, tmp[:n]...)
b = append(b, crc...)
if c.Type.Is(ChecksumMultipart) {
if parts < 0 {
parts = 0
}
n := binary.PutUvarint(tmp[:], uint64(parts))
b = append(b, tmp[:n]...)
}
return b
}
+2 -2
View File
@@ -56,8 +56,8 @@ func (r *RequestRecorder) Read(p []byte) (n int, err error) {
return n, err
}
// BodySize returns the body size if the currently read object
func (r *RequestRecorder) BodySize() int {
// Size returns the body size if the currently read object
func (r *RequestRecorder) Size() int {
return r.bytesRead
}
@@ -1,66 +0,0 @@
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package stats
import (
"io"
"net/http"
)
// IncomingTrafficMeter counts the incoming bytes from the underlying request.Body.
type IncomingTrafficMeter struct {
countBytes int64
io.ReadCloser
}
// Read calls the underlying Read and counts the transferred bytes.
func (r *IncomingTrafficMeter) Read(p []byte) (n int, err error) {
n, err = r.ReadCloser.Read(p)
r.countBytes += int64(n)
return n, err
}
// BytesRead returns the number of transferred bytes
func (r *IncomingTrafficMeter) BytesRead() int64 {
return r.countBytes
}
// OutgoingTrafficMeter counts the outgoing bytes through the responseWriter.
type OutgoingTrafficMeter struct {
countBytes int64
// wrapper for underlying http.ResponseWriter.
http.ResponseWriter
}
// Write calls the underlying write and counts the output bytes
func (w *OutgoingTrafficMeter) Write(p []byte) (n int, err error) {
n, err = w.ResponseWriter.Write(p)
w.countBytes += int64(n)
return n, err
}
// Flush calls the underlying Flush.
func (w *OutgoingTrafficMeter) Flush() {
w.ResponseWriter.(http.Flusher).Flush()
}
// BytesWritten returns the number of transferred bytes
func (w *OutgoingTrafficMeter) BytesWritten() int64 {
return w.countBytes
}
+9 -31
View File
@@ -24,11 +24,10 @@ import (
"strconv"
"time"
"github.com/minio/minio/internal/http/stats"
internalAudit "github.com/minio/minio/internal/logger/message/audit"
"github.com/minio/minio/internal/mcontext"
"github.com/minio/pkg/logger/message/audit"
"github.com/klauspost/compress/gzhttp"
"github.com/minio/madmin-go/v2"
xhttp "github.com/minio/minio/internal/http"
)
@@ -96,34 +95,13 @@ func AuditLog(ctx context.Context, w http.ResponseWriter, r *http.Request, reqCl
headerBytes int64
)
var st *xhttp.ResponseRecorder
for {
switch v := w.(type) {
case *gzhttp.GzipResponseWriter:
// the writer may be obscured by gzip response writer
w = v.ResponseWriter
case *gzhttp.NoGzipResponseWriter:
// the writer may be obscured by no-gzip response writer
w = v.ResponseWriter
case *stats.OutgoingTrafficMeter:
// the writer may be obscured by http stats response writer
w = v.ResponseWriter
case *xhttp.ResponseRecorder:
st = v
goto exit
default:
goto exit
}
}
exit:
if st != nil {
statusCode = st.StatusCode
timeToResponse = time.Now().UTC().Sub(st.StartTime)
timeToFirstByte = st.TimeToFirstByte
outputBytes = int64(st.Size())
headerBytes = int64(st.HeaderSize())
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
if ok {
statusCode = tc.ResponseRecorder.StatusCode
outputBytes = int64(tc.ResponseRecorder.Size())
headerBytes = int64(tc.ResponseRecorder.HeaderSize())
timeToResponse = time.Now().UTC().Sub(tc.ResponseRecorder.StartTime)
timeToFirstByte = tc.ResponseRecorder.TimeToFirstByte
}
entry.AccessKey = reqInfo.Cred.AccessKey
@@ -147,7 +125,7 @@ func AuditLog(ctx context.Context, w http.ResponseWriter, r *http.Request, reqCl
entry.API.TimeToResponse = strconv.FormatInt(timeToResponse.Nanoseconds(), 10) + "ns"
entry.API.TimeToResponseInNS = strconv.FormatInt(timeToResponse.Nanoseconds(), 10)
entry.Tags = reqInfo.GetTagsMap()
// ttfb will be recorded only for GET requests, Ignore such cases where ttfb will be empty.
// ignore cases for ttfb when its zero.
if timeToFirstByte != 0 {
entry.API.TimeToFirstByte = strconv.FormatInt(timeToFirstByte.Nanoseconds(), 10) + "ns"
entry.API.TimeToFirstByteInNS = strconv.FormatInt(timeToFirstByte.Nanoseconds(), 10)
+9 -10
View File
@@ -26,6 +26,7 @@ import (
"net"
"sync"
"sync/atomic"
"time"
"github.com/minio/pkg/logger/message/audit"
@@ -148,18 +149,16 @@ type Config struct {
}
// Check if atleast one broker in cluster is active
func (k Config) pingBrokers() error {
var err error
func (k Config) pingBrokers() (err error) {
d := net.Dialer{Timeout: 60 * time.Second}
for _, broker := range k.Brokers {
_, err1 := net.Dial("tcp", broker.String())
if err1 != nil {
if err == nil {
// Set first error
err = err1
}
}
}
_, err = d.Dial("tcp", broker.String())
if err != nil {
return err
}
}
return nil
}
// Stats returns the target statistics.