mirror of
https://github.com/pgsty/minio.git
synced 2026-07-20 04:30:26 +03:00
move madmin to github.com/minio/madmin-go (#12239)
This commit is contained in:
@@ -22,7 +22,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
"github.com/minio/madmin-go"
|
||||
)
|
||||
|
||||
// throttleBandwidth gets the throttle for bucket with the configured value
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
"github.com/minio/madmin-go"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+20
-39
@@ -24,37 +24,18 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/montanaflynn/stats"
|
||||
)
|
||||
|
||||
// Latency holds latency information for write operations to the drive
|
||||
type Latency struct {
|
||||
Avg float64 `json:"avg_secs,omitempty"`
|
||||
Percentile50 float64 `json:"percentile50_secs,omitempty"`
|
||||
Percentile90 float64 `json:"percentile90_secs,omitempty"`
|
||||
Percentile99 float64 `json:"percentile99_secs,omitempty"`
|
||||
Min float64 `json:"min_secs,omitempty"`
|
||||
Max float64 `json:"max_secs,omitempty"`
|
||||
}
|
||||
|
||||
// Throughput holds throughput information for write operations to the drive
|
||||
type Throughput struct {
|
||||
Avg float64 `json:"avg_bytes_per_sec,omitempty"`
|
||||
Percentile50 float64 `json:"percentile50_bytes_per_sec,omitempty"`
|
||||
Percentile90 float64 `json:"percentile90_bytes_per_sec,omitempty"`
|
||||
Percentile99 float64 `json:"percentile99_bytes_per_sec,omitempty"`
|
||||
Min float64 `json:"min_bytes_per_sec,omitempty"`
|
||||
Max float64 `json:"max_bytes_per_sec,omitempty"`
|
||||
}
|
||||
|
||||
// GetHealthInfo about the drive
|
||||
func GetHealthInfo(ctx context.Context, drive, fsPath string) (Latency, Throughput, error) {
|
||||
func GetHealthInfo(ctx context.Context, drive, fsPath string) (madmin.DiskLatency, madmin.DiskThroughput, error) {
|
||||
|
||||
// Create a file with O_DIRECT flag, choose default umask and also make sure
|
||||
// we are exclusively writing to a new file using O_EXCL.
|
||||
w, err := OpenFileDirectIO(fsPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -72,13 +53,13 @@ func GetHealthInfo(ctx context.Context, drive, fsPath string) (Latency, Throughp
|
||||
|
||||
for i := 0; i < (fileSize / blockSize); i++ {
|
||||
if ctx.Err() != nil {
|
||||
return Latency{}, Throughput{}, ctx.Err()
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, ctx.Err()
|
||||
}
|
||||
startTime := time.Now()
|
||||
if n, err := w.Write(data); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
} else if n != blockSize {
|
||||
return Latency{}, Throughput{}, fmt.Errorf("Expected to write %d, but only wrote %d", blockSize, n)
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, fmt.Errorf("Expected to write %d, but only wrote %d", blockSize, n)
|
||||
}
|
||||
latencyInSecs := time.Since(startTime).Seconds()
|
||||
latencies[i] = latencyInSecs
|
||||
@@ -107,24 +88,24 @@ func GetHealthInfo(ctx context.Context, drive, fsPath string) (Latency, Throughp
|
||||
var maxThroughput float64
|
||||
|
||||
if avgLatency, err = stats.Mean(latencies); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if percentile50Latency, err = stats.Percentile(latencies, 50); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if percentile90Latency, err = stats.Percentile(latencies, 90); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if percentile99Latency, err = stats.Percentile(latencies, 99); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if maxLatency, err = stats.Max(latencies); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if minLatency, err = stats.Min(latencies); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
l := Latency{
|
||||
l := madmin.DiskLatency{
|
||||
Avg: avgLatency,
|
||||
Percentile50: percentile50Latency,
|
||||
Percentile90: percentile90Latency,
|
||||
@@ -134,25 +115,25 @@ func GetHealthInfo(ctx context.Context, drive, fsPath string) (Latency, Throughp
|
||||
}
|
||||
|
||||
if avgThroughput, err = stats.Mean(throughputs); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if percentile50Throughput, err = stats.Percentile(throughputs, 50); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if percentile90Throughput, err = stats.Percentile(throughputs, 90); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if percentile99Throughput, err = stats.Percentile(throughputs, 99); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if maxThroughput, err = stats.Max(throughputs); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
if minThroughput, err = stats.Min(throughputs); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.DiskLatency{}, madmin.DiskThroughput{}, err
|
||||
}
|
||||
|
||||
t := Throughput{
|
||||
t := madmin.DiskThroughput{
|
||||
Avg: avgThroughput,
|
||||
Percentile50: percentile50Throughput,
|
||||
Percentile90: percentile90Throughput,
|
||||
|
||||
@@ -1,576 +0,0 @@
|
||||
# Golang Admin Client API Reference [](https://slack.min.io)
|
||||
The MinIO Admin Golang Client SDK provides APIs to manage MinIO services.
|
||||
|
||||
This quickstart guide will show you how to install the MinIO Admin client SDK, connect to MinIO admin service, and provide a walkthrough of a simple file uploader.
|
||||
|
||||
This document assumes that you have a working [Golang setup](https://golang.org/doc/install).
|
||||
|
||||
## Initialize MinIO Admin Client object.
|
||||
|
||||
## MinIO
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Use a secure connection.
|
||||
ssl := true
|
||||
|
||||
// Initialize minio client object.
|
||||
mdmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETKEY", ssl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch service status.
|
||||
st, err := mdmClnt.ServerInfo()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
for _, peerInfo := range serversInfo {
|
||||
log.Printf("Node: %s, Info: %v\n", peerInfo.Addr, peerInfo.Data)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
| Service operations | Info operations | Healing operations | Config operations |
|
||||
|:------------------------------------|:-----------------------------------------|:-------------------|:--------------------------|
|
||||
| [`ServiceTrace`](#ServiceTrace) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) |
|
||||
| [`ServiceStop`](#ServiceStop) | [`StorageInfo`](#StorageInfo) | | [`SetConfig`](#SetConfig) |
|
||||
| [`ServiceRestart`](#ServiceRestart) | [`AccountInfo`](#AccountInfo) | | |
|
||||
|
||||
|
||||
|
||||
| Top operations | IAM operations | Misc | KMS |
|
||||
|:------------------------|:--------------------------------------|:--------------------------------------------------|:--------------------------------|
|
||||
| [`TopLocks`](#TopLocks) | [`AddUser`](#AddUser) | [`StartProfiling`](#StartProfiling) | [`GetKeyStatus`](#GetKeyStatus) |
|
||||
| | [`SetUserPolicy`](#SetUserPolicy) | [`DownloadProfilingData`](#DownloadProfilingData) | |
|
||||
| | [`ListUsers`](#ListUsers) | [`ServerUpdate`](#ServerUpdate) | |
|
||||
| | [`AddCannedPolicy`](#AddCannedPolicy) | | |
|
||||
|
||||
## 1. Constructor
|
||||
<a name="MinIO"></a>
|
||||
|
||||
### New(endpoint string, accessKeyID string, secretAccessKey string, ssl bool) (*AdminClient, error)
|
||||
Initializes a new admin client object.
|
||||
|
||||
__Parameters__
|
||||
|
||||
| Param | Type | Description |
|
||||
|:------------------|:---------|:----------------------------------------------------------|
|
||||
| `endpoint` | _string_ | MinIO endpoint. |
|
||||
| `accessKeyID` | _string_ | Access key for the object storage endpoint. |
|
||||
| `secretAccessKey` | _string_ | Secret key for the object storage endpoint. |
|
||||
| `ssl` | _bool_ | Set this value to 'true' to enable secure (HTTPS) access. |
|
||||
|
||||
## 2. Service operations
|
||||
|
||||
<a name="ServiceStatus"></a>
|
||||
### ServiceStatus(ctx context.Context) (ServiceStatusMetadata, error)
|
||||
Fetch service status, replies disk space used, backend type and total disks offline/online (applicable in distributed mode).
|
||||
|
||||
| Param | Type | Description |
|
||||
|-----------------|-------------------------|------------------------------------------------------------|
|
||||
| `serviceStatus` | _ServiceStatusMetadata_ | Represents current server status info in following format: |
|
||||
|
||||
|
||||
| Param | Type | Description |
|
||||
|-----------------------------|-----------------|------------------------------------|
|
||||
| `st.ServerVersion.Version` | _string_ | Server version. |
|
||||
| `st.ServerVersion.CommitID` | _string_ | Server commit id. |
|
||||
| `st.Uptime` | _time.Duration_ | Server uptime duration in seconds. |
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
|
||||
st, err := madmClnt.ServiceStatus(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("%#v\n", st)
|
||||
|
||||
```
|
||||
|
||||
<a name="ServiceRestart"></a>
|
||||
### ServiceRestart(ctx context.Context) error
|
||||
Sends a service action restart command to MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
// To restart the service, restarts all servers in the cluster.
|
||||
err := madmClnt.ServiceRestart(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
```
|
||||
|
||||
<a name="ServiceStop"></a>
|
||||
### ServiceStop(ctx context.Context) error
|
||||
Sends a service action stop command to MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
// To stop the service, stops all servers in the cluster.
|
||||
err := madmClnt.ServiceStop(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
```
|
||||
|
||||
<a name="ServiceTrace"></a>
|
||||
### ServiceTrace(ctx context.Context, allTrace bool, doneCh <-chan struct{}) <-chan TraceInfo
|
||||
Enable HTTP request tracing on all nodes in a MinIO cluster
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
// listen to all trace including internal API calls
|
||||
allTrace := true
|
||||
// Start listening on all trace activity.
|
||||
traceCh := madmClnt.ServiceTrace(context.Background(), allTrace, doneCh)
|
||||
for traceInfo := range traceCh {
|
||||
fmt.Println(traceInfo.String())
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Info operations
|
||||
|
||||
<a name="ServerInfo"></a>
|
||||
### ServerInfo(ctx context.Context) ([]ServerInfo, error)
|
||||
Fetches information for all cluster nodes, such as server properties, storage information, network statistics, etc.
|
||||
|
||||
| Param | Type | Description |
|
||||
|----------------------------------|--------------------|--------------------------------------------------------------------|
|
||||
| `si.Addr` | _string_ | Address of the server the following information is retrieved from. |
|
||||
| `si.ConnStats` | _ServerConnStats_ | Connection statistics from the given server. |
|
||||
| `si.HTTPStats` | _ServerHTTPStats_ | HTTP connection statistics from the given server. |
|
||||
| `si.Properties` | _ServerProperties_ | Server properties such as region, notification targets. |
|
||||
|
||||
| Param | Type | Description |
|
||||
|-----------------------------|-----------------|----------------------------------------------------|
|
||||
| `ServerProperties.Uptime` | _time.Duration_ | Total duration in seconds since server is running. |
|
||||
| `ServerProperties.Version` | _string_ | Current server version. |
|
||||
| `ServerProperties.CommitID` | _string_ | Current server commitID. |
|
||||
| `ServerProperties.Region` | _string_ | Configured server region. |
|
||||
| `ServerProperties.SQSARN` | _[]string_ | List of notification target ARNs. |
|
||||
|
||||
| Param | Type | Description |
|
||||
|------------------------------------|----------|-------------------------------------|
|
||||
| `ServerConnStats.TotalInputBytes` | _uint64_ | Total bytes received by the server. |
|
||||
| `ServerConnStats.TotalOutputBytes` | _uint64_ | Total bytes sent by the server. |
|
||||
|
||||
| Param | Type | Description |
|
||||
|--------------------------------------|-------------------------|---------------------------------------------------------|
|
||||
| `ServerHTTPStats.TotalHEADStats` | _ServerHTTPMethodStats_ | Total statistics regarding HEAD operations |
|
||||
| `ServerHTTPStats.SuccessHEADStats` | _ServerHTTPMethodStats_ | Total statistics regarding successful HEAD operations |
|
||||
| `ServerHTTPStats.TotalGETStats` | _ServerHTTPMethodStats_ | Total statistics regarding GET operations |
|
||||
| `ServerHTTPStats.SuccessGETStats` | _ServerHTTPMethodStats_ | Total statistics regarding successful GET operations |
|
||||
| `ServerHTTPStats.TotalPUTStats` | _ServerHTTPMethodStats_ | Total statistics regarding PUT operations |
|
||||
| `ServerHTTPStats.SuccessPUTStats` | _ServerHTTPMethodStats_ | Total statistics regarding successful PUT operations |
|
||||
| `ServerHTTPStats.TotalPOSTStats` | _ServerHTTPMethodStats_ | Total statistics regarding POST operations |
|
||||
| `ServerHTTPStats.SuccessPOSTStats` | _ServerHTTPMethodStats_ | Total statistics regarding successful POST operations |
|
||||
| `ServerHTTPStats.TotalDELETEStats` | _ServerHTTPMethodStats_ | Total statistics regarding DELETE operations |
|
||||
| `ServerHTTPStats.SuccessDELETEStats` | _ServerHTTPMethodStats_ | Total statistics regarding successful DELETE operations |
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------------------------------------|----------|-------------------------------------------------|
|
||||
| `ServerHTTPMethodStats.Count` | _uint64_ | Total number of operations. |
|
||||
| `ServerHTTPMethodStats.AvgDuration` | _string_ | Average duration of Count number of operations. |
|
||||
|
||||
| Param | Type | Description |
|
||||
|----------------------|----------|-------------------------------------------------------|
|
||||
| `DriveInfo.UUID` | _string_ | Unique ID for each disk provisioned by server format. |
|
||||
| `DriveInfo.Endpoint` | _string_ | Endpoint location of the remote/local disk. |
|
||||
| `DriveInfo.State` | _string_ | Current state of the disk at endpoint. |
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
|
||||
serversInfo, err := madmClnt.ServerInfo(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for _, peerInfo := range serversInfo {
|
||||
log.Printf("Node: %s, Info: %v\n", peerInfo.Addr, peerInfo.Data)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<a name="StorageInfo"></a>
|
||||
### StorageInfo(ctx context.Context) (StorageInfo, error)
|
||||
|
||||
Fetches Storage information for all cluster nodes.
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------------------------|------------|---------------------------------------------|
|
||||
| `storageInfo.Used` | _[]int64_ | Used disk spaces. |
|
||||
| `storageInfo.Total` | _[]int64_ | Total disk spaces. |
|
||||
| `storageInfo.Available` | _[]int64_ | Available disk spaces. |
|
||||
| `StorageInfo.Backend` | _struct{}_ | Represents backend type embedded structure. |
|
||||
|
||||
| Param | Type | Description |
|
||||
|----------------------------|-----------------|--------------------------------------------------------------------------------------------------------------------------|
|
||||
| `Backend.Type` | _BackendType_ | Type of backend used by the server currently only FS or Erasure. |
|
||||
| `Backend.OnlineDisks` | _BackendDisks_ | Total number of disks online per node (only applies to Erasure backend) represented in map[string]int, is empty for FS. |
|
||||
| `Backend.OfflineDisks` | _BackendDisks_ | Total number of disks offline per node (only applies to Erasure backend) represented in map[string]int, is empty for FS. |
|
||||
| `Backend.StandardSCParity` | _int_ | Parity disks set for standard storage class, is empty for FS. |
|
||||
| `Backend.RRSCParity` | _int_ | Parity disks set for reduced redundancy storage class, is empty for FS. |
|
||||
| `Backend.Sets` | _[][]DriveInfo_ | Represents topology of drives in erasure coded sets. |
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
|
||||
storageInfo, err := madmClnt.StorageInfo(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Println(storageInfo)
|
||||
|
||||
```
|
||||
|
||||
<a name="AccountInfo"></a>
|
||||
|
||||
### AccountInfo(ctx context.Context) (AccountInfo, error)
|
||||
|
||||
Fetches accounting usage information for the current authenticated user
|
||||
|
||||
| Param | Type | Description |
|
||||
|--------------------------------|----------------------|-------------------------|
|
||||
| `AccountInfo.AccountName` | _string_ | Account name. |
|
||||
| `AccountInfo.Buckets` | _[]BucketAccessInfo_ | Bucket usage info. |
|
||||
|
||||
|
||||
| Param | Type | Description |
|
||||
|----------------------------|-----------------|-----------------------------------------|
|
||||
| `BucketAccessInfo.Name` | _string_ | The name of the current bucket
|
||||
| `BucketAccessInfo.Size` | _uint64_ | The total size of the current bucket
|
||||
| `BucketAccessInfo.Created` | _time.Time_ | Bucket creation time
|
||||
| `BucketAccessInfo.Access` | _AccountAccess_ | Type of access of the current account
|
||||
|
||||
|
||||
| Param | Type | Description |
|
||||
|------------------------|---------|------------------------------------------------------------------|
|
||||
| `AccountAccess.Read` | _bool_ | Indicate if the bucket is readable by the current account name. |
|
||||
| `AccountAccess.Write` | _bool_ | Indocate if the bucket is writable by the current account name. |
|
||||
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
|
||||
accountInfo, err := madmClnt.AccountInfo(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Println(accountInfo)
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 5. Heal operations
|
||||
|
||||
<a name="Heal"></a>
|
||||
### Heal(ctx context.Context, bucket, prefix string, healOpts HealOpts, clientToken string, forceStart bool, forceStop bool) (start HealStartSuccess, status HealTaskStatus, err error)
|
||||
|
||||
Start a heal sequence that scans data under given (possible empty)
|
||||
`bucket` and `prefix`. The `recursive` bool turns on recursive
|
||||
traversal under the given path. `dryRun` does not mutate on-disk data,
|
||||
but performs data validation.
|
||||
|
||||
Two heal sequences on overlapping paths may not be initiated.
|
||||
|
||||
The progress of a heal should be followed using the same API `Heal`
|
||||
by providing the `clientToken` previously obtained from a `Heal`
|
||||
API. The server accumulates results of the heal traversal and waits
|
||||
for the client to receive and acknowledge them using the status
|
||||
request by providing `clientToken`.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
|
||||
opts := madmin.HealOpts{
|
||||
Recursive: true,
|
||||
DryRun: false,
|
||||
}
|
||||
forceStart := false
|
||||
forceStop := false
|
||||
healPath, err := madmClnt.Heal(context.Background(), "", "", opts, "", forceStart, forceStop)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("Heal sequence started at %s", healPath)
|
||||
|
||||
```
|
||||
|
||||
#### HealStartSuccess structure
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `s.ClientToken` | _string_ | A unique token for a successfully started heal operation, this token is used to request realtime progress of the heal operation. |
|
||||
| `s.ClientAddress` | _string_ | Address of the client which initiated the heal operation, the client address has the form "host:port". |
|
||||
| `s.StartTime` | _time.Time_ | Time when heal was initially started. |
|
||||
|
||||
#### HealTaskStatus structure
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------------------|--------------------|---------------------------------------------------|
|
||||
| `s.Summary` | _string_ | Short status of heal sequence |
|
||||
| `s.FailureDetail` | _string_ | Error message in case of heal sequence failure |
|
||||
| `s.HealSettings` | _HealOpts_ | Contains the booleans set in the `HealStart` call |
|
||||
| `s.Items` | _[]HealResultItem_ | Heal records for actions performed by server |
|
||||
|
||||
#### HealResultItem structure
|
||||
|
||||
| Param | Type | Description |
|
||||
|------------------------|----------------|-----------------------------------------------------------------|
|
||||
| `ResultIndex` | _int64_ | Index of the heal-result record |
|
||||
| `Type` | _HealItemType_ | Represents kind of heal operation in the heal record |
|
||||
| `Bucket` | _string_ | Bucket name |
|
||||
| `Object` | _string_ | Object name |
|
||||
| `Detail` | _string_ | Details about heal operation |
|
||||
| `DiskInfo.AvailableOn` | _[]int_ | List of disks on which the healed entity is present and healthy |
|
||||
| `DiskInfo.HealedOn` | _[]int_ | List of disks on which the healed entity was restored |
|
||||
|
||||
## 6. Config operations
|
||||
|
||||
<a name="GetConfig"></a>
|
||||
### GetConfig(ctx context.Context) ([]byte, error)
|
||||
Get current `config.json` of a MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
configBytes, err := madmClnt.GetConfig(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("failed due to: %v", err)
|
||||
}
|
||||
|
||||
// Pretty-print config received as json.
|
||||
var buf bytes.Buffer
|
||||
err = json.Indent(buf, configBytes, "", "\t")
|
||||
if err != nil {
|
||||
log.Fatalf("failed due to: %v", err)
|
||||
}
|
||||
|
||||
log.Println("config received successfully: ", string(buf.Bytes()))
|
||||
```
|
||||
|
||||
|
||||
<a name="SetConfig"></a>
|
||||
### SetConfig(ctx context.Context, config io.Reader) error
|
||||
Set a new `config.json` for a MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
config := bytes.NewReader([]byte(`config.json contents go here`))
|
||||
if err := madmClnt.SetConfig(context.Background(), config); err != nil {
|
||||
log.Fatalf("failed due to: %v", err)
|
||||
}
|
||||
log.Println("SetConfig was successful")
|
||||
```
|
||||
|
||||
## 7. Top operations
|
||||
|
||||
<a name="TopLocks"></a>
|
||||
### TopLocks(ctx context.Context) (LockEntries, error)
|
||||
Get the oldest locks from MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
locks, err := madmClnt.TopLocks(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("failed due to: %v", err)
|
||||
}
|
||||
|
||||
out, err := json.Marshal(locks)
|
||||
if err != nil {
|
||||
log.Fatalf("Marshal failed due to: %v", err)
|
||||
}
|
||||
|
||||
log.Println("TopLocks received successfully: ", string(out))
|
||||
```
|
||||
|
||||
## 8. IAM operations
|
||||
|
||||
<a name="AddCannedPolicy"></a>
|
||||
### AddCannedPolicy(ctx context.Context, policyName string, policy *iampolicy.Policy) error
|
||||
Create a new canned policy on MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
```
|
||||
policy, err := iampolicy.ParseConfig(strings.NewReader(`{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject"],"Effect": "Allow","Resource": ["arn:aws:s3:::my-bucketname/*"],"Sid": ""}]}`))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if err = madmClnt.AddCannedPolicy(context.Background(), "get-only", policy); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="AddUser"></a>
|
||||
### AddUser(ctx context.Context, user string, secret string) error
|
||||
Add a new user on a MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
if err = madmClnt.AddUser(context.Background(), "newuser", "newstrongpassword"); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="SetUserPolicy"></a>
|
||||
### SetUserPolicy(ctx context.Context, user string, policyName string) error
|
||||
Enable a canned policy `get-only` for a given user on MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
if err = madmClnt.SetUserPolicy(context.Background(), "newuser", "get-only"); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="ListUsers"></a>
|
||||
### ListUsers(ctx context.Context) (map[string]UserInfo, error)
|
||||
Lists all users on MinIO server.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
users, err := madmClnt.ListUsers(context.Background());
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for k, v := range users {
|
||||
fmt.Printf("User %s Status %s\n", k, v.Status)
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Misc operations
|
||||
|
||||
<a name="ServerUpdate"></a>
|
||||
### ServerUpdate(ctx context.Context, updateURL string) (ServerUpdateStatus, error)
|
||||
Sends a update command to MinIO server, to update MinIO server to latest release. In distributed setup it updates all servers atomically.
|
||||
|
||||
__Example__
|
||||
|
||||
```go
|
||||
// Updates all servers and restarts all the servers in the cluster.
|
||||
// optionally takes an updateURL, which is used to update the binary.
|
||||
us, err := madmClnt.ServerUpdate(context.Background(), updateURL)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if us.CurrentVersion != us.UpdatedVersion {
|
||||
log.Printf("Updated server version from %s to %s successfully", us.CurrentVersion, us.UpdatedVersion)
|
||||
}
|
||||
```
|
||||
|
||||
<a name="StartProfiling"></a>
|
||||
### StartProfiling(ctx context.Context, profiler string) error
|
||||
Ask all nodes to start profiling using the specified profiler mode
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
startProfilingResults, err = madmClnt.StartProfiling(context.Background(), "cpu")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for _, result := range startProfilingResults {
|
||||
if !result.Success {
|
||||
log.Printf("Unable to start profiling on node `%s`, reason = `%s`\n", result.NodeName, result.Error)
|
||||
} else {
|
||||
log.Printf("Profiling successfully started on node `%s`\n", result.NodeName)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<a name="DownloadProfilingData"></a>
|
||||
### DownloadProfilingData(ctx context.Context) ([]byte, error)
|
||||
Download profiling data of all nodes in a zip format.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
profilingData, err := madmClnt.DownloadProfilingData(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
profilingFile, err := os.Create("/tmp/profiling-data.zip")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(profilingFile, profilingData); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := profilingFile.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := profilingData.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Profiling data successfully downloaded.")
|
||||
```
|
||||
|
||||
## 11. KMS
|
||||
|
||||
<a name="GetKeyStatus"></a>
|
||||
### GetKeyStatus(ctx context.Context, keyID string) (*KMSKeyStatus, error)
|
||||
Requests status information about one particular KMS master key
|
||||
from a MinIO server. The keyID is optional and the server will
|
||||
use the default master key (configured via `MINIO_KMS_VAULT_KEY_NAME`
|
||||
or `MINIO_KMS_MASTER_KEY`) if the keyID is empty.
|
||||
|
||||
__Example__
|
||||
|
||||
``` go
|
||||
keyInfo, err := madmClnt.GetKeyStatus(context.Background(), "my-minio-key")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if keyInfo.EncryptionErr != "" {
|
||||
log.Fatalf("Failed to perform encryption operation using '%s': %v\n", keyInfo.KeyID, keyInfo.EncryptionErr)
|
||||
}
|
||||
if keyInfo.UpdateErr != "" {
|
||||
log.Fatalf("Failed to perform key re-wrap operation using '%s': %v\n", keyInfo.KeyID, keyInfo.UpdateErr)
|
||||
}
|
||||
if keyInfo.DecryptionErr != "" {
|
||||
log.Fatalf("Failed to perform decryption operation using '%s': %v\n", keyInfo.KeyID, keyInfo.DecryptionErr)
|
||||
}
|
||||
```
|
||||
@@ -1,111 +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 madmin
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/* **** SAMPLE ERROR RESPONSE ****
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Error>
|
||||
<Code>AccessDenied</Code>
|
||||
<Message>Access Denied</Message>
|
||||
<BucketName>bucketName</BucketName>
|
||||
<Key>objectName</Key>
|
||||
<RequestId>F19772218238A85A</RequestId>
|
||||
<HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
|
||||
</Error>
|
||||
*/
|
||||
|
||||
// ErrorResponse - Is the typed error returned by all API operations.
|
||||
type ErrorResponse struct {
|
||||
XMLName xml.Name `xml:"Error" json:"-"`
|
||||
Code string
|
||||
Message string
|
||||
BucketName string
|
||||
Key string
|
||||
RequestID string `xml:"RequestId"`
|
||||
HostID string `xml:"HostId"`
|
||||
|
||||
// Region where the bucket is located. This header is returned
|
||||
// only in HEAD bucket and ListObjects response.
|
||||
Region string
|
||||
}
|
||||
|
||||
// Error - Returns HTTP error string
|
||||
func (e ErrorResponse) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
const (
|
||||
reportIssue = "Please report this issue at https://github.com/minio/minio/issues."
|
||||
)
|
||||
|
||||
// httpRespToErrorResponse returns a new encoded ErrorResponse
|
||||
// structure as error.
|
||||
func httpRespToErrorResponse(resp *http.Response) error {
|
||||
if resp == nil {
|
||||
msg := "Response is empty. " + reportIssue
|
||||
return ErrInvalidArgument(msg)
|
||||
}
|
||||
var errResp ErrorResponse
|
||||
// Decode the json error
|
||||
err := jsonDecoder(resp.Body, &errResp)
|
||||
if err != nil {
|
||||
return ErrorResponse{
|
||||
Code: resp.Status,
|
||||
Message: fmt.Sprintf("Failed to parse server response: %s.", err),
|
||||
}
|
||||
}
|
||||
closeResponse(resp)
|
||||
return errResp
|
||||
}
|
||||
|
||||
// ToErrorResponse - Returns parsed ErrorResponse struct from body and
|
||||
// http headers.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// import admin "github.com/minio/minio/pkg/madmin"
|
||||
// ...
|
||||
// ...
|
||||
// ss, err := adm.ServiceStatus(...)
|
||||
// if err != nil {
|
||||
// resp := admin.ToErrorResponse(err)
|
||||
// }
|
||||
// ...
|
||||
func ToErrorResponse(err error) ErrorResponse {
|
||||
switch err := err.(type) {
|
||||
case ErrorResponse:
|
||||
return err
|
||||
default:
|
||||
return ErrorResponse{}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrInvalidArgument - Invalid argument response.
|
||||
func ErrInvalidArgument(message string) error {
|
||||
return ErrorResponse{
|
||||
Code: "InvalidArgument",
|
||||
Message: message,
|
||||
RequestID: "minio",
|
||||
}
|
||||
}
|
||||
@@ -1,53 +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 madmin
|
||||
|
||||
// Args - defines the arguments for the API.
|
||||
type logArgs struct {
|
||||
Bucket string `json:"bucket,omitempty"`
|
||||
Object string `json:"object,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Trace - defines the trace.
|
||||
type logTrace struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Source []string `json:"source,omitempty"`
|
||||
Variables map[string]string `json:"variables,omitempty"`
|
||||
}
|
||||
|
||||
// API - defines the api type and its args.
|
||||
type logAPI struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Args *logArgs `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
// Entry - defines fields and values of each log entry.
|
||||
type logEntry struct {
|
||||
DeploymentID string `json:"deploymentid,omitempty"`
|
||||
Level string `json:"level"`
|
||||
LogKind string `json:"errKind"`
|
||||
Time string `json:"time"`
|
||||
API *logAPI `json:"api,omitempty"`
|
||||
RemoteHost string `json:"remotehost,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
RequestID string `json:"requestID,omitempty"`
|
||||
UserAgent string `json:"userAgent,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Trace *logTrace `json:"error,omitempty"`
|
||||
}
|
||||
@@ -1,81 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// LogInfo holds console log messages
|
||||
type LogInfo struct {
|
||||
logEntry
|
||||
ConsoleMsg string
|
||||
NodeName string `json:"node"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// GetLogs - listen on console log messages.
|
||||
func (adm AdminClient) GetLogs(ctx context.Context, node string, lineCnt int, logKind string) <-chan LogInfo {
|
||||
logCh := make(chan LogInfo, 1)
|
||||
|
||||
// Only success, start a routine to start reading line by line.
|
||||
go func(logCh chan<- LogInfo) {
|
||||
defer close(logCh)
|
||||
urlValues := make(url.Values)
|
||||
urlValues.Set("node", node)
|
||||
urlValues.Set("limit", strconv.Itoa(lineCnt))
|
||||
urlValues.Set("logType", logKind)
|
||||
for {
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/log",
|
||||
queryValues: urlValues,
|
||||
}
|
||||
// Execute GET to call log handler
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
if err != nil {
|
||||
closeResponse(resp)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logCh <- LogInfo{Err: httpRespToErrorResponse(resp)}
|
||||
return
|
||||
}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var info LogInfo
|
||||
if err = dec.Decode(&info); err != nil {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case logCh <- info:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}(logCh)
|
||||
|
||||
// Returns the log info channel, for caller to start reading from.
|
||||
return logCh
|
||||
}
|
||||
@@ -1,510 +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 madmin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/signer"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// AdminClient implements Amazon S3 compatible methods.
|
||||
type AdminClient struct {
|
||||
/// Standard options.
|
||||
|
||||
// Parsed endpoint url provided by the user.
|
||||
endpointURL *url.URL
|
||||
|
||||
// Holds various credential providers.
|
||||
credsProvider *credentials.Credentials
|
||||
|
||||
// User supplied.
|
||||
appInfo struct {
|
||||
appName string
|
||||
appVersion string
|
||||
}
|
||||
|
||||
// Indicate whether we are using https or not
|
||||
secure bool
|
||||
|
||||
// Needs allocation.
|
||||
httpClient *http.Client
|
||||
|
||||
random *rand.Rand
|
||||
|
||||
// Advanced functionality.
|
||||
isTraceEnabled bool
|
||||
traceOutput io.Writer
|
||||
}
|
||||
|
||||
// Global constants.
|
||||
const (
|
||||
libraryName = "madmin-go"
|
||||
libraryVersion = "0.0.1"
|
||||
|
||||
libraryAdminURLPrefix = "/minio/admin"
|
||||
)
|
||||
|
||||
// User Agent should always following the below style.
|
||||
// Please open an issue to discuss any new changes here.
|
||||
//
|
||||
// MinIO (OS; ARCH) LIB/VER APP/VER
|
||||
const (
|
||||
libraryUserAgentPrefix = "MinIO (" + runtime.GOOS + "; " + runtime.GOARCH + ") "
|
||||
libraryUserAgent = libraryUserAgentPrefix + libraryName + "/" + libraryVersion
|
||||
)
|
||||
|
||||
// Options for New method
|
||||
type Options struct {
|
||||
Creds *credentials.Credentials
|
||||
Secure bool
|
||||
// Add future fields here
|
||||
}
|
||||
|
||||
// New - instantiate minio admin client
|
||||
func New(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*AdminClient, error) {
|
||||
creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "")
|
||||
|
||||
clnt, err := privateNew(endpoint, creds, secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clnt, nil
|
||||
}
|
||||
|
||||
// NewWithOptions - instantiate minio admin client with options.
|
||||
func NewWithOptions(endpoint string, opts *Options) (*AdminClient, error) {
|
||||
clnt, err := privateNew(endpoint, opts.Creds, opts.Secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clnt, nil
|
||||
}
|
||||
|
||||
func privateNew(endpoint string, creds *credentials.Credentials, secure bool) (*AdminClient, error) {
|
||||
// Initialize cookies to preserve server sent cookies if any and replay
|
||||
// them upon each request.
|
||||
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// construct endpoint.
|
||||
endpointURL, err := getEndpointURL(endpoint, secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clnt := new(AdminClient)
|
||||
|
||||
// Save the credentials.
|
||||
clnt.credsProvider = creds
|
||||
|
||||
// Remember whether we are using https or not
|
||||
clnt.secure = secure
|
||||
|
||||
// Save endpoint URL, user agent for future uses.
|
||||
clnt.endpointURL = endpointURL
|
||||
|
||||
// Instantiate http client and bucket location cache.
|
||||
clnt.httpClient = &http.Client{
|
||||
Jar: jar,
|
||||
Transport: DefaultTransport(secure),
|
||||
}
|
||||
|
||||
// Add locked pseudo-random number generator.
|
||||
clnt.random = rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UTC().UnixNano())})
|
||||
|
||||
// Return.
|
||||
return clnt, nil
|
||||
}
|
||||
|
||||
// SetAppInfo - add application details to user agent.
|
||||
func (adm *AdminClient) SetAppInfo(appName string, appVersion string) {
|
||||
// if app name and version is not set, we do not a new user
|
||||
// agent.
|
||||
if appName != "" && appVersion != "" {
|
||||
adm.appInfo.appName = appName
|
||||
adm.appInfo.appVersion = appVersion
|
||||
}
|
||||
}
|
||||
|
||||
// SetCustomTransport - set new custom transport.
|
||||
func (adm *AdminClient) SetCustomTransport(customHTTPTransport http.RoundTripper) {
|
||||
// Set this to override default transport
|
||||
// ``http.DefaultTransport``.
|
||||
//
|
||||
// This transport is usually needed for debugging OR to add your
|
||||
// own custom TLS certificates on the client transport, for custom
|
||||
// CA's and certs which are not part of standard certificate
|
||||
// authority follow this example :-
|
||||
//
|
||||
// tr := &http.Transport{
|
||||
// TLSClientConfig: &tls.Config{RootCAs: pool},
|
||||
// DisableCompression: true,
|
||||
// }
|
||||
// api.SetTransport(tr)
|
||||
//
|
||||
if adm.httpClient != nil {
|
||||
adm.httpClient.Transport = customHTTPTransport
|
||||
}
|
||||
}
|
||||
|
||||
// TraceOn - enable HTTP tracing.
|
||||
func (adm *AdminClient) TraceOn(outputStream io.Writer) {
|
||||
// if outputStream is nil then default to os.Stdout.
|
||||
if outputStream == nil {
|
||||
outputStream = os.Stdout
|
||||
}
|
||||
// Sets a new output stream.
|
||||
adm.traceOutput = outputStream
|
||||
|
||||
// Enable tracing.
|
||||
adm.isTraceEnabled = true
|
||||
}
|
||||
|
||||
// TraceOff - disable HTTP tracing.
|
||||
func (adm *AdminClient) TraceOff() {
|
||||
// Disable tracing.
|
||||
adm.isTraceEnabled = false
|
||||
}
|
||||
|
||||
// requestMetadata - is container for all the values to make a
|
||||
// request.
|
||||
type requestData struct {
|
||||
customHeaders http.Header
|
||||
queryValues url.Values
|
||||
relPath string // URL path relative to admin API base endpoint
|
||||
content []byte
|
||||
}
|
||||
|
||||
// Filter out signature value from Authorization header.
|
||||
func (adm AdminClient) filterSignature(req *http.Request) {
|
||||
/// Signature V4 authorization header.
|
||||
|
||||
// Save the original auth.
|
||||
origAuth := req.Header.Get("Authorization")
|
||||
// Strip out accessKeyID from:
|
||||
// Credential=<access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request
|
||||
regCred := regexp.MustCompile("Credential=([A-Z0-9]+)/")
|
||||
newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/")
|
||||
|
||||
// Strip out 256-bit signature from: Signature=<256-bit signature>
|
||||
regSign := regexp.MustCompile("Signature=([[0-9a-f]+)")
|
||||
newAuth = regSign.ReplaceAllString(newAuth, "Signature=**REDACTED**")
|
||||
|
||||
// Set a temporary redacted auth
|
||||
req.Header.Set("Authorization", newAuth)
|
||||
}
|
||||
|
||||
// dumpHTTP - dump HTTP request and response.
|
||||
func (adm AdminClient) dumpHTTP(req *http.Request, resp *http.Response) error {
|
||||
// Starts http dump.
|
||||
_, err := fmt.Fprintln(adm.traceOutput, "---------START-HTTP---------")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Filter out Signature field from Authorization header.
|
||||
adm.filterSignature(req)
|
||||
|
||||
// Only display request header.
|
||||
reqTrace, err := httputil.DumpRequestOut(req, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write request to trace output.
|
||||
_, err = fmt.Fprint(adm.traceOutput, string(reqTrace))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only display response header.
|
||||
var respTrace []byte
|
||||
|
||||
// For errors we make sure to dump response body as well.
|
||||
if resp.StatusCode != http.StatusOK &&
|
||||
resp.StatusCode != http.StatusPartialContent &&
|
||||
resp.StatusCode != http.StatusNoContent {
|
||||
respTrace, err = httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// WORKAROUND for https://github.com/golang/go/issues/13942.
|
||||
// httputil.DumpResponse does not print response headers for
|
||||
// all successful calls which have response ContentLength set
|
||||
// to zero. Keep this workaround until the above bug is fixed.
|
||||
if resp.ContentLength == 0 {
|
||||
var buffer bytes.Buffer
|
||||
if err = resp.Header.Write(&buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
respTrace = buffer.Bytes()
|
||||
respTrace = append(respTrace, []byte("\r\n")...)
|
||||
} else {
|
||||
respTrace, err = httputil.DumpResponse(resp, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// Write response to trace output.
|
||||
_, err = fmt.Fprint(adm.traceOutput, strings.TrimSuffix(string(respTrace), "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ends the http dump.
|
||||
_, err = fmt.Fprintln(adm.traceOutput, "---------END-HTTP---------")
|
||||
return err
|
||||
}
|
||||
|
||||
// do - execute http request.
|
||||
func (adm AdminClient) do(req *http.Request) (*http.Response, error) {
|
||||
resp, err := adm.httpClient.Do(req)
|
||||
if err != nil {
|
||||
// Handle this specifically for now until future Golang versions fix this issue properly.
|
||||
if urlErr, ok := err.(*url.Error); ok {
|
||||
if strings.Contains(urlErr.Err.Error(), "EOF") {
|
||||
return nil, &url.Error{
|
||||
Op: urlErr.Op,
|
||||
URL: urlErr.URL,
|
||||
Err: errors.New("Connection closed by foreign host " + urlErr.URL + ". Retry again."),
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Response cannot be non-nil, report if its the case.
|
||||
if resp == nil {
|
||||
msg := "Response is empty. " // + reportIssue
|
||||
return nil, ErrInvalidArgument(msg)
|
||||
}
|
||||
|
||||
// If trace is enabled, dump http request and response.
|
||||
if adm.isTraceEnabled {
|
||||
err = adm.dumpHTTP(req, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// List of success status.
|
||||
var successStatus = []int{
|
||||
http.StatusOK,
|
||||
http.StatusNoContent,
|
||||
http.StatusPartialContent,
|
||||
}
|
||||
|
||||
// executeMethod - instantiates a given method, and retries the
|
||||
// request upon any error up to maxRetries attempts in a binomially
|
||||
// delayed manner using a standard back off algorithm.
|
||||
func (adm AdminClient) executeMethod(ctx context.Context, method string, reqData requestData) (res *http.Response, err error) {
|
||||
var reqRetry = MaxRetry // Indicates how many times we can retry the request
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// close idle connections before returning, upon error.
|
||||
adm.httpClient.CloseIdleConnections()
|
||||
}
|
||||
}()
|
||||
|
||||
// Create cancel context to control 'newRetryTimer' go routine.
|
||||
retryCtx, cancel := context.WithCancel(ctx)
|
||||
|
||||
// Indicate to our routine to exit cleanly upon return.
|
||||
defer cancel()
|
||||
|
||||
for range adm.newRetryTimer(retryCtx, reqRetry, DefaultRetryUnit, DefaultRetryCap, MaxJitter) {
|
||||
// Instantiate a new request.
|
||||
var req *http.Request
|
||||
req, err = adm.newRequest(ctx, method, reqData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initiate the request.
|
||||
res, err = adm.do(req)
|
||||
if err != nil {
|
||||
// Give up right away if it is a connection refused problem
|
||||
if errors.Is(err, syscall.ECONNREFUSED) {
|
||||
return nil, err
|
||||
}
|
||||
if err == context.Canceled || err == context.DeadlineExceeded {
|
||||
return nil, err
|
||||
}
|
||||
// retry all network errors.
|
||||
continue
|
||||
}
|
||||
|
||||
// For any known successful http status, return quickly.
|
||||
for _, httpStatus := range successStatus {
|
||||
if httpStatus == res.StatusCode {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Read the body to be saved later.
|
||||
errBodyBytes, err := ioutil.ReadAll(res.Body)
|
||||
// res.Body should be closed
|
||||
closeResponse(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save the body.
|
||||
errBodySeeker := bytes.NewReader(errBodyBytes)
|
||||
res.Body = ioutil.NopCloser(errBodySeeker)
|
||||
|
||||
// For errors verify if its retryable otherwise fail quickly.
|
||||
errResponse := ToErrorResponse(httpRespToErrorResponse(res))
|
||||
|
||||
// Save the body back again.
|
||||
errBodySeeker.Seek(0, 0) // Seek back to starting point.
|
||||
res.Body = ioutil.NopCloser(errBodySeeker)
|
||||
|
||||
// Verify if error response code is retryable.
|
||||
if isS3CodeRetryable(errResponse.Code) {
|
||||
continue // Retry.
|
||||
}
|
||||
|
||||
// Verify if http status code is retryable.
|
||||
if isHTTPStatusRetryable(res.StatusCode) {
|
||||
continue // Retry.
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// Return an error when retry is canceled or deadlined
|
||||
if e := retryCtx.Err(); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// set User agent.
|
||||
func (adm AdminClient) setUserAgent(req *http.Request) {
|
||||
req.Header.Set("User-Agent", libraryUserAgent)
|
||||
if adm.appInfo.appName != "" && adm.appInfo.appVersion != "" {
|
||||
req.Header.Set("User-Agent", libraryUserAgent+" "+adm.appInfo.appName+"/"+adm.appInfo.appVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func (adm AdminClient) getSecretKey() string {
|
||||
value, err := adm.credsProvider.Get()
|
||||
if err != nil {
|
||||
// Return empty, call will fail.
|
||||
return ""
|
||||
}
|
||||
|
||||
return value.SecretAccessKey
|
||||
}
|
||||
|
||||
// newRequest - instantiate a new HTTP request for a given method.
|
||||
func (adm AdminClient) newRequest(ctx context.Context, method string, reqData requestData) (req *http.Request, err error) {
|
||||
// If no method is supplied default to 'POST'.
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
// Default all requests to ""
|
||||
location := ""
|
||||
|
||||
// Construct a new target URL.
|
||||
targetURL, err := adm.makeTargetURL(reqData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize a new HTTP request for the method.
|
||||
req, err = http.NewRequestWithContext(ctx, method, targetURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value, err := adm.credsProvider.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
accessKeyID = value.AccessKeyID
|
||||
secretAccessKey = value.SecretAccessKey
|
||||
sessionToken = value.SessionToken
|
||||
)
|
||||
|
||||
adm.setUserAgent(req)
|
||||
for k, v := range reqData.customHeaders {
|
||||
req.Header.Set(k, v[0])
|
||||
}
|
||||
if length := len(reqData.content); length > 0 {
|
||||
req.ContentLength = int64(length)
|
||||
}
|
||||
sum := sha256.Sum256(reqData.content)
|
||||
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(reqData.content))
|
||||
|
||||
req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, location)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// makeTargetURL make a new target url.
|
||||
func (adm AdminClient) makeTargetURL(r requestData) (*url.URL, error) {
|
||||
|
||||
host := adm.endpointURL.Host
|
||||
scheme := adm.endpointURL.Scheme
|
||||
|
||||
urlStr := scheme + "://" + host + libraryAdminURLPrefix + r.relPath
|
||||
|
||||
// If there are any query values, add them to the end.
|
||||
if len(r.queryValues) > 0 {
|
||||
urlStr = urlStr + "?" + s3utils.QueryEncode(r.queryValues)
|
||||
}
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
@@ -1,32 +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 madmin_test
|
||||
package madmin_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func TestMinioAdminClient(t *testing.T) {
|
||||
_, err := madmin.New("localhost:9000", "food", "food123", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BandwidthDetails for the measured bandwidth
|
||||
type BandwidthDetails struct {
|
||||
LimitInBytesPerSecond int64 `json:"limitInBits"`
|
||||
CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"`
|
||||
}
|
||||
|
||||
// BucketBandwidthReport captures the details for all buckets.
|
||||
type BucketBandwidthReport struct {
|
||||
BucketStats map[string]BandwidthDetails `json:"bucketStats,omitempty"`
|
||||
}
|
||||
|
||||
// Report includes the bandwidth report or the error encountered.
|
||||
type Report struct {
|
||||
Report BucketBandwidthReport `json:"report"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// GetBucketBandwidth - Gets a channel reporting bandwidth measurements for replication buckets. If no buckets
|
||||
// generate replication traffic an empty map is returned in the report until traffic is seen.
|
||||
func (adm *AdminClient) GetBucketBandwidth(ctx context.Context, buckets ...string) <-chan Report {
|
||||
queryValues := url.Values{}
|
||||
ch := make(chan Report)
|
||||
if len(buckets) > 0 {
|
||||
queryValues.Set("buckets", strings.Join(buckets, ","))
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/bandwidth",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
if err != nil {
|
||||
defer closeResponse(resp)
|
||||
ch <- Report{Err: err}
|
||||
return ch
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
ch <- Report{Err: httpRespToErrorResponse(resp)}
|
||||
return ch
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
|
||||
go func(ctx context.Context, ch chan<- Report, resp *http.Response) {
|
||||
defer func() {
|
||||
closeResponse(resp)
|
||||
close(ch)
|
||||
}()
|
||||
for {
|
||||
var report BucketBandwidthReport
|
||||
|
||||
if err = dec.Decode(&report); err != nil {
|
||||
ch <- Report{Err: err}
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case ch <- Report{Report: report}:
|
||||
}
|
||||
}
|
||||
}(ctx, ch, resp)
|
||||
return ch
|
||||
}
|
||||
@@ -1,82 +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 madmin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GetConfig - returns the config.json of a minio setup, incoming data is encrypted.
|
||||
func (adm *AdminClient) GetConfig(ctx context.Context) ([]byte, error) {
|
||||
// Execute GET on /minio/admin/v3/config to get config of a setup.
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodGet,
|
||||
requestData{relPath: adminAPIPrefix + "/config"})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return DecryptData(adm.getSecretKey(), resp.Body)
|
||||
}
|
||||
|
||||
// SetConfig - set config supplied as config.json for the setup.
|
||||
func (adm *AdminClient) SetConfig(ctx context.Context, config io.Reader) (err error) {
|
||||
const maxConfigJSONSize = 256 * 1024 // 256KiB
|
||||
|
||||
// Read configuration bytes
|
||||
configBuf := make([]byte, maxConfigJSONSize+1)
|
||||
n, err := io.ReadFull(config, configBuf)
|
||||
if err == nil {
|
||||
return bytes.ErrTooLarge
|
||||
}
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
}
|
||||
configBytes := configBuf[:n]
|
||||
econfigBytes, err := EncryptData(adm.getSecretKey(), configBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/config",
|
||||
content: econfigBytes,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/config to set config.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,90 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Help - return sub-system level help
|
||||
type Help struct {
|
||||
SubSys string `json:"subSys"`
|
||||
Description string `json:"description"`
|
||||
MultipleTargets bool `json:"multipleTargets"`
|
||||
KeysHelp HelpKVS `json:"keysHelp"`
|
||||
}
|
||||
|
||||
// HelpKV - implements help messages for keys
|
||||
// with value as description of the keys.
|
||||
type HelpKV struct {
|
||||
Key string `json:"key"`
|
||||
Description string `json:"description"`
|
||||
Optional bool `json:"optional"`
|
||||
Type string `json:"type"`
|
||||
MultipleTargets bool `json:"multipleTargets"`
|
||||
}
|
||||
|
||||
// HelpKVS - implement order of keys help messages.
|
||||
type HelpKVS []HelpKV
|
||||
|
||||
// Keys returns help keys
|
||||
func (h Help) Keys() []string {
|
||||
var keys []string
|
||||
for _, kh := range h.KeysHelp {
|
||||
keys = append(keys, kh.Key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// HelpConfigKV - return help for a given sub-system.
|
||||
func (adm *AdminClient) HelpConfigKV(ctx context.Context, subSys, key string, envOnly bool) (Help, error) {
|
||||
v := url.Values{}
|
||||
v.Set("subSys", subSys)
|
||||
v.Set("key", key)
|
||||
if envOnly {
|
||||
v.Set("env", "")
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/help-config-kv",
|
||||
queryValues: v,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/help-config-kv
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
if err != nil {
|
||||
return Help{}, err
|
||||
}
|
||||
defer closeResponse(resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return Help{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
var help = Help{}
|
||||
d := json.NewDecoder(resp.Body)
|
||||
d.DisallowUnknownFields()
|
||||
if err = d.Decode(&help); err != nil {
|
||||
return help, err
|
||||
}
|
||||
|
||||
return help, nil
|
||||
}
|
||||
@@ -1,127 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClearConfigHistoryKV - clears the config entry represented by restoreID.
|
||||
// optionally allows setting `all` as a special keyword to automatically
|
||||
// erase all config set history entires.
|
||||
func (adm *AdminClient) ClearConfigHistoryKV(ctx context.Context, restoreID string) (err error) {
|
||||
v := url.Values{}
|
||||
v.Set("restoreId", restoreID)
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/clear-config-history-kv",
|
||||
queryValues: v,
|
||||
}
|
||||
|
||||
// Execute DELETE on /minio/admin/v3/clear-config-history-kv
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreConfigHistoryKV - Restore a previous config set history.
|
||||
// Input is a unique id which represents the previous setting.
|
||||
func (adm *AdminClient) RestoreConfigHistoryKV(ctx context.Context, restoreID string) (err error) {
|
||||
v := url.Values{}
|
||||
v.Set("restoreId", restoreID)
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/restore-config-history-kv",
|
||||
queryValues: v,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-config-kv to set config key/value.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigHistoryEntry - captures config set history with a unique
|
||||
// restore ID and createTime
|
||||
type ConfigHistoryEntry struct {
|
||||
RestoreID string `json:"restoreId"`
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// CreateTimeFormatted is used to print formatted time for CreateTime.
|
||||
func (ch ConfigHistoryEntry) CreateTimeFormatted() string {
|
||||
return ch.CreateTime.Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
// ListConfigHistoryKV - lists a slice of ConfigHistoryEntries sorted by createTime.
|
||||
func (adm *AdminClient) ListConfigHistoryKV(ctx context.Context, count int) ([]ConfigHistoryEntry, error) {
|
||||
if count == 0 {
|
||||
count = 10
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("count", strconv.Itoa(count))
|
||||
|
||||
// Execute GET on /minio/admin/v3/list-config-history-kv
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodGet,
|
||||
requestData{
|
||||
relPath: adminAPIPrefix + "/list-config-history-kv",
|
||||
queryValues: v,
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
data, err := DecryptData(adm.getSecretKey(), resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var chEntries []ConfigHistoryEntry
|
||||
if err = json.Unmarshal(data, &chEntries); err != nil {
|
||||
return chEntries, err
|
||||
}
|
||||
|
||||
return chEntries, nil
|
||||
}
|
||||
@@ -1,112 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// DelConfigKV - delete key from server config.
|
||||
func (adm *AdminClient) DelConfigKV(ctx context.Context, k string) (err error) {
|
||||
econfigBytes, err := EncryptData(adm.getSecretKey(), []byte(k))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/del-config-kv",
|
||||
content: econfigBytes,
|
||||
}
|
||||
|
||||
// Execute DELETE on /minio/admin/v3/del-config-kv to delete config key.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// ConfigAppliedHeader is the header indicating whether the config was applied without requiring a restart.
|
||||
ConfigAppliedHeader = "x-minio-config-applied"
|
||||
|
||||
// ConfigAppliedTrue is the value set in header if the config was applied.
|
||||
ConfigAppliedTrue = "true"
|
||||
)
|
||||
|
||||
// SetConfigKV - set key value config to server.
|
||||
func (adm *AdminClient) SetConfigKV(ctx context.Context, kv string) (restart bool, err error) {
|
||||
econfigBytes, err := EncryptData(adm.getSecretKey(), []byte(kv))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-config-kv",
|
||||
content: econfigBytes,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-config-kv to set config key/value.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return resp.Header.Get(ConfigAppliedHeader) != ConfigAppliedTrue, nil
|
||||
}
|
||||
|
||||
// GetConfigKV - returns the key, value of the requested key, incoming data is encrypted.
|
||||
func (adm *AdminClient) GetConfigKV(ctx context.Context, key string) ([]byte, error) {
|
||||
v := url.Values{}
|
||||
v.Set("key", key)
|
||||
|
||||
// Execute GET on /minio/admin/v3/get-config-kv?key={key} to get value of key.
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodGet,
|
||||
requestData{
|
||||
relPath: adminAPIPrefix + "/get-config-kv",
|
||||
queryValues: v,
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer closeResponse(resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return DecryptData(adm.getSecretKey(), resp.Body)
|
||||
}
|
||||
@@ -1,160 +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 madmin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/minio/argon2"
|
||||
"github.com/minio/minio/pkg/fips"
|
||||
"github.com/secure-io/sio-go"
|
||||
"github.com/secure-io/sio-go/sioutil"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// EncryptData encrypts the data with an unique key
|
||||
// derived from password using the Argon2id PBKDF.
|
||||
//
|
||||
// The returned ciphertext data consists of:
|
||||
// salt | AEAD ID | nonce | encrypted data
|
||||
// 32 1 8 ~ len(data)
|
||||
func EncryptData(password string, data []byte) ([]byte, error) {
|
||||
salt := sioutil.MustRandom(32)
|
||||
|
||||
var (
|
||||
id byte
|
||||
err error
|
||||
stream *sio.Stream
|
||||
)
|
||||
if fips.Enabled() {
|
||||
key := pbkdf2.Key([]byte(password), salt, pbkdf2Cost, 32, sha256.New)
|
||||
stream, err = sio.AES_256_GCM.Stream(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id = pbkdf2AESGCM
|
||||
} else {
|
||||
key := argon2.IDKey([]byte(password), salt, argon2idTime, argon2idMemory, argon2idThreads, 32)
|
||||
if sioutil.NativeAES() {
|
||||
stream, err = sio.AES_256_GCM.Stream(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id = argon2idAESGCM
|
||||
} else {
|
||||
stream, err = sio.ChaCha20Poly1305.Stream(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id = argon2idChaCHa20Poly1305
|
||||
}
|
||||
}
|
||||
|
||||
nonce := sioutil.MustRandom(stream.NonceSize())
|
||||
|
||||
// ciphertext = salt || AEAD ID | nonce | encrypted data
|
||||
cLen := int64(len(salt)+1+len(nonce)+len(data)) + stream.Overhead(int64(len(data)))
|
||||
ciphertext := bytes.NewBuffer(make([]byte, 0, cLen)) // pre-alloc correct length
|
||||
|
||||
// Prefix the ciphertext with salt, AEAD ID and nonce
|
||||
ciphertext.Write(salt)
|
||||
ciphertext.WriteByte(id)
|
||||
ciphertext.Write(nonce)
|
||||
|
||||
w := stream.EncryptWriter(ciphertext, nonce, nil)
|
||||
if _, err = w.Write(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ciphertext.Bytes(), nil
|
||||
}
|
||||
|
||||
// ErrMaliciousData indicates that the stream cannot be
|
||||
// decrypted by provided credentials.
|
||||
var ErrMaliciousData = sio.NotAuthentic
|
||||
|
||||
// DecryptData decrypts the data with the key derived
|
||||
// from the salt (part of data) and the password using
|
||||
// the PBKDF used in EncryptData. DecryptData returns
|
||||
// the decrypted plaintext on success.
|
||||
//
|
||||
// The data must be a valid ciphertext produced by
|
||||
// EncryptData. Otherwise, the decryption will fail.
|
||||
func DecryptData(password string, data io.Reader) ([]byte, error) {
|
||||
var (
|
||||
salt [32]byte
|
||||
id [1]byte
|
||||
nonce [8]byte // This depends on the AEAD but both used ciphers have the same nonce length.
|
||||
)
|
||||
|
||||
if _, err := io.ReadFull(data, salt[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.ReadFull(data, id[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.ReadFull(data, nonce[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
stream *sio.Stream
|
||||
)
|
||||
switch {
|
||||
case id[0] == argon2idAESGCM:
|
||||
key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32)
|
||||
stream, err = sio.AES_256_GCM.Stream(key)
|
||||
case id[0] == argon2idChaCHa20Poly1305:
|
||||
key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32)
|
||||
stream, err = sio.ChaCha20Poly1305.Stream(key)
|
||||
case id[0] == pbkdf2AESGCM:
|
||||
key := pbkdf2.Key([]byte(password), salt[:], pbkdf2Cost, 32, sha256.New)
|
||||
stream, err = sio.AES_256_GCM.Stream(key)
|
||||
default:
|
||||
err = errors.New("madmin: invalid encryption algorithm ID")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plaintext, err := ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plaintext, err
|
||||
}
|
||||
|
||||
const (
|
||||
argon2idAESGCM = 0x00
|
||||
argon2idChaCHa20Poly1305 = 0x01
|
||||
pbkdf2AESGCM = 0x02
|
||||
)
|
||||
|
||||
const (
|
||||
argon2idTime = 1
|
||||
argon2idMemory = 64 * 1024
|
||||
argon2idThreads = 4
|
||||
pbkdf2Cost = 8192
|
||||
)
|
||||
@@ -1,97 +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 madmin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var encryptDataTests = []struct {
|
||||
Password string
|
||||
Data []byte
|
||||
}{
|
||||
{Password: "", Data: nil},
|
||||
{Password: "", Data: make([]byte, 256)},
|
||||
{Password: `xPl.8/rhR"Q_1xLt`, Data: make([]byte, 32)},
|
||||
{Password: "m69?yz4W-!k+7p0", Data: make([]byte, 1024*1024)},
|
||||
{Password: `7h5oU4$te{;K}fgqlI^]`, Data: make([]byte, 256)},
|
||||
}
|
||||
|
||||
func TestEncryptData(t *testing.T) {
|
||||
for i, test := range encryptDataTests {
|
||||
i, test := i, test
|
||||
t.Run(fmt.Sprintf("Test-%d", i), func(t *testing.T) {
|
||||
ciphertext, err := EncryptData(test.Password, test.Data)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to encrypt data: %v", err)
|
||||
}
|
||||
|
||||
plaintext, err := DecryptData(test.Password, bytes.NewReader(ciphertext))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decrypt data: %v", err)
|
||||
}
|
||||
if !bytes.Equal(plaintext, test.Data) {
|
||||
t.Fatal("Decrypt plaintext does not match origin data")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var decryptDataTests = []struct {
|
||||
Password string
|
||||
Data string
|
||||
}{
|
||||
{Password: "", Data: "828aa81599df0651c0461adb82283e8b89956baee9f6e719947ef9cddc849028001dc9d3ac0938f66b07bacc9751437e1985f8a9763c240e81"},
|
||||
|
||||
{Password: "", Data: "1793c71df6647860437134073c15688cbb15961dc0758c7ee1225e66e79c724c00d790dba9c671eae89da2c736d858286ac9bd027abacc6443" +
|
||||
"0375cd41b63b67c070c7fba475a8dd66ae65ba905176c48cbe6f734fc74df87343d8ccff54bada4aeb0a04bd021633ebe6c4768e23f5dea142" +
|
||||
"561d4fe3f90ed59d13dc5fb3a585dadec1742325291b9c81692bdd3420b2428127f8195e0ecd9a1c9237712ed67af7339fbbf7ff3ee1c516e1" +
|
||||
"f81e69d933e057b30997e7274a2c9698e07c39f0e8d6818858f34c8191871b5a52bea9061806bd029024bfc1d9c1f230904968d6c9e10fddcb" +
|
||||
"c006ba97356ff243570fd96df07dd6894e215a6b24c4ed730369519289ebd877aff6ccbd2265985e4ab1a2b7930bab9cfb767b97348a639ddf" +
|
||||
"8db81bf5151da7e8f3d9638a1b86eb1dd78cc6a526f10a414c78638f"},
|
||||
|
||||
{Password: `xPl.8/rhR"Q_1xLt`, Data: "b5c016e93b84b473fc8a37af94936563630c36d6df1841d23a86ee51ca161f9e00ac19116b32f643ff6a56a212b265d8c56" +
|
||||
"195bb0d12ce199e13dfdc5272f80c1564da2c6fc2fa18da91d8062de02af5cdafea491c6f3cae1f"},
|
||||
|
||||
{Password: `7h5oU4$te{;K}fgqlI^]`, Data: "c58edf7cfd557b6b655de6f48b1a3049d8d049dadb3a7bfa9ac9ccbb5baf37ec00f83086a26f43b7d6bc9075ad0" +
|
||||
"38bf5741f118d502ebe94165e4072ba7f98535d6b1e3b6ae67a98115d146d9b4d90e4df4ae82df9cfa17ed7cd42" +
|
||||
"465181559f7ddf09c98beec521bb4478e0cb73c4e0827af8688ff4e7a07327a10d5a180035e6ddb16d974a85257" +
|
||||
"981cd9e0360a20f7b4d653190267dfb241148f018ae180568042e864b9e1b5bc05425a3abc2b0324f50c72d5679" +
|
||||
"8f924405dfc0f8523f4bb564ed65af8e1b1c82a7a0640552ecf81985d95d0993d99172592ddc1393dfa63e8f0b3" +
|
||||
"d744b2cc4b73384ca4693f0c1aec0e9b00e85f2937e891105d67da8f59c14ca96608e0425c42f9c1e7c2a8b3413" +
|
||||
"e1381784f9cfe01de7c47cea1f8d7a7d88f5d4aca783cf55332b47f957a6b9a65269d7eb606b877b"},
|
||||
}
|
||||
|
||||
func TestDecryptData(t *testing.T) {
|
||||
for i, test := range decryptDataTests {
|
||||
i, test := i, test
|
||||
t.Run(fmt.Sprintf("Test-%d", i), func(t *testing.T) {
|
||||
ciphertext, err := hex.DecodeString(test.Data)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode ciphertext data: %v", err)
|
||||
}
|
||||
_, err = DecryptData(test.Password, bytes.NewReader(ciphertext))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decrypt data: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
accountInfo, err := madmClnt.AccountInfo(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Println(accountInfo)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if err = madmClnt.AddUser(context.Background(), "newuser", "newstrongpassword"); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Create policy
|
||||
p := iampolicy.Policy{
|
||||
Version: iampolicy.DefaultVersion,
|
||||
Statements: []iampolicy.Statement{
|
||||
iampolicy.NewStatement(
|
||||
policy.Allow,
|
||||
iampolicy.NewActionSet(iampolicy.GetObjectAction),
|
||||
iampolicy.NewResourceSet(iampolicy.NewResource("testbucket/*", "")),
|
||||
condition.NewFunctions(),
|
||||
)},
|
||||
}
|
||||
|
||||
if err = madmClnt.AddCannedPolicy(context.Background(), "get-only", &p); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if err = madmClnt.SetUserPolicy(context.Background(), "newuser", "get-only"); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madminClient, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
reportCh := madminClient.GetBucketBandwidth(ctx)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
report := <-reportCh
|
||||
fmt.Printf("Report: %+v\n", report)
|
||||
}
|
||||
reportCh = madminClient.GetBucketBandwidth(ctx, "sourceBucket", "sourceBucket2")
|
||||
for i := 0; i < 10; i++ {
|
||||
report := <-reportCh
|
||||
fmt.Printf("Report: %+v\n", report)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
var kiB int64 = 1 << 10
|
||||
ctx := context.Background()
|
||||
// set bucket quota config
|
||||
if err := madmClnt.SetBucketQuota(ctx, "bucket-name", 64*kiB, HardQuota); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// gets bucket quota config
|
||||
quotaCfg, err := madmClnt.GetBucketQuota(ctx, "bucket-name")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println(quotaCfg)
|
||||
// remove bucket quota config
|
||||
if err := madmClnt.RemoveBucketQuota(ctx, "bucket-name"); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
creds, err := auth.CreateCredentials("access-key", "secret-key")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
target := madmin.BucketTarget{Endpoint: "site2:9000", Credentials: creds, TargetBucket: "destbucket", IsSSL: false, Type: madmin.ReplicationArn, BandwidthLimit: 2 * 1024 * 1024}
|
||||
// Set bucket target
|
||||
arn, err := madmClnt.SetBucketTarget(ctx, "srcbucket", &target)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println("replication target ARN is:", arn)
|
||||
// List all bucket target(s)
|
||||
target, err = madmClnt.ListBucketTargets(ctx, "srcbucket", "")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// Get bucket target for arn type "replica"
|
||||
target, err = madmClnt.ListBucketTargets(ctx, "srcbucket", "replica")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// update credentials for target
|
||||
creds, err := auth.CreateCredentials("access-key2", "secret-key2")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
target := madmin.BucketTarget{Endpoint: "site2:9000", Credentials: creds, SourceBucket: "srcbucket", TargetBucket: "destbucket", IsSSL: false, Arn: "arn:minio:ilm:us-east-1:3cbe15b8-82b9-44bc-a737-db9051ab359a:srcbucket"}
|
||||
// update credentials on bucket target
|
||||
if _, err := madmClnt.UpdateBucketTarget(ctx, &target); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Remove bucket target
|
||||
arn := "arn:minio:replica::ac66b2cf-dd8f-4e7e-a882-9a64132f0d59:dest"
|
||||
if err := madmClnt.RemoveBucketTarget(ctx, "srcbucket", arn); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
dataUsageInfo, err := madmClnt.DataUsageInfo(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(dataUsageInfo)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Heal bucket mybucket - dry run
|
||||
isDryRun := true
|
||||
err = madmClnt.HealBucket(context.Background(), "mybucket", isDryRun)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
|
||||
}
|
||||
|
||||
// Heal bucket mybucket - for real this time.
|
||||
isDryRun := false
|
||||
err = madmClnt.HealBucket(context.Background(), "mybucket", isDryRun)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Println("successfully healed mybucket")
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Attempt healing format in dry-run mode.
|
||||
isDryRun := true
|
||||
err = madmClnt.HealFormat(context.Background(), isDryRun)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Perform actual healing of format.
|
||||
isDryRun = false
|
||||
err = madmClnt.HealFormat(context.Background(), isDryRun)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Println("successfully healed storage format on available disks.")
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Heal object mybucket/myobject - dry run.
|
||||
isDryRun := true
|
||||
_, err = madmClnt.HealObject(context.Background(), "mybucket", "myobject", isDryRun)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Heal object mybucket/myobject - this time for real.
|
||||
isDryRun = false
|
||||
healResult, err := madmClnt.HealObject(context.Background(), "mybucket", "myobject", isDryRun)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Printf("heal result: %#v\n", healResult)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
healStatusResult, err := madmClnt.BackgroundHealStatus(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
js, _ := json.MarshalIndent(healStatusResult, "", " ")
|
||||
|
||||
log.Printf("Heal status result: %s\n", string(js))
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
status, err := madmClnt.GetKeyStatus(context.Background(), "") // empty string refers to the default master key
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Printf("Key: %s\n", status.KeyID)
|
||||
if status.EncryptionErr == "" {
|
||||
log.Println("\t • Encryption ✔")
|
||||
} else {
|
||||
log.Printf("\t • Encryption failed: %s\n", status.EncryptionErr)
|
||||
}
|
||||
if status.UpdateErr == "" {
|
||||
log.Println("\t • Re-wrap ✔")
|
||||
} else {
|
||||
log.Printf("\t • Re-wrap failed: %s\n", status.UpdateErr)
|
||||
}
|
||||
if status.DecryptionErr == "" {
|
||||
log.Println("\t • Decryption ✔")
|
||||
} else {
|
||||
log.Printf("\t • Decryption failed: %s\n", status.DecryptionErr)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Clear locks held on mybucket/myprefix for longer than 30s.
|
||||
olderThan := time.Duration(30 * time.Second)
|
||||
locksCleared, err := madmClnt.ClearLocks(context.Background(), "mybucket", "myprefix", olderThan)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(locksCleared)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
profiler := madmin.ProfilerCPU
|
||||
log.Println("Starting " + profiler + " profiling..")
|
||||
|
||||
startResults, err := madmClnt.StartProfiling(context.Background(), profiler)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for _, result := range startResults {
|
||||
if !result.Success {
|
||||
log.Printf("Unable to start profiling on node `%s`, reason = `%s`\n", result.NodeName, result.Error)
|
||||
continue
|
||||
}
|
||||
log.Printf("Profiling successfully started on node `%s`\n", result.NodeName)
|
||||
}
|
||||
|
||||
sleep := time.Duration(10)
|
||||
time.Sleep(time.Second * sleep)
|
||||
|
||||
log.Println("Stopping profiling..")
|
||||
|
||||
profilingData, err := madmClnt.DownloadProfilingData(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
profilingFile, err := os.Create("/tmp/profiling-" + string(profiler) + ".zip")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(profilingFile, profilingData); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := profilingFile.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := profilingData.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Profiling files " + profilingFile.Name() + " successfully downloaded.")
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
st, err := madmClnt.ServerInfo(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("%+v\n", st)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
p := iampolicy.Policy{
|
||||
Version: iampolicy.DefaultVersion,
|
||||
Statements: []iampolicy.Statement{
|
||||
iampolicy.NewStatement(
|
||||
policy.Allow,
|
||||
iampolicy.NewActionSet(iampolicy.GetObjectAction),
|
||||
iampolicy.NewResourceSet(iampolicy.NewResource("testbucket/*", "")),
|
||||
condition.NewFunctions(),
|
||||
)},
|
||||
}
|
||||
|
||||
// Create a new service account
|
||||
creds, err := madmClnt.AddServiceAccount(context.Background(), madmin.AddServiceAccountReq{Policy: &p})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println(creds)
|
||||
|
||||
// List all services accounts
|
||||
list, err := madmClnt.ListServiceAccounts(context.Background(), "")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println(list)
|
||||
|
||||
// Delete a service account
|
||||
err = madmClnt.DeleteServiceAccount(context.Background(), list.Accounts[0])
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = madmClnt.ServiceRestart(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Success")
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Start listening on all http trace activity from all servers in the minio cluster.
|
||||
traceCh := madmClnt.ServiceTrace(context.Background(), madmin.ServiceTraceOpts{
|
||||
S3: true,
|
||||
Internal: true,
|
||||
Storage: true,
|
||||
OS: true,
|
||||
Threshold: 0,
|
||||
})
|
||||
for traceInfo := range traceCh {
|
||||
if traceInfo.Err != nil {
|
||||
fmt.Println(traceInfo.Err)
|
||||
}
|
||||
fmt.Println(traceInfo)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTPS) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
st, err := madmClnt.StorageInfo(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Printf("%+v\n", st)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||
// dummy values, please replace them with original values.
|
||||
|
||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||
// New returns an MinIO Admin client object.
|
||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
locks, err := madmClnt.TopLocks(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("failed due to: %v", err)
|
||||
}
|
||||
|
||||
out, err := json.Marshal(locks)
|
||||
if err != nil {
|
||||
log.Fatalf("Marshal failed due to: %v", err)
|
||||
}
|
||||
log.Println("Top Locks received successfully: ", string(out))
|
||||
}
|
||||
@@ -1,165 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// GroupAddRemove is type for adding/removing members to/from a group.
|
||||
type GroupAddRemove struct {
|
||||
Group string `json:"group"`
|
||||
Members []string `json:"members"`
|
||||
IsRemove bool `json:"isRemove"`
|
||||
}
|
||||
|
||||
// UpdateGroupMembers - adds/removes users to/from a group. Server
|
||||
// creates the group as needed. Group is removed if remove request is
|
||||
// made on empty group.
|
||||
func (adm *AdminClient) UpdateGroupMembers(ctx context.Context, g GroupAddRemove) error {
|
||||
data, err := json.Marshal(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/update-group-members",
|
||||
content: data,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/update-group-members
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GroupDesc is a type that holds group info along with the policy
|
||||
// attached to it.
|
||||
type GroupDesc struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Members []string `json:"members"`
|
||||
Policy string `json:"policy"`
|
||||
}
|
||||
|
||||
// GetGroupDescription - fetches information on a group.
|
||||
func (adm *AdminClient) GetGroupDescription(ctx context.Context, group string) (*GroupDesc, error) {
|
||||
v := url.Values{}
|
||||
v.Set("group", group)
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/group",
|
||||
queryValues: v,
|
||||
}
|
||||
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gd := GroupDesc{}
|
||||
if err = json.Unmarshal(data, &gd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gd, nil
|
||||
}
|
||||
|
||||
// ListGroups - lists all groups names present on the server.
|
||||
func (adm *AdminClient) ListGroups(ctx context.Context) ([]string, error) {
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/groups",
|
||||
}
|
||||
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups := []string{}
|
||||
if err = json.Unmarshal(data, &groups); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// GroupStatus - group status.
|
||||
type GroupStatus string
|
||||
|
||||
// GroupStatus values.
|
||||
const (
|
||||
GroupEnabled GroupStatus = "enabled"
|
||||
GroupDisabled GroupStatus = "disabled"
|
||||
)
|
||||
|
||||
// SetGroupStatus - sets the status of a group.
|
||||
func (adm *AdminClient) SetGroupStatus(ctx context.Context, group string, status GroupStatus) error {
|
||||
v := url.Values{}
|
||||
v.Set("group", group)
|
||||
v.Set("status", string(status))
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-group-status",
|
||||
queryValues: v,
|
||||
}
|
||||
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,423 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HealScanMode represents the type of healing scan
|
||||
type HealScanMode int
|
||||
|
||||
const (
|
||||
// HealUnknownScan default is unknown
|
||||
HealUnknownScan HealScanMode = iota
|
||||
|
||||
// HealNormalScan checks if parts are present and not outdated
|
||||
HealNormalScan
|
||||
|
||||
// HealDeepScan checks for parts bitrot checksums
|
||||
HealDeepScan
|
||||
)
|
||||
|
||||
// HealOpts - collection of options for a heal sequence
|
||||
type HealOpts struct {
|
||||
Recursive bool `json:"recursive"`
|
||||
DryRun bool `json:"dryRun"`
|
||||
Remove bool `json:"remove"`
|
||||
Recreate bool `json:"recreate"` // only used when bucket needs to be healed
|
||||
ScanMode HealScanMode `json:"scanMode"`
|
||||
NoLock bool `json:"nolock"`
|
||||
}
|
||||
|
||||
// Equal returns true if no is same as o.
|
||||
func (o HealOpts) Equal(no HealOpts) bool {
|
||||
if o.Recursive != no.Recursive {
|
||||
return false
|
||||
}
|
||||
if o.DryRun != no.DryRun {
|
||||
return false
|
||||
}
|
||||
if o.Remove != no.Remove {
|
||||
return false
|
||||
}
|
||||
return o.ScanMode == no.ScanMode
|
||||
}
|
||||
|
||||
// HealStartSuccess - holds information about a successfully started
|
||||
// heal operation
|
||||
type HealStartSuccess struct {
|
||||
ClientToken string `json:"clientToken"`
|
||||
ClientAddress string `json:"clientAddress"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
}
|
||||
|
||||
// HealStopSuccess - holds information about a successfully stopped
|
||||
// heal operation.
|
||||
type HealStopSuccess HealStartSuccess
|
||||
|
||||
// HealTaskStatus - status struct for a heal task
|
||||
type HealTaskStatus struct {
|
||||
Summary string `json:"summary"`
|
||||
FailureDetail string `json:"detail"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
HealSettings HealOpts `json:"settings"`
|
||||
|
||||
Items []HealResultItem `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// HealItemType - specify the type of heal operation in a healing
|
||||
// result
|
||||
type HealItemType string
|
||||
|
||||
// HealItemType constants
|
||||
const (
|
||||
HealItemMetadata HealItemType = "metadata"
|
||||
HealItemBucket = "bucket"
|
||||
HealItemBucketMetadata = "bucket-metadata"
|
||||
HealItemObject = "object"
|
||||
)
|
||||
|
||||
// Drive state constants
|
||||
const (
|
||||
DriveStateOk string = "ok"
|
||||
DriveStateOffline = "offline"
|
||||
DriveStateCorrupt = "corrupt"
|
||||
DriveStateMissing = "missing"
|
||||
DriveStatePermission = "permission-denied"
|
||||
DriveStateFaulty = "faulty"
|
||||
DriveStateUnknown = "unknown"
|
||||
DriveStateUnformatted = "unformatted" // only returned by disk
|
||||
)
|
||||
|
||||
// HealDriveInfo - struct for an individual drive info item.
|
||||
type HealDriveInfo struct {
|
||||
UUID string `json:"uuid"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
// HealResultItem - struct for an individual heal result item
|
||||
type HealResultItem struct {
|
||||
ResultIndex int64 `json:"resultId"`
|
||||
Type HealItemType `json:"type"`
|
||||
Bucket string `json:"bucket"`
|
||||
Object string `json:"object"`
|
||||
VersionID string `json:"versionId"`
|
||||
Detail string `json:"detail"`
|
||||
ParityBlocks int `json:"parityBlocks,omitempty"`
|
||||
DataBlocks int `json:"dataBlocks,omitempty"`
|
||||
DiskCount int `json:"diskCount"`
|
||||
SetCount int `json:"setCount"`
|
||||
// below slices are from drive info.
|
||||
Before struct {
|
||||
Drives []HealDriveInfo `json:"drives"`
|
||||
} `json:"before"`
|
||||
After struct {
|
||||
Drives []HealDriveInfo `json:"drives"`
|
||||
} `json:"after"`
|
||||
ObjectSize int64 `json:"objectSize"`
|
||||
}
|
||||
|
||||
// GetMissingCounts - returns the number of missing disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetMissingCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateMissing {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateMissing {
|
||||
a++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOfflineCounts - returns the number of offline disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetOfflineCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateOffline {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateOffline {
|
||||
a++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetCorruptedCounts - returns the number of corrupted disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetCorruptedCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateCorrupt {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateCorrupt {
|
||||
a++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOnlineCounts - returns the number of online disks before
|
||||
// and after heal
|
||||
func (hri *HealResultItem) GetOnlineCounts() (b, a int) {
|
||||
if hri == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range hri.Before.Drives {
|
||||
if v.State == DriveStateOk {
|
||||
b++
|
||||
}
|
||||
}
|
||||
for _, v := range hri.After.Drives {
|
||||
if v.State == DriveStateOk {
|
||||
a++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Heal - API endpoint to start heal and to fetch status
|
||||
// forceStart and forceStop are mutually exclusive, you can either
|
||||
// set one of them to 'true'. If both are set 'forceStart' will be
|
||||
// honored.
|
||||
func (adm *AdminClient) Heal(ctx context.Context, bucket, prefix string,
|
||||
healOpts HealOpts, clientToken string, forceStart, forceStop bool) (
|
||||
healStart HealStartSuccess, healTaskStatus HealTaskStatus, err error) {
|
||||
|
||||
if forceStart && forceStop {
|
||||
return healStart, healTaskStatus, ErrInvalidArgument("forceStart and forceStop set to true is not allowed")
|
||||
}
|
||||
|
||||
body, err := json.Marshal(healOpts)
|
||||
if err != nil {
|
||||
return healStart, healTaskStatus, err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(adminAPIPrefix+"/heal/%s", bucket)
|
||||
if bucket != "" && prefix != "" {
|
||||
path += "/" + prefix
|
||||
}
|
||||
|
||||
// execute POST request to heal api
|
||||
queryVals := make(url.Values)
|
||||
if clientToken != "" {
|
||||
queryVals.Set("clientToken", clientToken)
|
||||
body = []byte{}
|
||||
}
|
||||
|
||||
// Anyone can be set, either force start or forceStop.
|
||||
if forceStart {
|
||||
queryVals.Set("forceStart", "true")
|
||||
} else if forceStop {
|
||||
queryVals.Set("forceStop", "true")
|
||||
}
|
||||
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodPost, requestData{
|
||||
relPath: path,
|
||||
content: body,
|
||||
queryValues: queryVals,
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return healStart, healTaskStatus, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return healStart, healTaskStatus, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return healStart, healTaskStatus, err
|
||||
}
|
||||
|
||||
// Was it a status request?
|
||||
if clientToken == "" {
|
||||
// As a special operation forceStop would return a
|
||||
// similar struct as healStart will have the
|
||||
// heal sequence information about the heal which
|
||||
// was stopped.
|
||||
err = json.Unmarshal(respBytes, &healStart)
|
||||
} else {
|
||||
err = json.Unmarshal(respBytes, &healTaskStatus)
|
||||
}
|
||||
if err != nil {
|
||||
// May be the server responded with error after success
|
||||
// message, handle it separately here.
|
||||
var errResp ErrorResponse
|
||||
err = json.Unmarshal(respBytes, &errResp)
|
||||
if err != nil {
|
||||
// Unknown structure return error anyways.
|
||||
return healStart, healTaskStatus, err
|
||||
}
|
||||
return healStart, healTaskStatus, errResp
|
||||
}
|
||||
return healStart, healTaskStatus, nil
|
||||
}
|
||||
|
||||
// BgHealState represents the status of the background heal
|
||||
type BgHealState struct {
|
||||
ScannedItemsCount int64
|
||||
|
||||
HealDisks []string
|
||||
|
||||
// SetStatus contains information for each set.
|
||||
Sets []SetStatus `json:"sets"`
|
||||
}
|
||||
|
||||
// SetStatus contains information about the heal status of a set.
|
||||
type SetStatus struct {
|
||||
ID string `json:"id"`
|
||||
PoolIndex int `json:"pool_index"`
|
||||
SetIndex int `json:"set_index"`
|
||||
HealStatus string `json:"heal_status"`
|
||||
HealPriority string `json:"heal_priority"`
|
||||
Disks []Disk `json:"disks"`
|
||||
}
|
||||
|
||||
// HealingDisk contains information about
|
||||
type HealingDisk struct {
|
||||
// Copied from cmd/background-newdisks-heal-ops.go
|
||||
// When adding new field, update (*healingTracker).toHealingDisk
|
||||
|
||||
ID string `json:"id"`
|
||||
PoolIndex int `json:"pool_index"`
|
||||
SetIndex int `json:"set_index"`
|
||||
DiskIndex int `json:"disk_index"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Path string `json:"path"`
|
||||
Started time.Time `json:"started"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
ObjectsHealed uint64 `json:"objects_healed"`
|
||||
ObjectsFailed uint64 `json:"objects_failed"`
|
||||
BytesDone uint64 `json:"bytes_done"`
|
||||
BytesFailed uint64 `json:"bytes_failed"`
|
||||
|
||||
// Last object scanned.
|
||||
Bucket string `json:"current_bucket"`
|
||||
Object string `json:"current_object"`
|
||||
|
||||
// Filled on startup/restarts.
|
||||
QueuedBuckets []string `json:"queued_buckets"`
|
||||
|
||||
// Filled during heal.
|
||||
HealedBuckets []string `json:"healed_buckets"`
|
||||
// future add more tracking capabilities
|
||||
}
|
||||
|
||||
// Merge others into b.
|
||||
func (b *BgHealState) Merge(others ...BgHealState) {
|
||||
for _, other := range others {
|
||||
b.ScannedItemsCount += other.ScannedItemsCount
|
||||
if len(b.Sets) == 0 {
|
||||
b.Sets = make([]SetStatus, len(other.Sets))
|
||||
copy(b.Sets, other.Sets)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add disk if not present.
|
||||
// If present select the one with latest lastupdate.
|
||||
addSet := func(set SetStatus) {
|
||||
for eSetIdx, existing := range b.Sets {
|
||||
if existing.ID != set.ID {
|
||||
continue
|
||||
}
|
||||
if len(existing.Disks) < len(set.Disks) {
|
||||
b.Sets[eSetIdx].Disks = set.Disks
|
||||
}
|
||||
if len(existing.Disks) < len(set.Disks) {
|
||||
return
|
||||
}
|
||||
for i, disk := range set.Disks {
|
||||
// Disks should be the same.
|
||||
if disk.HealInfo != nil {
|
||||
existing.Disks[i].HealInfo = disk.HealInfo
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
b.Sets = append(b.Sets, set)
|
||||
}
|
||||
for _, disk := range other.Sets {
|
||||
addSet(disk)
|
||||
}
|
||||
}
|
||||
sort.Slice(b.Sets, func(i, j int) bool {
|
||||
if b.Sets[i].PoolIndex != b.Sets[j].PoolIndex {
|
||||
return b.Sets[i].PoolIndex < b.Sets[j].PoolIndex
|
||||
}
|
||||
return b.Sets[i].SetIndex < b.Sets[j].SetIndex
|
||||
})
|
||||
}
|
||||
|
||||
// BackgroundHealStatus returns the background heal status of the
|
||||
// current server or cluster.
|
||||
func (adm *AdminClient) BackgroundHealStatus(ctx context.Context) (BgHealState, error) {
|
||||
// Execute POST request to background heal status api
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodPost,
|
||||
requestData{relPath: adminAPIPrefix + "/background-heal/status"})
|
||||
if err != nil {
|
||||
return BgHealState{}, err
|
||||
}
|
||||
defer closeResponse(resp)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return BgHealState{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return BgHealState{}, err
|
||||
}
|
||||
|
||||
var healState BgHealState
|
||||
|
||||
err = json.Unmarshal(respBytes, &healState)
|
||||
if err != nil {
|
||||
return BgHealState{}, err
|
||||
}
|
||||
return healState, nil
|
||||
}
|
||||
@@ -1,73 +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 madmin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests heal drives missing and offline counts.
|
||||
func TestHealDriveCounts(t *testing.T) {
|
||||
rs := HealResultItem{}
|
||||
rs.Before.Drives = make([]HealDriveInfo, 20)
|
||||
rs.After.Drives = make([]HealDriveInfo, 20)
|
||||
for i := range rs.Before.Drives {
|
||||
if i < 4 {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateMissing}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateMissing}
|
||||
} else if i > 4 && i < 15 {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateOffline}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateOffline}
|
||||
} else if i > 15 {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateCorrupt}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateCorrupt}
|
||||
} else {
|
||||
rs.Before.Drives[i] = HealDriveInfo{State: DriveStateOk}
|
||||
rs.After.Drives[i] = HealDriveInfo{State: DriveStateOk}
|
||||
}
|
||||
}
|
||||
|
||||
i, j := rs.GetOnlineCounts()
|
||||
if i > 2 {
|
||||
t.Errorf("Expected '2', got %d before online disks", i)
|
||||
}
|
||||
if j > 2 {
|
||||
t.Errorf("Expected '2', got %d after online disks", j)
|
||||
}
|
||||
i, j = rs.GetOfflineCounts()
|
||||
if i > 10 {
|
||||
t.Errorf("Expected '10', got %d before offline disks", i)
|
||||
}
|
||||
if j > 10 {
|
||||
t.Errorf("Expected '10', got %d after offline disks", j)
|
||||
}
|
||||
i, j = rs.GetCorruptedCounts()
|
||||
if i > 4 {
|
||||
t.Errorf("Expected '4', got %d before corrupted disks", i)
|
||||
}
|
||||
if j > 4 {
|
||||
t.Errorf("Expected '4', got %d after corrupted disks", j)
|
||||
}
|
||||
i, j = rs.GetMissingCounts()
|
||||
if i > 4 {
|
||||
t.Errorf("Expected '4', got %d before missing disks", i)
|
||||
}
|
||||
if j > 4 {
|
||||
t.Errorf("Expected '4', got %d after missing disks", i)
|
||||
}
|
||||
}
|
||||
@@ -1,339 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/disk"
|
||||
"github.com/minio/minio/pkg/net"
|
||||
|
||||
smart "github.com/minio/minio/pkg/smart"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
diskhw "github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
// HealthInfo - MinIO cluster's health Info
|
||||
type HealthInfo struct {
|
||||
TimeStamp time.Time `json:"timestamp,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Perf PerfInfo `json:"perf,omitempty"`
|
||||
Minio MinioHealthInfo `json:"minio,omitempty"`
|
||||
Sys SysHealthInfo `json:"sys,omitempty"`
|
||||
}
|
||||
|
||||
// SysHealthInfo - Includes hardware and system information of the MinIO cluster
|
||||
type SysHealthInfo struct {
|
||||
CPUInfo []ServerCPUInfo `json:"cpus,omitempty"`
|
||||
DiskHwInfo []ServerDiskHwInfo `json:"drives,omitempty"`
|
||||
OsInfo []ServerOsInfo `json:"osinfos,omitempty"`
|
||||
MemInfo []ServerMemInfo `json:"meminfos,omitempty"`
|
||||
ProcInfo []ServerProcInfo `json:"procinfos,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ServerProcInfo - Includes host process lvl information
|
||||
type ServerProcInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
Processes []SysProcess `json:"processes,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SysProcess - Includes process lvl information about a single process
|
||||
type SysProcess struct {
|
||||
Pid int32 `json:"pid"`
|
||||
Background bool `json:"background,omitempty"`
|
||||
CPUPercent float64 `json:"cpupercent,omitempty"`
|
||||
Children []int32 `json:"children,omitempty"`
|
||||
CmdLine string `json:"cmd,omitempty"`
|
||||
ConnectionCount int `json:"connection_count,omitempty"`
|
||||
CreateTime int64 `json:"createtime,omitempty"`
|
||||
Cwd string `json:"cwd,omitempty"`
|
||||
Exe string `json:"exe,omitempty"`
|
||||
Gids []int32 `json:"gids,omitempty"`
|
||||
IOCounters *process.IOCountersStat `json:"iocounters,omitempty"`
|
||||
IsRunning bool `json:"isrunning,omitempty"`
|
||||
MemInfo *process.MemoryInfoStat `json:"meminfo,omitempty"`
|
||||
MemMaps *[]process.MemoryMapsStat `json:"memmaps,omitempty"`
|
||||
MemPercent float32 `json:"mempercent,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Nice int32 `json:"nice,omitempty"`
|
||||
NumCtxSwitches *process.NumCtxSwitchesStat `json:"numctxswitches,omitempty"`
|
||||
NumFds int32 `json:"numfds,omitempty"`
|
||||
NumThreads int32 `json:"numthreads,omitempty"`
|
||||
PageFaults *process.PageFaultsStat `json:"pagefaults,omitempty"`
|
||||
Parent int32 `json:"parent,omitempty"`
|
||||
Ppid int32 `json:"ppid,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Tgid int32 `json:"tgid,omitempty"`
|
||||
Times *cpu.TimesStat `json:"cputimes,omitempty"`
|
||||
Uids []int32 `json:"uids,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
// ServerMemInfo - Includes host virtual and swap mem information
|
||||
type ServerMemInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
SwapMem *mem.SwapMemoryStat `json:"swap,omitempty"`
|
||||
VirtualMem *mem.VirtualMemoryStat `json:"virtualmem,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ServerOsInfo - Includes host os information
|
||||
type ServerOsInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
Info *host.InfoStat `json:"info,omitempty"`
|
||||
Sensors []host.TemperatureStat `json:"sensors,omitempty"`
|
||||
Users []host.UserStat `json:"users,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ServerCPUInfo - Includes cpu and timer stats of each node of the MinIO cluster
|
||||
type ServerCPUInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
CPUStat []cpu.InfoStat `json:"cpu,omitempty"`
|
||||
TimeStat []cpu.TimesStat `json:"time,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// MinioHealthInfo - Includes MinIO confifuration information
|
||||
type MinioHealthInfo struct {
|
||||
Info InfoMessage `json:"info,omitempty"`
|
||||
Config interface{} `json:"config,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ServerDiskHwInfo - Includes usage counters, disk counters and partitions
|
||||
type ServerDiskHwInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
Usage []*diskhw.UsageStat `json:"usages,omitempty"`
|
||||
Partitions []PartitionStat `json:"partitions,omitempty"`
|
||||
Counters map[string]diskhw.IOCountersStat `json:"counters,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// PartitionStat - includes data from both shirou/psutil.diskHw.PartitionStat as well as SMART data
|
||||
type PartitionStat struct {
|
||||
Device string `json:"device"`
|
||||
Mountpoint string `json:"mountpoint,omitempty"`
|
||||
Fstype string `json:"fstype,omitempty"`
|
||||
Opts string `json:"opts,omitempty"`
|
||||
SmartInfo smart.Info `json:"smartInfo,omitempty"`
|
||||
}
|
||||
|
||||
// PerfInfo - Includes Drive and Net perf info for the entire MinIO cluster
|
||||
type PerfInfo struct {
|
||||
DriveInfo []ServerDrivesInfo `json:"drives,omitempty"`
|
||||
Net []ServerNetHealthInfo `json:"net,omitempty"`
|
||||
NetParallel ServerNetHealthInfo `json:"net_parallel,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ServerDrivesInfo - Drive info about all drives in a single MinIO node
|
||||
type ServerDrivesInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
Serial []DrivePerfInfo `json:"serial,omitempty"` // Drive perf info collected one drive at a time
|
||||
Parallel []DrivePerfInfo `json:"parallel,omitempty"` // Drive perf info collected in parallel
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// DrivePerfInfo - Stats about a single drive in a MinIO node
|
||||
type DrivePerfInfo struct {
|
||||
Path string `json:"endpoint"`
|
||||
Latency disk.Latency `json:"latency,omitempty"`
|
||||
Throughput disk.Throughput `json:"throughput,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ServerNetHealthInfo - Network health info about a single MinIO node
|
||||
type ServerNetHealthInfo struct {
|
||||
Addr string `json:"addr"`
|
||||
Net []NetPerfInfo `json:"net,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// NetPerfInfo - one-to-one network connectivity Stats between 2 MinIO nodes
|
||||
type NetPerfInfo struct {
|
||||
Addr string `json:"remote"`
|
||||
Latency net.Latency `json:"latency,omitempty"`
|
||||
Throughput net.Throughput `json:"throughput,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// HealthDataType - Typed Health data types
|
||||
type HealthDataType string
|
||||
|
||||
// HealthDataTypes
|
||||
const (
|
||||
HealthDataTypePerfDrive HealthDataType = "perfdrive"
|
||||
HealthDataTypePerfNet HealthDataType = "perfnet"
|
||||
HealthDataTypeMinioInfo HealthDataType = "minioinfo"
|
||||
HealthDataTypeMinioConfig HealthDataType = "minioconfig"
|
||||
HealthDataTypeSysCPU HealthDataType = "syscpu"
|
||||
HealthDataTypeSysDiskHw HealthDataType = "sysdiskhw"
|
||||
HealthDataTypeSysDocker HealthDataType = "sysdocker" // is this really needed?
|
||||
HealthDataTypeSysOsInfo HealthDataType = "sysosinfo"
|
||||
HealthDataTypeSysLoad HealthDataType = "sysload" // provides very little info. Making it TBD
|
||||
HealthDataTypeSysMem HealthDataType = "sysmem"
|
||||
HealthDataTypeSysNet HealthDataType = "sysnet"
|
||||
HealthDataTypeSysProcess HealthDataType = "sysprocess"
|
||||
)
|
||||
|
||||
// HealthDataTypesMap - Map of Health datatypes
|
||||
var HealthDataTypesMap = map[string]HealthDataType{
|
||||
"perfdrive": HealthDataTypePerfDrive,
|
||||
"perfnet": HealthDataTypePerfNet,
|
||||
"minioinfo": HealthDataTypeMinioInfo,
|
||||
"minioconfig": HealthDataTypeMinioConfig,
|
||||
"syscpu": HealthDataTypeSysCPU,
|
||||
"sysdiskhw": HealthDataTypeSysDiskHw,
|
||||
"sysdocker": HealthDataTypeSysDocker,
|
||||
"sysosinfo": HealthDataTypeSysOsInfo,
|
||||
"sysload": HealthDataTypeSysLoad,
|
||||
"sysmem": HealthDataTypeSysMem,
|
||||
"sysnet": HealthDataTypeSysNet,
|
||||
"sysprocess": HealthDataTypeSysProcess,
|
||||
}
|
||||
|
||||
// HealthDataTypesList - List of Health datatypes
|
||||
var HealthDataTypesList = []HealthDataType{
|
||||
HealthDataTypePerfDrive,
|
||||
HealthDataTypePerfNet,
|
||||
HealthDataTypeMinioInfo,
|
||||
HealthDataTypeMinioConfig,
|
||||
HealthDataTypeSysCPU,
|
||||
HealthDataTypeSysDiskHw,
|
||||
HealthDataTypeSysDocker,
|
||||
HealthDataTypeSysOsInfo,
|
||||
HealthDataTypeSysLoad,
|
||||
HealthDataTypeSysMem,
|
||||
HealthDataTypeSysNet,
|
||||
HealthDataTypeSysProcess,
|
||||
}
|
||||
|
||||
// ServerHealthInfo - Connect to a minio server and call Health Info Management API
|
||||
// to fetch server's information represented by HealthInfo structure
|
||||
func (adm *AdminClient) ServerHealthInfo(ctx context.Context, healthDataTypes []HealthDataType, deadline time.Duration) <-chan HealthInfo {
|
||||
respChan := make(chan HealthInfo)
|
||||
go func() {
|
||||
v := url.Values{}
|
||||
|
||||
v.Set("deadline",
|
||||
deadline.Truncate(1*time.Second).String())
|
||||
|
||||
// start with all set to false
|
||||
for _, d := range HealthDataTypesList {
|
||||
v.Set(string(d), "false")
|
||||
}
|
||||
|
||||
// only 'trueify' user provided values
|
||||
for _, d := range healthDataTypes {
|
||||
v.Set(string(d), "true")
|
||||
}
|
||||
var healthInfoMessage HealthInfo
|
||||
healthInfoMessage.TimeStamp = time.Now()
|
||||
|
||||
resp, err := adm.executeMethod(ctx, "GET", requestData{
|
||||
relPath: adminAPIPrefix + "/healthinfo",
|
||||
queryValues: v,
|
||||
})
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
respChan <- HealthInfo{
|
||||
Error: err.Error(),
|
||||
}
|
||||
close(respChan)
|
||||
return
|
||||
}
|
||||
|
||||
// Check response http status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respChan <- HealthInfo{
|
||||
Error: httpRespToErrorResponse(resp).Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal the server's json response
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
err := decoder.Decode(&healthInfoMessage)
|
||||
healthInfoMessage.TimeStamp = time.Now()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
respChan <- HealthInfo{
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
respChan <- healthInfoMessage
|
||||
}
|
||||
|
||||
respChan <- healthInfoMessage
|
||||
|
||||
if v.Get(string(HealthDataTypeMinioInfo)) == "true" {
|
||||
info, err := adm.ServerInfo(ctx)
|
||||
if err != nil {
|
||||
respChan <- HealthInfo{
|
||||
Error: err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
healthInfoMessage.Minio.Info = info
|
||||
respChan <- healthInfoMessage
|
||||
}
|
||||
|
||||
close(respChan)
|
||||
}()
|
||||
return respChan
|
||||
}
|
||||
|
||||
// GetTotalCapacity gets the total capacity a server holds.
|
||||
func (s *ServerDiskHwInfo) GetTotalCapacity() (capacity uint64) {
|
||||
for _, u := range s.Usage {
|
||||
capacity += u.Total
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTotalFreeCapacity gets the total capacity that is free.
|
||||
func (s *ServerDiskHwInfo) GetTotalFreeCapacity() (capacity uint64) {
|
||||
for _, u := range s.Usage {
|
||||
capacity += u.Free
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTotalUsedCapacity gets the total capacity used.
|
||||
func (s *ServerDiskHwInfo) GetTotalUsedCapacity() (capacity uint64) {
|
||||
for _, u := range s.Usage {
|
||||
capacity += u.Used
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,381 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BackendType - represents different backend types.
|
||||
type BackendType int
|
||||
|
||||
// Enum for different backend types.
|
||||
const (
|
||||
Unknown BackendType = iota
|
||||
// Filesystem backend.
|
||||
FS
|
||||
// Multi disk Erasure (single, distributed) backend.
|
||||
Erasure
|
||||
// Gateway to other storage
|
||||
Gateway
|
||||
|
||||
// Add your own backend.
|
||||
)
|
||||
|
||||
// ItemState - represents the status of any item in offline,init,online state
|
||||
type ItemState string
|
||||
|
||||
const (
|
||||
|
||||
// ItemOffline indicates that the item is offline
|
||||
ItemOffline = ItemState("offline")
|
||||
// ItemInitializing indicates that the item is still in initialization phase
|
||||
ItemInitializing = ItemState("initializing")
|
||||
// ItemOnline indicates that the item is online
|
||||
ItemOnline = ItemState("online")
|
||||
)
|
||||
|
||||
// StorageInfo - represents total capacity of underlying storage.
|
||||
type StorageInfo struct {
|
||||
Disks []Disk
|
||||
|
||||
// Backend type.
|
||||
Backend BackendInfo
|
||||
}
|
||||
|
||||
// BackendInfo - contains info of the underlying backend
|
||||
type BackendInfo struct {
|
||||
// Represents various backend types, currently on FS, Erasure and Gateway
|
||||
Type BackendType
|
||||
|
||||
// Following fields are only meaningful if BackendType is Gateway.
|
||||
GatewayOnline bool
|
||||
|
||||
// Following fields are only meaningful if BackendType is Erasure.
|
||||
OnlineDisks BackendDisks // Online disks during server startup.
|
||||
OfflineDisks BackendDisks // Offline disks during server startup.
|
||||
|
||||
// Following fields are only meaningful if BackendType is Erasure.
|
||||
StandardSCData []int // Data disks for currently configured Standard storage class.
|
||||
StandardSCParity int // Parity disks for currently configured Standard storage class.
|
||||
RRSCData []int // Data disks for currently configured Reduced Redundancy storage class.
|
||||
RRSCParity int // Parity disks for currently configured Reduced Redundancy storage class.
|
||||
}
|
||||
|
||||
// BackendDisks - represents the map of endpoint-disks.
|
||||
type BackendDisks map[string]int
|
||||
|
||||
// Sum - Return the sum of the disks in the endpoint-disk map.
|
||||
func (d1 BackendDisks) Sum() (sum int) {
|
||||
for _, count := range d1 {
|
||||
sum += count
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// Merge - Reduces two endpoint-disk maps.
|
||||
func (d1 BackendDisks) Merge(d2 BackendDisks) BackendDisks {
|
||||
if len(d2) == 0 {
|
||||
d2 = make(BackendDisks)
|
||||
}
|
||||
var merged = make(BackendDisks)
|
||||
for i1, v1 := range d1 {
|
||||
if v2, ok := d2[i1]; ok {
|
||||
merged[i1] = v2 + v1
|
||||
continue
|
||||
}
|
||||
merged[i1] = v1
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// StorageInfo - Connect to a minio server and call Storage Info Management API
|
||||
// to fetch server's information represented by StorageInfo structure
|
||||
func (adm *AdminClient) StorageInfo(ctx context.Context) (StorageInfo, error) {
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, requestData{relPath: adminAPIPrefix + "/storageinfo"})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return StorageInfo{}, err
|
||||
}
|
||||
|
||||
// Check response http status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return StorageInfo{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
// Unmarshal the server's json response
|
||||
var storageInfo StorageInfo
|
||||
if err = json.NewDecoder(resp.Body).Decode(&storageInfo); err != nil {
|
||||
return StorageInfo{}, err
|
||||
}
|
||||
|
||||
return storageInfo, nil
|
||||
}
|
||||
|
||||
// BucketUsageInfo - bucket usage info provides
|
||||
// - total size of the bucket
|
||||
// - total objects in a bucket
|
||||
// - object size histogram per bucket
|
||||
type BucketUsageInfo struct {
|
||||
Size uint64 `json:"size"`
|
||||
ReplicationPendingSize uint64 `json:"objectsPendingReplicationTotalSize"`
|
||||
ReplicationFailedSize uint64 `json:"objectsFailedReplicationTotalSize"`
|
||||
ReplicatedSize uint64 `json:"objectsReplicatedTotalSize"`
|
||||
ReplicaSize uint64 `json:"objectReplicaTotalSize"`
|
||||
ReplicationPendingCount uint64 `json:"objectsPendingReplicationCount"`
|
||||
ReplicationFailedCount uint64 `json:"objectsFailedReplicationCount"`
|
||||
|
||||
ObjectsCount uint64 `json:"objectsCount"`
|
||||
ObjectSizesHistogram map[string]uint64 `json:"objectsSizesHistogram"`
|
||||
}
|
||||
|
||||
// DataUsageInfo represents data usage stats of the underlying Object API
|
||||
type DataUsageInfo struct {
|
||||
// LastUpdate is the timestamp of when the data usage info was last updated.
|
||||
// This does not indicate a full scan.
|
||||
LastUpdate time.Time `json:"lastUpdate"`
|
||||
|
||||
// Objects total count across all buckets
|
||||
ObjectsTotalCount uint64 `json:"objectsCount"`
|
||||
|
||||
// Objects total size across all buckets
|
||||
ObjectsTotalSize uint64 `json:"objectsTotalSize"`
|
||||
|
||||
// Total Size for objects that have not yet been replicated
|
||||
ReplicationPendingSize uint64 `json:"objectsPendingReplicationTotalSize"`
|
||||
|
||||
// Total size for objects that have witness one or more failures and will be retried
|
||||
ReplicationFailedSize uint64 `json:"objectsFailedReplicationTotalSize"`
|
||||
|
||||
// Total size for objects that have been replicated to destination
|
||||
ReplicatedSize uint64 `json:"objectsReplicatedTotalSize"`
|
||||
|
||||
// Total size for objects that are replicas
|
||||
ReplicaSize uint64 `json:"objectsReplicaTotalSize"`
|
||||
|
||||
// Total number of objects pending replication
|
||||
ReplicationPendingCount uint64 `json:"objectsPendingReplicationCount"`
|
||||
|
||||
// Total number of objects that failed replication
|
||||
ReplicationFailedCount uint64 `json:"objectsFailedReplicationCount"`
|
||||
|
||||
// Total number of buckets in this cluster
|
||||
BucketsCount uint64 `json:"bucketsCount"`
|
||||
|
||||
// Buckets usage info provides following information across all buckets
|
||||
// - total size of the bucket
|
||||
// - total objects in a bucket
|
||||
// - object size histogram per bucket
|
||||
BucketsUsage map[string]BucketUsageInfo `json:"bucketsUsageInfo"`
|
||||
|
||||
// Deprecated kept here for backward compatibility reasons.
|
||||
BucketSizes map[string]uint64 `json:"bucketsSizes"`
|
||||
}
|
||||
|
||||
// DataUsageInfo - returns data usage of the current object API
|
||||
func (adm *AdminClient) DataUsageInfo(ctx context.Context) (DataUsageInfo, error) {
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, requestData{relPath: adminAPIPrefix + "/datausageinfo"})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return DataUsageInfo{}, err
|
||||
}
|
||||
|
||||
// Check response http status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return DataUsageInfo{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
// Unmarshal the server's json response
|
||||
var dataUsageInfo DataUsageInfo
|
||||
if err = json.NewDecoder(resp.Body).Decode(&dataUsageInfo); err != nil {
|
||||
return DataUsageInfo{}, err
|
||||
}
|
||||
|
||||
return dataUsageInfo, nil
|
||||
}
|
||||
|
||||
// InfoMessage container to hold server admin related information.
|
||||
type InfoMessage struct {
|
||||
Mode string `json:"mode,omitempty"`
|
||||
Domain []string `json:"domain,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
SQSARN []string `json:"sqsARN,omitempty"`
|
||||
DeploymentID string `json:"deploymentID,omitempty"`
|
||||
Buckets Buckets `json:"buckets,omitempty"`
|
||||
Objects Objects `json:"objects,omitempty"`
|
||||
Usage Usage `json:"usage,omitempty"`
|
||||
Services Services `json:"services,omitempty"`
|
||||
Backend interface{} `json:"backend,omitempty"`
|
||||
Servers []ServerProperties `json:"servers,omitempty"`
|
||||
}
|
||||
|
||||
// Services contains different services information
|
||||
type Services struct {
|
||||
KMS KMS `json:"kms,omitempty"`
|
||||
LDAP LDAP `json:"ldap,omitempty"`
|
||||
Logger []Logger `json:"logger,omitempty"`
|
||||
Audit []Audit `json:"audit,omitempty"`
|
||||
Notifications []map[string][]TargetIDStatus `json:"notifications,omitempty"`
|
||||
}
|
||||
|
||||
// Buckets contains the number of buckets
|
||||
type Buckets struct {
|
||||
Count uint64 `json:"count"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Objects contains the number of objects
|
||||
type Objects struct {
|
||||
Count uint64 `json:"count"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Usage contains the total size used
|
||||
type Usage struct {
|
||||
Size uint64 `json:"size"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KMS contains KMS status information
|
||||
type KMS struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Encrypt string `json:"encrypt,omitempty"`
|
||||
Decrypt string `json:"decrypt,omitempty"`
|
||||
}
|
||||
|
||||
// LDAP contains ldap status
|
||||
type LDAP struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Status of endpoint
|
||||
type Status struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Audit contains audit logger status
|
||||
type Audit map[string]Status
|
||||
|
||||
// Logger contains logger status
|
||||
type Logger map[string]Status
|
||||
|
||||
// TargetIDStatus containsid and status
|
||||
type TargetIDStatus map[string]Status
|
||||
|
||||
// backendType - indicates the type of backend storage
|
||||
type backendType string
|
||||
|
||||
const (
|
||||
// FsType - Backend is FS Type
|
||||
FsType = backendType("FS")
|
||||
// ErasureType - Backend is Erasure type
|
||||
ErasureType = backendType("Erasure")
|
||||
)
|
||||
|
||||
// FSBackend contains specific FS storage information
|
||||
type FSBackend struct {
|
||||
Type backendType `json:"backendType,omitempty"`
|
||||
}
|
||||
|
||||
// ErasureBackend contains specific erasure storage information
|
||||
type ErasureBackend struct {
|
||||
Type backendType `json:"backendType,omitempty"`
|
||||
OnlineDisks int `json:"onlineDisks,omitempty"`
|
||||
OfflineDisks int `json:"offlineDisks,omitempty"`
|
||||
// Parity disks for currently configured Standard storage class.
|
||||
StandardSCParity int `json:"standardSCParity,omitempty"`
|
||||
// Parity disks for currently configured Reduced Redundancy storage class.
|
||||
RRSCParity int `json:"rrSCParity,omitempty"`
|
||||
}
|
||||
|
||||
// ServerProperties holds server information
|
||||
type ServerProperties struct {
|
||||
State string `json:"state,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Uptime int64 `json:"uptime,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
CommitID string `json:"commitID,omitempty"`
|
||||
Network map[string]string `json:"network,omitempty"`
|
||||
Disks []Disk `json:"drives,omitempty"`
|
||||
PoolNumber int `json:"poolNumber,omitempty"`
|
||||
MemStats runtime.MemStats `json:"mem_stats"`
|
||||
}
|
||||
|
||||
// DiskMetrics has the information about XL Storage APIs
|
||||
// the number of calls of each API and the moving average of
|
||||
// the duration of each API.
|
||||
type DiskMetrics struct {
|
||||
APILatencies map[string]string `json:"apiLatencies,omitempty"`
|
||||
APICalls map[string]uint64 `json:"apiCalls,omitempty"`
|
||||
}
|
||||
|
||||
// Disk holds Disk information
|
||||
type Disk struct {
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
RootDisk bool `json:"rootDisk,omitempty"`
|
||||
DrivePath string `json:"path,omitempty"`
|
||||
Healing bool `json:"healing,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
TotalSpace uint64 `json:"totalspace,omitempty"`
|
||||
UsedSpace uint64 `json:"usedspace,omitempty"`
|
||||
AvailableSpace uint64 `json:"availspace,omitempty"`
|
||||
ReadThroughput float64 `json:"readthroughput,omitempty"`
|
||||
WriteThroughPut float64 `json:"writethroughput,omitempty"`
|
||||
ReadLatency float64 `json:"readlatency,omitempty"`
|
||||
WriteLatency float64 `json:"writelatency,omitempty"`
|
||||
Utilization float64 `json:"utilization,omitempty"`
|
||||
Metrics *DiskMetrics `json:"metrics,omitempty"`
|
||||
HealInfo *HealingDisk `json:"heal_info,omitempty"`
|
||||
|
||||
// Indexes, will be -1 until assigned a set.
|
||||
PoolIndex int `json:"pool_index"`
|
||||
SetIndex int `json:"set_index"`
|
||||
DiskIndex int `json:"disk_index"`
|
||||
}
|
||||
|
||||
// ServerInfo - Connect to a minio server and call Server Admin Info Management API
|
||||
// to fetch server's information represented by infoMessage structure
|
||||
func (adm *AdminClient) ServerInfo(ctx context.Context) (InfoMessage, error) {
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodGet,
|
||||
requestData{relPath: adminAPIPrefix + "/info"},
|
||||
)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return InfoMessage{}, err
|
||||
}
|
||||
|
||||
// Check response http status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return InfoMessage{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
// Unmarshal the server's json response
|
||||
var message InfoMessage
|
||||
if err = json.NewDecoder(resp.Body).Decode(&message); err != nil {
|
||||
return InfoMessage{}, err
|
||||
}
|
||||
|
||||
return message, nil
|
||||
}
|
||||
@@ -1,85 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// CreateKey tries to create a new master key with the given keyID
|
||||
// at the KMS connected to a MinIO server.
|
||||
func (adm *AdminClient) CreateKey(ctx context.Context, keyID string) error {
|
||||
// POST /minio/admin/v3/kms/key/create?key-id=<keyID>
|
||||
qv := url.Values{}
|
||||
qv.Set("key-id", keyID)
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/kms/key/create",
|
||||
queryValues: qv,
|
||||
}
|
||||
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPost, reqData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeResponse(resp)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKeyStatus requests status information about the key referenced by keyID
|
||||
// from the KMS connected to a MinIO by performing a Admin-API request.
|
||||
// It basically hits the `/minio/admin/v3/kms/key/status` API endpoint.
|
||||
func (adm *AdminClient) GetKeyStatus(ctx context.Context, keyID string) (*KMSKeyStatus, error) {
|
||||
// GET /minio/admin/v3/kms/key/status?key-id=<keyID>
|
||||
qv := url.Values{}
|
||||
qv.Set("key-id", keyID)
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/kms/key/status",
|
||||
queryValues: qv,
|
||||
}
|
||||
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closeResponse(resp)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
var keyInfo KMSKeyStatus
|
||||
if err = json.NewDecoder(resp.Body).Decode(&keyInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &keyInfo, nil
|
||||
}
|
||||
|
||||
// KMSKeyStatus contains some status information about a KMS master key.
|
||||
// The MinIO server tries to access the KMS and perform encryption and
|
||||
// decryption operations. If the MinIO server can access the KMS and
|
||||
// all master key operations succeed it returns a status containing only
|
||||
// the master key ID but no error.
|
||||
type KMSKeyStatus struct {
|
||||
KeyID string `json:"key-id"`
|
||||
EncryptionErr string `json:"encryption-error,omitempty"` // An empty error == success
|
||||
DecryptionErr string `json:"decryption-error,omitempty"` // An empty error == success
|
||||
}
|
||||
@@ -1,204 +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 madmin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// KV - is a shorthand of each key value.
|
||||
type KV struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// KVS - is a shorthand for some wrapper functions
|
||||
// to operate on list of key values.
|
||||
type KVS []KV
|
||||
|
||||
// Empty - return if kv is empty
|
||||
func (kvs KVS) Empty() bool {
|
||||
return len(kvs) == 0
|
||||
}
|
||||
|
||||
// Set sets a value, if not sets a default value.
|
||||
func (kvs *KVS) Set(key, value string) {
|
||||
for i, kv := range *kvs {
|
||||
if kv.Key == key {
|
||||
(*kvs)[i] = KV{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
*kvs = append(*kvs, KV{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
// Get - returns the value of a key, if not found returns empty.
|
||||
func (kvs KVS) Get(key string) string {
|
||||
v, ok := kvs.Lookup(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Lookup - lookup a key in a list of KVS
|
||||
func (kvs KVS) Lookup(key string) (string, bool) {
|
||||
for _, kv := range kvs {
|
||||
if kv.Key == key {
|
||||
return kv.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Target signifies an individual target
|
||||
type Target struct {
|
||||
SubSystem string `json:"subSys"`
|
||||
KVS KVS `json:"kvs"`
|
||||
}
|
||||
|
||||
// Standard config keys and values.
|
||||
const (
|
||||
EnableKey = "enable"
|
||||
CommentKey = "comment"
|
||||
|
||||
// Enable values
|
||||
EnableOn = "on"
|
||||
EnableOff = "off"
|
||||
)
|
||||
|
||||
// HasSpace - returns if given string has space.
|
||||
func HasSpace(s string) bool {
|
||||
for _, r := range s {
|
||||
if unicode.IsSpace(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Constant separators
|
||||
const (
|
||||
SubSystemSeparator = `:`
|
||||
KvSeparator = `=`
|
||||
KvComment = `#`
|
||||
KvSpaceSeparator = ` `
|
||||
KvNewline = "\n"
|
||||
KvDoubleQuote = `"`
|
||||
KvSingleQuote = `'`
|
||||
|
||||
Default = `_`
|
||||
)
|
||||
|
||||
// SanitizeValue - this function is needed, to trim off single or double quotes, creeping into the values.
|
||||
func SanitizeValue(v string) string {
|
||||
v = strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(v), KvDoubleQuote), KvDoubleQuote)
|
||||
return strings.TrimSuffix(strings.TrimPrefix(v, KvSingleQuote), KvSingleQuote)
|
||||
}
|
||||
|
||||
// KvFields - converts an input string of form "k1=v1 k2=v2" into
|
||||
// fields of ["k1=v1", "k2=v2"], the tokenization of each `k=v`
|
||||
// happens with the right number of input keys, if keys
|
||||
// input is empty returned value is empty slice as well.
|
||||
func KvFields(input string, keys []string) []string {
|
||||
var valueIndexes []int
|
||||
for _, key := range keys {
|
||||
i := strings.Index(input, key+KvSeparator)
|
||||
if i == -1 {
|
||||
continue
|
||||
}
|
||||
valueIndexes = append(valueIndexes, i)
|
||||
}
|
||||
|
||||
sort.Ints(valueIndexes)
|
||||
var fields = make([]string, len(valueIndexes))
|
||||
for i := range valueIndexes {
|
||||
j := i + 1
|
||||
if j < len(valueIndexes) {
|
||||
fields[i] = strings.TrimSpace(input[valueIndexes[i]:valueIndexes[j]])
|
||||
} else {
|
||||
fields[i] = strings.TrimSpace(input[valueIndexes[i]:])
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// ParseTarget - adds new targets, by parsing the input string s.
|
||||
func ParseTarget(s string, help Help) (*Target, error) {
|
||||
inputs := strings.SplitN(s, KvSpaceSeparator, 2)
|
||||
if len(inputs) <= 1 {
|
||||
return nil, fmt.Errorf("invalid number of arguments '%s'", s)
|
||||
}
|
||||
|
||||
subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
|
||||
if len(subSystemValue) == 0 {
|
||||
return nil, fmt.Errorf("invalid number of arguments %s", s)
|
||||
}
|
||||
|
||||
if help.SubSys != subSystemValue[0] {
|
||||
return nil, fmt.Errorf("unknown sub-system %s", subSystemValue[0])
|
||||
}
|
||||
|
||||
var kvs = KVS{}
|
||||
var prevK string
|
||||
for _, v := range KvFields(inputs[1], help.Keys()) {
|
||||
kv := strings.SplitN(v, KvSeparator, 2)
|
||||
if len(kv) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(kv) == 1 && prevK != "" {
|
||||
value := strings.Join([]string{
|
||||
kvs.Get(prevK),
|
||||
SanitizeValue(kv[0]),
|
||||
}, KvSpaceSeparator)
|
||||
kvs.Set(prevK, value)
|
||||
continue
|
||||
}
|
||||
if len(kv) == 2 {
|
||||
prevK = kv[0]
|
||||
kvs.Set(prevK, SanitizeValue(kv[1]))
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("value for key '%s' cannot be empty", kv[0])
|
||||
}
|
||||
|
||||
return &Target{
|
||||
SubSystem: inputs[0],
|
||||
KVS: kvs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseSubSysTarget - parse a sub-system target
|
||||
func ParseSubSysTarget(buf []byte, help Help) (target *Target, err error) {
|
||||
bio := bufio.NewScanner(bytes.NewReader(buf))
|
||||
if bio.Scan() {
|
||||
return ParseTarget(bio.Text(), help)
|
||||
}
|
||||
return nil, bio.Err()
|
||||
}
|
||||
@@ -1,177 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// InfoCannedPolicy - expand canned policy into JSON structure.
|
||||
func (adm *AdminClient) InfoCannedPolicy(ctx context.Context, policyName string) (*iampolicy.Policy, error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("name", policyName)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/info-canned-policy",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/info-canned-policy
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return iampolicy.ParseConfig(resp.Body)
|
||||
}
|
||||
|
||||
// ListCannedPolicies - list all configured canned policies.
|
||||
func (adm *AdminClient) ListCannedPolicies(ctx context.Context) (map[string]*iampolicy.Policy, error) {
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/list-canned-policies",
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/list-canned-policies
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var policies = make(map[string]*iampolicy.Policy)
|
||||
if err = json.Unmarshal(respBytes, &policies); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// RemoveCannedPolicy - remove a policy for a canned.
|
||||
func (adm *AdminClient) RemoveCannedPolicy(ctx context.Context, policyName string) error {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("name", policyName)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/remove-canned-policy",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute DELETE on /minio/admin/v3/remove-canned-policy to remove policy.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCannedPolicy - adds a policy for a canned.
|
||||
func (adm *AdminClient) AddCannedPolicy(ctx context.Context, policyName string, policy *iampolicy.Policy) error {
|
||||
if policy == nil {
|
||||
return ErrInvalidArgument("policy input cannot be empty")
|
||||
}
|
||||
|
||||
if err := policy.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("name", policyName)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/add-canned-policy",
|
||||
queryValues: queryValues,
|
||||
content: buf,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/add-canned-policy to set policy.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPolicy - sets the policy for a user or a group.
|
||||
func (adm *AdminClient) SetPolicy(ctx context.Context, policyName, entityName string, isGroup bool) error {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("policyName", policyName)
|
||||
queryValues.Set("userOrGroup", entityName)
|
||||
groupStr := "false"
|
||||
if isGroup {
|
||||
groupStr = "true"
|
||||
}
|
||||
queryValues.Set("isGroup", groupStr)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-user-or-group-policy",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-user-or-group-policy to set policy.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,112 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ProfilerType represents the profiler type
|
||||
// passed to the profiler subsystem.
|
||||
type ProfilerType string
|
||||
|
||||
// Different supported profiler types.
|
||||
const (
|
||||
ProfilerCPU ProfilerType = "cpu" // represents CPU profiler type
|
||||
ProfilerMEM ProfilerType = "mem" // represents MEM profiler type
|
||||
ProfilerBlock ProfilerType = "block" // represents Block profiler type
|
||||
ProfilerMutex ProfilerType = "mutex" // represents Mutex profiler type
|
||||
ProfilerTrace ProfilerType = "trace" // represents Trace profiler type
|
||||
ProfilerThreads ProfilerType = "threads" // represents ThreadCreate profiler type
|
||||
ProfilerGoroutines ProfilerType = "goroutines" // represents Goroutine dumps.
|
||||
)
|
||||
|
||||
// StartProfilingResult holds the result of starting
|
||||
// profiler result in a given node.
|
||||
type StartProfilingResult struct {
|
||||
NodeName string `json:"nodeName"`
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// StartProfiling makes an admin call to remotely start profiling on a standalone
|
||||
// server or the whole cluster in case of a distributed setup.
|
||||
func (adm *AdminClient) StartProfiling(ctx context.Context, profiler ProfilerType) ([]StartProfilingResult, error) {
|
||||
v := url.Values{}
|
||||
v.Set("profilerType", string(profiler))
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodPost, requestData{
|
||||
relPath: adminAPIPrefix + "/profiling/start",
|
||||
queryValues: v,
|
||||
},
|
||||
)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
jsonResult, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var startResults []StartProfilingResult
|
||||
err = json.Unmarshal(jsonResult, &startResults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return startResults, nil
|
||||
}
|
||||
|
||||
// DownloadProfilingData makes an admin call to download profiling data of a standalone
|
||||
// server or of the whole cluster in case of a distributed setup.
|
||||
func (adm *AdminClient) DownloadProfilingData(ctx context.Context) (io.ReadCloser, error) {
|
||||
path := fmt.Sprintf(adminAPIPrefix + "/profiling/download")
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodGet, requestData{
|
||||
relPath: path,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
closeResponse(resp)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("body is nil")
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
@@ -1,122 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// QuotaType represents bucket quota type
|
||||
type QuotaType string
|
||||
|
||||
const (
|
||||
// HardQuota specifies a hard quota of usage for bucket
|
||||
HardQuota QuotaType = "hard"
|
||||
// FIFOQuota specifies a quota limit beyond which older files are deleted from bucket
|
||||
FIFOQuota QuotaType = "fifo"
|
||||
)
|
||||
|
||||
// IsValid returns true if quota type is one of FIFO or Hard
|
||||
func (t QuotaType) IsValid() bool {
|
||||
return t == HardQuota || t == FIFOQuota
|
||||
}
|
||||
|
||||
// BucketQuota holds bucket quota restrictions
|
||||
type BucketQuota struct {
|
||||
Quota uint64 `json:"quota"`
|
||||
Type QuotaType `json:"quotatype,omitempty"`
|
||||
}
|
||||
|
||||
// IsValid returns false if quota is invalid
|
||||
// empty quota when Quota == 0 is always true.
|
||||
func (q BucketQuota) IsValid() bool {
|
||||
if q.Quota > 0 {
|
||||
return q.Type.IsValid()
|
||||
}
|
||||
// Empty configs are valid.
|
||||
return true
|
||||
}
|
||||
|
||||
// GetBucketQuota - get info on a user
|
||||
func (adm *AdminClient) GetBucketQuota(ctx context.Context, bucket string) (q BucketQuota, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/get-bucket-quota",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/get-quota
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return q, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return q, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return q, err
|
||||
}
|
||||
if err = json.Unmarshal(b, &q); err != nil {
|
||||
return q, err
|
||||
}
|
||||
|
||||
return q, nil
|
||||
}
|
||||
|
||||
// SetBucketQuota - sets a bucket's quota, if quota is set to '0'
|
||||
// quota is disabled.
|
||||
func (adm *AdminClient) SetBucketQuota(ctx context.Context, bucket string, quota *BucketQuota) error {
|
||||
data, err := json.Marshal(quota)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-bucket-quota",
|
||||
queryValues: queryValues,
|
||||
content: data,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-bucket-quota to set quota for a bucket.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,371 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
)
|
||||
|
||||
// ServiceType represents service type
|
||||
type ServiceType string
|
||||
|
||||
const (
|
||||
// ReplicationService specifies replication service
|
||||
ReplicationService ServiceType = "replication"
|
||||
)
|
||||
|
||||
// IsValid returns true if ARN type represents replication
|
||||
func (t ServiceType) IsValid() bool {
|
||||
return t == ReplicationService
|
||||
}
|
||||
|
||||
// ARN is a struct to define arn.
|
||||
type ARN struct {
|
||||
Type ServiceType
|
||||
ID string
|
||||
Region string
|
||||
Bucket string
|
||||
}
|
||||
|
||||
// Empty returns true if arn struct is empty
|
||||
func (a ARN) Empty() bool {
|
||||
return !a.Type.IsValid()
|
||||
}
|
||||
func (a ARN) String() string {
|
||||
return fmt.Sprintf("arn:minio:%s:%s:%s:%s", a.Type, a.Region, a.ID, a.Bucket)
|
||||
}
|
||||
|
||||
// ParseARN return ARN struct from string in arn format.
|
||||
func ParseARN(s string) (*ARN, error) {
|
||||
// ARN must be in the format of arn:minio:<Type>:<REGION>:<ID>:<remote-bucket>
|
||||
if !strings.HasPrefix(s, "arn:minio:") {
|
||||
return nil, fmt.Errorf("Invalid ARN %s", s)
|
||||
}
|
||||
|
||||
tokens := strings.Split(s, ":")
|
||||
if len(tokens) != 6 {
|
||||
return nil, fmt.Errorf("Invalid ARN %s", s)
|
||||
}
|
||||
|
||||
if tokens[4] == "" || tokens[5] == "" {
|
||||
return nil, fmt.Errorf("Invalid ARN %s", s)
|
||||
}
|
||||
|
||||
return &ARN{
|
||||
Type: ServiceType(tokens[2]),
|
||||
Region: tokens[3],
|
||||
ID: tokens[4],
|
||||
Bucket: tokens[5],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BucketTarget represents the target bucket and site association.
|
||||
type BucketTarget struct {
|
||||
SourceBucket string `json:"sourcebucket"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Credentials *auth.Credentials `json:"credentials"`
|
||||
TargetBucket string `json:"targetbucket"`
|
||||
Secure bool `json:"secure"`
|
||||
Path string `json:"path,omitempty"`
|
||||
API string `json:"api,omitempty"`
|
||||
Arn string `json:"arn,omitempty"`
|
||||
Type ServiceType `json:"type"`
|
||||
Region string `json:"omitempty"`
|
||||
BandwidthLimit int64 `json:"bandwidthlimit,omitempty"`
|
||||
ReplicationSync bool `json:"replicationSync"`
|
||||
StorageClass string `json:"storageclass,omitempty"`
|
||||
HealthCheckDuration time.Duration `json:"healthCheckDuration,omitempty"`
|
||||
DisableProxy bool `json:"disableProxy"`
|
||||
}
|
||||
|
||||
// Clone returns shallow clone of BucketTarget without secret key in credentials
|
||||
func (t *BucketTarget) Clone() BucketTarget {
|
||||
return BucketTarget{
|
||||
SourceBucket: t.SourceBucket,
|
||||
Endpoint: t.Endpoint,
|
||||
TargetBucket: t.TargetBucket,
|
||||
Credentials: &auth.Credentials{AccessKey: t.Credentials.AccessKey},
|
||||
Secure: t.Secure,
|
||||
Path: t.Path,
|
||||
API: t.API,
|
||||
Arn: t.Arn,
|
||||
Type: t.Type,
|
||||
Region: t.Region,
|
||||
BandwidthLimit: t.BandwidthLimit,
|
||||
ReplicationSync: t.ReplicationSync,
|
||||
StorageClass: t.StorageClass, // target storage class
|
||||
HealthCheckDuration: t.HealthCheckDuration,
|
||||
DisableProxy: t.DisableProxy,
|
||||
}
|
||||
}
|
||||
|
||||
// URL returns target url
|
||||
func (t BucketTarget) URL() *url.URL {
|
||||
scheme := "http"
|
||||
if t.Secure {
|
||||
scheme = "https"
|
||||
}
|
||||
return &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: t.Endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// Empty returns true if struct is empty.
|
||||
func (t BucketTarget) Empty() bool {
|
||||
return t.String() == "" || t.Credentials == nil
|
||||
}
|
||||
|
||||
func (t *BucketTarget) String() string {
|
||||
return fmt.Sprintf("%s %s", t.Endpoint, t.TargetBucket)
|
||||
}
|
||||
|
||||
// BucketTargets represents a slice of bucket targets by type and endpoint
|
||||
type BucketTargets struct {
|
||||
Targets []BucketTarget
|
||||
}
|
||||
|
||||
// Empty returns true if struct is empty.
|
||||
func (t BucketTargets) Empty() bool {
|
||||
if len(t.Targets) == 0 {
|
||||
return true
|
||||
}
|
||||
empty := true
|
||||
for _, t := range t.Targets {
|
||||
if !t.Empty() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return empty
|
||||
}
|
||||
|
||||
// ListRemoteTargets - gets target(s) for this bucket
|
||||
func (adm *AdminClient) ListRemoteTargets(ctx context.Context, bucket, arnType string) (targets []BucketTarget, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
queryValues.Set("type", arnType)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/list-remote-targets",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/list-remote-targets
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return targets, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return targets, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return targets, err
|
||||
}
|
||||
if err = json.Unmarshal(b, &targets); err != nil {
|
||||
return targets, err
|
||||
}
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
// SetRemoteTarget sets up a remote target for this bucket
|
||||
func (adm *AdminClient) SetRemoteTarget(ctx context.Context, bucket string, target *BucketTarget) (string, error) {
|
||||
data, err := json.Marshal(target)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encData, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-remote-target",
|
||||
queryValues: queryValues,
|
||||
content: encData,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", httpRespToErrorResponse(resp)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var arn string
|
||||
if err = json.Unmarshal(b, &arn); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return arn, nil
|
||||
}
|
||||
|
||||
// TargetUpdateType - type of update on the remote target
|
||||
type TargetUpdateType int
|
||||
|
||||
const (
|
||||
// CredentialsUpdateType update creds
|
||||
CredentialsUpdateType TargetUpdateType = 1 + iota
|
||||
// SyncUpdateType update synchronous replication setting
|
||||
SyncUpdateType
|
||||
// ProxyUpdateType update proxy setting
|
||||
ProxyUpdateType
|
||||
// BandwidthLimitUpdateType update bandwidth limit
|
||||
BandwidthLimitUpdateType
|
||||
// HealthCheckDurationUpdateType update health check duration
|
||||
HealthCheckDurationUpdateType
|
||||
// PathUpdateType update Path
|
||||
PathUpdateType
|
||||
)
|
||||
|
||||
// GetTargetUpdateOps returns a slice of update operations being
|
||||
// performed with `mc admin bucket remote edit`
|
||||
func GetTargetUpdateOps(values url.Values) []TargetUpdateType {
|
||||
var ops []TargetUpdateType
|
||||
if values.Get("update") != "true" {
|
||||
return ops
|
||||
}
|
||||
if values.Get("creds") == "true" {
|
||||
ops = append(ops, CredentialsUpdateType)
|
||||
}
|
||||
if values.Get("sync") == "true" {
|
||||
ops = append(ops, SyncUpdateType)
|
||||
}
|
||||
if values.Get("proxy") == "true" {
|
||||
ops = append(ops, ProxyUpdateType)
|
||||
}
|
||||
if values.Get("healthcheck") == "true" {
|
||||
ops = append(ops, HealthCheckDurationUpdateType)
|
||||
}
|
||||
if values.Get("bandwidth") == "true" {
|
||||
ops = append(ops, BandwidthLimitUpdateType)
|
||||
}
|
||||
if values.Get("path") == "true" {
|
||||
ops = append(ops, PathUpdateType)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
// UpdateRemoteTarget updates credentials for a remote bucket target
|
||||
func (adm *AdminClient) UpdateRemoteTarget(ctx context.Context, target *BucketTarget, ops ...TargetUpdateType) (string, error) {
|
||||
if target == nil {
|
||||
return "", fmt.Errorf("target cannot be nil")
|
||||
}
|
||||
data, err := json.Marshal(target)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encData, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", target.SourceBucket)
|
||||
queryValues.Set("update", "true")
|
||||
|
||||
for _, op := range ops {
|
||||
switch op {
|
||||
case CredentialsUpdateType:
|
||||
queryValues.Set("creds", "true")
|
||||
case SyncUpdateType:
|
||||
queryValues.Set("sync", "true")
|
||||
case ProxyUpdateType:
|
||||
queryValues.Set("proxy", "true")
|
||||
case BandwidthLimitUpdateType:
|
||||
queryValues.Set("bandwidth", "true")
|
||||
case HealthCheckDurationUpdateType:
|
||||
queryValues.Set("healthcheck", "true")
|
||||
case PathUpdateType:
|
||||
queryValues.Set("path", "true")
|
||||
}
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-remote-target",
|
||||
queryValues: queryValues,
|
||||
content: encData,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", httpRespToErrorResponse(resp)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var arn string
|
||||
if err = json.Unmarshal(b, &arn); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return arn, nil
|
||||
}
|
||||
|
||||
// RemoveRemoteTarget removes a remote target associated with particular ARN for this bucket
|
||||
func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("bucket", bucket)
|
||||
queryValues.Set("arn", arn)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/remove-remote-target",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/remove-remote-target to remove a target for this bucket
|
||||
// with specific ARN
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright (c) 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 madmin
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func isOpsEqual(op1 []TargetUpdateType, op2 []TargetUpdateType) bool {
|
||||
if len(op1) != len(op2) {
|
||||
return false
|
||||
}
|
||||
for _, o1 := range op1 {
|
||||
found := false
|
||||
for _, o2 := range op2 {
|
||||
if o2 == o1 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TestGetTargetUpdateOps tests GetTargetUpdateOps
|
||||
func TestGetTargetUpdateOps(t *testing.T) {
|
||||
testCases := []struct {
|
||||
values url.Values
|
||||
expectedOps []TargetUpdateType
|
||||
}{
|
||||
{values: url.Values{
|
||||
"update": []string{"true"}},
|
||||
expectedOps: []TargetUpdateType{},
|
||||
},
|
||||
{values: url.Values{
|
||||
"update": []string{"false"},
|
||||
"path": []string{"true"},
|
||||
},
|
||||
expectedOps: []TargetUpdateType{},
|
||||
},
|
||||
{values: url.Values{
|
||||
"update": []string{"true"},
|
||||
"path": []string{""},
|
||||
},
|
||||
expectedOps: []TargetUpdateType{},
|
||||
},
|
||||
{values: url.Values{
|
||||
"update": []string{"true"},
|
||||
"path": []string{"true"},
|
||||
"bzzzz": []string{"true"},
|
||||
},
|
||||
expectedOps: []TargetUpdateType{PathUpdateType},
|
||||
},
|
||||
|
||||
{values: url.Values{
|
||||
"update": []string{"true"},
|
||||
"path": []string{"true"},
|
||||
"creds": []string{"true"},
|
||||
"sync": []string{"true"},
|
||||
"proxy": []string{"true"},
|
||||
"bandwidth": []string{"true"},
|
||||
"healthcheck": []string{"true"},
|
||||
},
|
||||
expectedOps: []TargetUpdateType{
|
||||
PathUpdateType, CredentialsUpdateType, SyncUpdateType, ProxyUpdateType, BandwidthLimitUpdateType, HealthCheckDurationUpdateType},
|
||||
},
|
||||
}
|
||||
for i, test := range testCases {
|
||||
gotOps := GetTargetUpdateOps(test.values)
|
||||
if !isOpsEqual(gotOps, test.expectedOps) {
|
||||
t.Fatalf("test %d: expected %v got %v", i+1, test.expectedOps, gotOps)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxRetry is the maximum number of retries before stopping.
|
||||
var MaxRetry = 10
|
||||
|
||||
// MaxJitter will randomize over the full exponential backoff time
|
||||
const MaxJitter = 1.0
|
||||
|
||||
// NoJitter disables the use of jitter for randomizing the exponential backoff time
|
||||
const NoJitter = 0.0
|
||||
|
||||
// DefaultRetryUnit - default unit multiplicative per retry.
|
||||
// defaults to 1 second.
|
||||
const DefaultRetryUnit = time.Second
|
||||
|
||||
// DefaultRetryCap - Each retry attempt never waits no longer than
|
||||
// this maximum time duration.
|
||||
const DefaultRetryCap = time.Second * 30
|
||||
|
||||
// lockedRandSource provides protected rand source, implements rand.Source interface.
|
||||
type lockedRandSource struct {
|
||||
lk sync.Mutex
|
||||
src rand.Source
|
||||
}
|
||||
|
||||
// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
|
||||
func (r *lockedRandSource) Int63() (n int64) {
|
||||
r.lk.Lock()
|
||||
n = r.src.Int63()
|
||||
r.lk.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Seed uses the provided seed value to initialize the generator to a
|
||||
// deterministic state.
|
||||
func (r *lockedRandSource) Seed(seed int64) {
|
||||
r.lk.Lock()
|
||||
r.src.Seed(seed)
|
||||
r.lk.Unlock()
|
||||
}
|
||||
|
||||
// newRetryTimer creates a timer with exponentially increasing
|
||||
// delays until the maximum retry attempts are reached.
|
||||
func (adm AdminClient) newRetryTimer(ctx context.Context, maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int {
|
||||
attemptCh := make(chan int)
|
||||
|
||||
// computes the exponential backoff duration according to
|
||||
// https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||
exponentialBackoffWait := func(attempt int) time.Duration {
|
||||
// normalize jitter to the range [0, 1.0]
|
||||
if jitter < NoJitter {
|
||||
jitter = NoJitter
|
||||
}
|
||||
if jitter > MaxJitter {
|
||||
jitter = MaxJitter
|
||||
}
|
||||
|
||||
//sleep = random_between(0, min(cap, base * 2 ** attempt))
|
||||
sleep := unit * 1 << uint(attempt)
|
||||
if sleep > cap {
|
||||
sleep = cap
|
||||
}
|
||||
if jitter > NoJitter {
|
||||
sleep -= time.Duration(adm.random.Float64() * float64(sleep) * jitter)
|
||||
}
|
||||
return sleep
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(attemptCh)
|
||||
for i := 0; i < maxRetry; i++ {
|
||||
// Attempts start from 1.
|
||||
select {
|
||||
case attemptCh <- i + 1:
|
||||
case <-ctx.Done():
|
||||
// Stop the routine.
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(exponentialBackoffWait(i)):
|
||||
case <-ctx.Done():
|
||||
// Stop the routine.
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return attemptCh
|
||||
}
|
||||
|
||||
// List of AWS S3 error codes which are retryable.
|
||||
var retryableS3Codes = map[string]struct{}{
|
||||
"RequestError": {},
|
||||
"RequestTimeout": {},
|
||||
"Throttling": {},
|
||||
"ThrottlingException": {},
|
||||
"RequestLimitExceeded": {},
|
||||
"RequestThrottled": {},
|
||||
"InternalError": {},
|
||||
"SlowDown": {},
|
||||
// Add more AWS S3 codes here.
|
||||
}
|
||||
|
||||
// isS3CodeRetryable - is s3 error code retryable.
|
||||
func isS3CodeRetryable(s3Code string) (ok bool) {
|
||||
_, ok = retryableS3Codes[s3Code]
|
||||
return ok
|
||||
}
|
||||
|
||||
// List of HTTP status codes which are retryable.
|
||||
var retryableHTTPStatusCodes = map[int]struct{}{
|
||||
http.StatusRequestTimeout: {},
|
||||
http.StatusTooManyRequests: {},
|
||||
http.StatusInternalServerError: {},
|
||||
http.StatusBadGateway: {},
|
||||
http.StatusServiceUnavailable: {},
|
||||
// Add more HTTP status codes here.
|
||||
}
|
||||
|
||||
// isHTTPStatusRetryable - is HTTP error code retryable.
|
||||
func isHTTPStatusRetryable(httpStatusCode int) (ok bool) {
|
||||
_, ok = retryableHTTPStatusCodes[httpStatusCode]
|
||||
return ok
|
||||
}
|
||||
@@ -1,147 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
trace "github.com/minio/minio/pkg/trace"
|
||||
)
|
||||
|
||||
// ServiceRestart - restarts the MinIO cluster
|
||||
func (adm *AdminClient) ServiceRestart(ctx context.Context) error {
|
||||
return adm.serviceCallAction(ctx, ServiceActionRestart)
|
||||
}
|
||||
|
||||
// ServiceStop - stops the MinIO cluster
|
||||
func (adm *AdminClient) ServiceStop(ctx context.Context) error {
|
||||
return adm.serviceCallAction(ctx, ServiceActionStop)
|
||||
}
|
||||
|
||||
// ServiceAction - type to restrict service-action values
|
||||
type ServiceAction string
|
||||
|
||||
const (
|
||||
// ServiceActionRestart represents restart action
|
||||
ServiceActionRestart ServiceAction = "restart"
|
||||
// ServiceActionStop represents stop action
|
||||
ServiceActionStop = "stop"
|
||||
)
|
||||
|
||||
// serviceCallAction - call service restart/update/stop API.
|
||||
func (adm *AdminClient) serviceCallAction(ctx context.Context, action ServiceAction) error {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("action", string(action))
|
||||
|
||||
// Request API to Restart server
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodPost, requestData{
|
||||
relPath: adminAPIPrefix + "/service",
|
||||
queryValues: queryValues,
|
||||
},
|
||||
)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServiceTraceInfo holds http trace
|
||||
type ServiceTraceInfo struct {
|
||||
Trace trace.Info
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// ServiceTraceOpts holds tracing options
|
||||
type ServiceTraceOpts struct {
|
||||
All bool // Deprecated
|
||||
|
||||
S3 bool
|
||||
Internal bool
|
||||
Storage bool
|
||||
OS bool
|
||||
OnlyErrors bool
|
||||
Threshold time.Duration
|
||||
}
|
||||
|
||||
// ServiceTrace - listen on http trace notifications.
|
||||
func (adm AdminClient) ServiceTrace(ctx context.Context, opts ServiceTraceOpts) <-chan ServiceTraceInfo {
|
||||
traceInfoCh := make(chan ServiceTraceInfo)
|
||||
// Only success, start a routine to start reading line by line.
|
||||
go func(traceInfoCh chan<- ServiceTraceInfo) {
|
||||
defer close(traceInfoCh)
|
||||
for {
|
||||
urlValues := make(url.Values)
|
||||
urlValues.Set("err", strconv.FormatBool(opts.OnlyErrors))
|
||||
urlValues.Set("threshold", opts.Threshold.String())
|
||||
|
||||
if opts.All {
|
||||
// Deprecated flag
|
||||
urlValues.Set("all", "true")
|
||||
} else {
|
||||
urlValues.Set("s3", strconv.FormatBool(opts.S3))
|
||||
urlValues.Set("internal", strconv.FormatBool(opts.Internal))
|
||||
urlValues.Set("storage", strconv.FormatBool(opts.Storage))
|
||||
urlValues.Set("os", strconv.FormatBool(opts.OS))
|
||||
}
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/trace",
|
||||
queryValues: urlValues,
|
||||
}
|
||||
// Execute GET to call trace handler
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
if err != nil {
|
||||
closeResponse(resp)
|
||||
traceInfoCh <- ServiceTraceInfo{Err: err}
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
traceInfoCh <- ServiceTraceInfo{Err: httpRespToErrorResponse(resp)}
|
||||
return
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var info trace.Info
|
||||
if err = dec.Decode(&info); err != nil {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case traceInfoCh <- ServiceTraceInfo{Trace: info}:
|
||||
}
|
||||
}
|
||||
}
|
||||
}(traceInfoCh)
|
||||
|
||||
// Returns the trace info channel, for caller to start reading from.
|
||||
return traceInfoCh
|
||||
}
|
||||
@@ -1,99 +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 madmin
|
||||
|
||||
//go:generate msgp -file $GOFILE
|
||||
|
||||
// TierAzure represents the remote tier configuration for Azure Blob Storage.
|
||||
type TierAzure struct {
|
||||
Endpoint string `json:",omitempty"`
|
||||
AccountName string `json:",omitempty"`
|
||||
AccountKey string `json:",omitempty"`
|
||||
Bucket string `json:",omitempty"`
|
||||
Prefix string `json:",omitempty"`
|
||||
Region string `json:",omitempty"`
|
||||
StorageClass string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AzureOptions supports NewTierAzure to take variadic options
|
||||
type AzureOptions func(*TierAzure) error
|
||||
|
||||
// AzurePrefix helper to supply optional object prefix to NewTierAzure
|
||||
func AzurePrefix(prefix string) func(az *TierAzure) error {
|
||||
return func(az *TierAzure) error {
|
||||
az.Prefix = prefix
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AzureEndpoint helper to supply optional endpoint to NewTierAzure
|
||||
func AzureEndpoint(endpoint string) func(az *TierAzure) error {
|
||||
return func(az *TierAzure) error {
|
||||
az.Endpoint = endpoint
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AzureRegion helper to supply optional region to NewTierAzure
|
||||
func AzureRegion(region string) func(az *TierAzure) error {
|
||||
return func(az *TierAzure) error {
|
||||
az.Region = region
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AzureStorageClass helper to supply optional storage class to NewTierAzure
|
||||
func AzureStorageClass(sc string) func(az *TierAzure) error {
|
||||
return func(az *TierAzure) error {
|
||||
az.StorageClass = sc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewTierAzure returns a TierConfig of Azure type. Returns error if the given
|
||||
// parameters are invalid like name is empty etc.
|
||||
func NewTierAzure(name, accountName, accountKey, bucket string, options ...AzureOptions) (*TierConfig, error) {
|
||||
if name == "" {
|
||||
return nil, ErrTierNameEmpty
|
||||
}
|
||||
|
||||
az := &TierAzure{
|
||||
AccountName: accountName,
|
||||
AccountKey: accountKey,
|
||||
Bucket: bucket,
|
||||
// Defaults
|
||||
Endpoint: "http://blob.core.windows.net",
|
||||
Prefix: "",
|
||||
Region: "",
|
||||
StorageClass: "",
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
err := option(az)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Type: Azure,
|
||||
Name: name,
|
||||
Azure: az,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *TierAzure) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, err = dc.ReadMapKeyPtr()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Endpoint":
|
||||
z.Endpoint, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "AccountName":
|
||||
z.AccountName, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccountName")
|
||||
return
|
||||
}
|
||||
case "AccountKey":
|
||||
z.AccountKey, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccountKey")
|
||||
return
|
||||
}
|
||||
case "Bucket":
|
||||
z.Bucket, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
case "Prefix":
|
||||
z.Prefix, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
case "Region":
|
||||
z.Region, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
case "StorageClass":
|
||||
z.StorageClass, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *TierAzure) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 7
|
||||
// write "Endpoint"
|
||||
err = en.Append(0x87, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Endpoint)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
// write "AccountName"
|
||||
err = en.Append(0xab, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.AccountName)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccountName")
|
||||
return
|
||||
}
|
||||
// write "AccountKey"
|
||||
err = en.Append(0xaa, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4b, 0x65, 0x79)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.AccountKey)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccountKey")
|
||||
return
|
||||
}
|
||||
// write "Bucket"
|
||||
err = en.Append(0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Bucket)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
// write "Prefix"
|
||||
err = en.Append(0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Prefix)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
// write "Region"
|
||||
err = en.Append(0xa6, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Region)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
// write "StorageClass"
|
||||
err = en.Append(0xac, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.StorageClass)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *TierAzure) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 7
|
||||
// string "Endpoint"
|
||||
o = append(o, 0x87, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
o = msgp.AppendString(o, z.Endpoint)
|
||||
// string "AccountName"
|
||||
o = append(o, 0xab, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65)
|
||||
o = msgp.AppendString(o, z.AccountName)
|
||||
// string "AccountKey"
|
||||
o = append(o, 0xaa, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4b, 0x65, 0x79)
|
||||
o = msgp.AppendString(o, z.AccountKey)
|
||||
// string "Bucket"
|
||||
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
o = msgp.AppendString(o, z.Bucket)
|
||||
// string "Prefix"
|
||||
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||
o = msgp.AppendString(o, z.Prefix)
|
||||
// string "Region"
|
||||
o = append(o, 0xa6, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e)
|
||||
o = msgp.AppendString(o, z.Region)
|
||||
// string "StorageClass"
|
||||
o = append(o, 0xac, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73)
|
||||
o = msgp.AppendString(o, z.StorageClass)
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *TierAzure) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Endpoint":
|
||||
z.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "AccountName":
|
||||
z.AccountName, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccountName")
|
||||
return
|
||||
}
|
||||
case "AccountKey":
|
||||
z.AccountKey, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccountKey")
|
||||
return
|
||||
}
|
||||
case "Bucket":
|
||||
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
case "Prefix":
|
||||
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
case "Region":
|
||||
z.Region, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
case "StorageClass":
|
||||
z.StorageClass, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *TierAzure) Msgsize() (s int) {
|
||||
s = 1 + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 12 + msgp.StringPrefixSize + len(z.AccountName) + 11 + msgp.StringPrefixSize + len(z.AccountKey) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 7 + msgp.StringPrefixSize + len(z.Region) + 13 + msgp.StringPrefixSize + len(z.StorageClass)
|
||||
return
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshalTierAzure(t *testing.T) {
|
||||
v := TierAzure{}
|
||||
bts, err := v.MarshalMsg(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
left, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||
}
|
||||
|
||||
left, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMsgTierAzure(b *testing.B) {
|
||||
v := TierAzure{}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.MarshalMsg(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendMsgTierAzure(b *testing.B) {
|
||||
v := TierAzure{}
|
||||
bts := make([]byte, 0, v.Msgsize())
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalTierAzure(b *testing.B) {
|
||||
v := TierAzure{}
|
||||
bts, _ := v.MarshalMsg(nil)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeTierAzure(t *testing.T) {
|
||||
v := TierAzure{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
|
||||
m := v.Msgsize()
|
||||
if buf.Len() > m {
|
||||
t.Log("WARNING: TestEncodeDecodeTierAzure Msgsize() is inaccurate")
|
||||
}
|
||||
|
||||
vn := TierAzure{}
|
||||
err := msgp.Decode(&buf, &vn)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
msgp.Encode(&buf, &v)
|
||||
err = msgp.NewReader(&buf).Skip()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeTierAzure(b *testing.B) {
|
||||
v := TierAzure{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
en := msgp.NewWriter(msgp.Nowhere)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.EncodeMsg(en)
|
||||
}
|
||||
en.Flush()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeTierAzure(b *testing.B) {
|
||||
v := TierAzure{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||
dc := msgp.NewReader(rd)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := v.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,239 +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 madmin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
)
|
||||
|
||||
//go:generate msgp -file $GOFILE
|
||||
|
||||
// TierConfigV1 is the supported tier config version
|
||||
const TierConfigV1 = "v1"
|
||||
|
||||
// TierType enumerates different remote tier backends.
|
||||
type TierType int
|
||||
|
||||
const (
|
||||
// Unsupported refers to remote tier backend that is not supported in this version
|
||||
Unsupported TierType = iota
|
||||
// S3 refers to AWS S3 compatible backend
|
||||
S3
|
||||
// Azure refers to Azure Blob Storage
|
||||
Azure
|
||||
// GCS refers to Google Cloud Storage
|
||||
GCS
|
||||
)
|
||||
|
||||
// String returns the name of tt's remote tier backend.
|
||||
func (tt TierType) String() string {
|
||||
switch tt {
|
||||
case S3:
|
||||
return "s3"
|
||||
case Azure:
|
||||
return "azure"
|
||||
case GCS:
|
||||
return "gcs"
|
||||
}
|
||||
return "unsupported"
|
||||
}
|
||||
|
||||
// MarshalJSON returns the canonical json representation of tt.
|
||||
func (tt TierType) MarshalJSON() ([]byte, error) {
|
||||
typ := tt.String()
|
||||
return json.Marshal(typ)
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the provided tier type string, failing unmarshal
|
||||
// if data contains invalid tier type.
|
||||
func (tt *TierType) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newtt, err := NewTierType(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*tt = newtt
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTierType creates TierType if scType is a valid tier type string, otherwise
|
||||
// returns an error.
|
||||
func NewTierType(scType string) (TierType, error) {
|
||||
switch scType {
|
||||
case S3.String():
|
||||
return S3, nil
|
||||
case Azure.String():
|
||||
return Azure, nil
|
||||
case GCS.String():
|
||||
return GCS, nil
|
||||
}
|
||||
|
||||
return Unsupported, ErrTierTypeUnsupported
|
||||
}
|
||||
|
||||
// TierConfig represents the different remote tier backend configurations
|
||||
// supported. The specific backend is identified by the Type field. It has a
|
||||
// Version field to allow for backwards-compatible extension in the future.
|
||||
type TierConfig struct {
|
||||
Version string
|
||||
Type TierType `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
S3 *TierS3 `json:",omitempty"`
|
||||
Azure *TierAzure `json:",omitempty"`
|
||||
GCS *TierGCS `json:",omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrTierNameEmpty "remote tier name empty"
|
||||
ErrTierNameEmpty = errors.New("remote tier name empty")
|
||||
// ErrTierInvalidConfig "invalid tier config"
|
||||
ErrTierInvalidConfig = errors.New("invalid tier config")
|
||||
// ErrTierInvalidConfigVersion "invalid tier config version"
|
||||
ErrTierInvalidConfigVersion = errors.New("invalid tier config version")
|
||||
// ErrTierTypeUnsupported "unsupported tier type"
|
||||
ErrTierTypeUnsupported = errors.New("unsupported tier type")
|
||||
)
|
||||
|
||||
// Clone returns a copy of TierConfig with secret key/credentials redacted.
|
||||
func (cfg *TierConfig) Clone() TierConfig {
|
||||
var (
|
||||
s3 TierS3
|
||||
az TierAzure
|
||||
gcs TierGCS
|
||||
)
|
||||
switch cfg.Type {
|
||||
case S3:
|
||||
s3 = *cfg.S3
|
||||
s3.SecretKey = "REDACTED"
|
||||
case Azure:
|
||||
az = *cfg.Azure
|
||||
az.AccountKey = "REDACTED"
|
||||
case GCS:
|
||||
gcs = *cfg.GCS
|
||||
gcs.Creds = "REDACTED"
|
||||
}
|
||||
return TierConfig{
|
||||
Version: cfg.Version,
|
||||
Type: cfg.Type,
|
||||
Name: cfg.Name,
|
||||
S3: &s3,
|
||||
Azure: &az,
|
||||
GCS: &gcs,
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals json value to ensure that Type field is filled in
|
||||
// correspondence with the tier config supplied.
|
||||
// See TestUnmarshalTierConfig for an example json.
|
||||
func (cfg *TierConfig) UnmarshalJSON(b []byte) error {
|
||||
type tierConfig TierConfig
|
||||
var m tierConfig
|
||||
err := json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.Version != TierConfigV1 {
|
||||
return ErrTierInvalidConfigVersion
|
||||
}
|
||||
|
||||
switch m.Type {
|
||||
case S3:
|
||||
if m.S3 == nil {
|
||||
return ErrTierInvalidConfig
|
||||
}
|
||||
case Azure:
|
||||
if m.Azure == nil {
|
||||
return ErrTierInvalidConfig
|
||||
}
|
||||
case GCS:
|
||||
if m.GCS == nil {
|
||||
return ErrTierInvalidConfig
|
||||
}
|
||||
}
|
||||
|
||||
if m.Name == "" {
|
||||
return ErrTierNameEmpty
|
||||
}
|
||||
|
||||
*cfg = TierConfig(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Endpoint returns the remote tier backend endpoint.
|
||||
func (cfg *TierConfig) Endpoint() string {
|
||||
switch cfg.Type {
|
||||
case S3:
|
||||
return cfg.S3.Endpoint
|
||||
case Azure:
|
||||
return cfg.Azure.Endpoint
|
||||
case GCS:
|
||||
return cfg.GCS.Endpoint
|
||||
}
|
||||
log.Printf("unexpected tier type %s", cfg.Type)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Bucket returns the remote tier backend bucket.
|
||||
func (cfg *TierConfig) Bucket() string {
|
||||
switch cfg.Type {
|
||||
case S3:
|
||||
return cfg.S3.Bucket
|
||||
case Azure:
|
||||
return cfg.Azure.Bucket
|
||||
case GCS:
|
||||
return cfg.GCS.Bucket
|
||||
}
|
||||
log.Printf("unexpected tier type %s", cfg.Type)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Prefix returns the remote tier backend prefix.
|
||||
func (cfg *TierConfig) Prefix() string {
|
||||
switch cfg.Type {
|
||||
case S3:
|
||||
return cfg.S3.Prefix
|
||||
case Azure:
|
||||
return cfg.Azure.Prefix
|
||||
case GCS:
|
||||
return cfg.GCS.Prefix
|
||||
}
|
||||
log.Printf("unexpected tier type %s", cfg.Type)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Region returns the remote tier backend region.
|
||||
func (cfg *TierConfig) Region() string {
|
||||
switch cfg.Type {
|
||||
case S3:
|
||||
return cfg.S3.Region
|
||||
case Azure:
|
||||
return cfg.Azure.Region
|
||||
case GCS:
|
||||
return cfg.GCS.Region
|
||||
}
|
||||
log.Printf("unexpected tier type %s", cfg.Type)
|
||||
return ""
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *TierConfig) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, err = dc.ReadMapKeyPtr()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Version":
|
||||
z.Version, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Version")
|
||||
return
|
||||
}
|
||||
case "Type":
|
||||
{
|
||||
var zb0002 int
|
||||
zb0002, err = dc.ReadInt()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Type")
|
||||
return
|
||||
}
|
||||
z.Type = TierType(zb0002)
|
||||
}
|
||||
case "Name":
|
||||
z.Name, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Name")
|
||||
return
|
||||
}
|
||||
case "S3":
|
||||
if dc.IsNil() {
|
||||
err = dc.ReadNil()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "S3")
|
||||
return
|
||||
}
|
||||
z.S3 = nil
|
||||
} else {
|
||||
if z.S3 == nil {
|
||||
z.S3 = new(TierS3)
|
||||
}
|
||||
err = z.S3.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "S3")
|
||||
return
|
||||
}
|
||||
}
|
||||
case "Azure":
|
||||
if dc.IsNil() {
|
||||
err = dc.ReadNil()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Azure")
|
||||
return
|
||||
}
|
||||
z.Azure = nil
|
||||
} else {
|
||||
if z.Azure == nil {
|
||||
z.Azure = new(TierAzure)
|
||||
}
|
||||
err = z.Azure.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Azure")
|
||||
return
|
||||
}
|
||||
}
|
||||
case "GCS":
|
||||
if dc.IsNil() {
|
||||
err = dc.ReadNil()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "GCS")
|
||||
return
|
||||
}
|
||||
z.GCS = nil
|
||||
} else {
|
||||
if z.GCS == nil {
|
||||
z.GCS = new(TierGCS)
|
||||
}
|
||||
err = z.GCS.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "GCS")
|
||||
return
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *TierConfig) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 6
|
||||
// write "Version"
|
||||
err = en.Append(0x86, 0xa7, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Version)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Version")
|
||||
return
|
||||
}
|
||||
// write "Type"
|
||||
err = en.Append(0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteInt(int(z.Type))
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Type")
|
||||
return
|
||||
}
|
||||
// write "Name"
|
||||
err = en.Append(0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Name)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Name")
|
||||
return
|
||||
}
|
||||
// write "S3"
|
||||
err = en.Append(0xa2, 0x53, 0x33)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if z.S3 == nil {
|
||||
err = en.WriteNil()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = z.S3.EncodeMsg(en)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "S3")
|
||||
return
|
||||
}
|
||||
}
|
||||
// write "Azure"
|
||||
err = en.Append(0xa5, 0x41, 0x7a, 0x75, 0x72, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if z.Azure == nil {
|
||||
err = en.WriteNil()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = z.Azure.EncodeMsg(en)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Azure")
|
||||
return
|
||||
}
|
||||
}
|
||||
// write "GCS"
|
||||
err = en.Append(0xa3, 0x47, 0x43, 0x53)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if z.GCS == nil {
|
||||
err = en.WriteNil()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = z.GCS.EncodeMsg(en)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "GCS")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *TierConfig) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 6
|
||||
// string "Version"
|
||||
o = append(o, 0x86, 0xa7, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e)
|
||||
o = msgp.AppendString(o, z.Version)
|
||||
// string "Type"
|
||||
o = append(o, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
o = msgp.AppendInt(o, int(z.Type))
|
||||
// string "Name"
|
||||
o = append(o, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
|
||||
o = msgp.AppendString(o, z.Name)
|
||||
// string "S3"
|
||||
o = append(o, 0xa2, 0x53, 0x33)
|
||||
if z.S3 == nil {
|
||||
o = msgp.AppendNil(o)
|
||||
} else {
|
||||
o, err = z.S3.MarshalMsg(o)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "S3")
|
||||
return
|
||||
}
|
||||
}
|
||||
// string "Azure"
|
||||
o = append(o, 0xa5, 0x41, 0x7a, 0x75, 0x72, 0x65)
|
||||
if z.Azure == nil {
|
||||
o = msgp.AppendNil(o)
|
||||
} else {
|
||||
o, err = z.Azure.MarshalMsg(o)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Azure")
|
||||
return
|
||||
}
|
||||
}
|
||||
// string "GCS"
|
||||
o = append(o, 0xa3, 0x47, 0x43, 0x53)
|
||||
if z.GCS == nil {
|
||||
o = msgp.AppendNil(o)
|
||||
} else {
|
||||
o, err = z.GCS.MarshalMsg(o)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "GCS")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *TierConfig) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Version":
|
||||
z.Version, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Version")
|
||||
return
|
||||
}
|
||||
case "Type":
|
||||
{
|
||||
var zb0002 int
|
||||
zb0002, bts, err = msgp.ReadIntBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Type")
|
||||
return
|
||||
}
|
||||
z.Type = TierType(zb0002)
|
||||
}
|
||||
case "Name":
|
||||
z.Name, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Name")
|
||||
return
|
||||
}
|
||||
case "S3":
|
||||
if msgp.IsNil(bts) {
|
||||
bts, err = msgp.ReadNilBytes(bts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
z.S3 = nil
|
||||
} else {
|
||||
if z.S3 == nil {
|
||||
z.S3 = new(TierS3)
|
||||
}
|
||||
bts, err = z.S3.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "S3")
|
||||
return
|
||||
}
|
||||
}
|
||||
case "Azure":
|
||||
if msgp.IsNil(bts) {
|
||||
bts, err = msgp.ReadNilBytes(bts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
z.Azure = nil
|
||||
} else {
|
||||
if z.Azure == nil {
|
||||
z.Azure = new(TierAzure)
|
||||
}
|
||||
bts, err = z.Azure.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Azure")
|
||||
return
|
||||
}
|
||||
}
|
||||
case "GCS":
|
||||
if msgp.IsNil(bts) {
|
||||
bts, err = msgp.ReadNilBytes(bts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
z.GCS = nil
|
||||
} else {
|
||||
if z.GCS == nil {
|
||||
z.GCS = new(TierGCS)
|
||||
}
|
||||
bts, err = z.GCS.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "GCS")
|
||||
return
|
||||
}
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *TierConfig) Msgsize() (s int) {
|
||||
s = 1 + 8 + msgp.StringPrefixSize + len(z.Version) + 5 + msgp.IntSize + 5 + msgp.StringPrefixSize + len(z.Name) + 3
|
||||
if z.S3 == nil {
|
||||
s += msgp.NilSize
|
||||
} else {
|
||||
s += z.S3.Msgsize()
|
||||
}
|
||||
s += 6
|
||||
if z.Azure == nil {
|
||||
s += msgp.NilSize
|
||||
} else {
|
||||
s += z.Azure.Msgsize()
|
||||
}
|
||||
s += 4
|
||||
if z.GCS == nil {
|
||||
s += msgp.NilSize
|
||||
} else {
|
||||
s += z.GCS.Msgsize()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *TierType) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
{
|
||||
var zb0001 int
|
||||
zb0001, err = dc.ReadInt()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
(*z) = TierType(zb0001)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z TierType) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = en.WriteInt(int(z))
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z TierType) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
o = msgp.AppendInt(o, int(z))
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *TierType) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
{
|
||||
var zb0001 int
|
||||
zb0001, bts, err = msgp.ReadIntBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
(*z) = TierType(zb0001)
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z TierType) Msgsize() (s int) {
|
||||
s = msgp.IntSize
|
||||
return
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshalTierConfig(t *testing.T) {
|
||||
v := TierConfig{}
|
||||
bts, err := v.MarshalMsg(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
left, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||
}
|
||||
|
||||
left, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMsgTierConfig(b *testing.B) {
|
||||
v := TierConfig{}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.MarshalMsg(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendMsgTierConfig(b *testing.B) {
|
||||
v := TierConfig{}
|
||||
bts := make([]byte, 0, v.Msgsize())
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalTierConfig(b *testing.B) {
|
||||
v := TierConfig{}
|
||||
bts, _ := v.MarshalMsg(nil)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeTierConfig(t *testing.T) {
|
||||
v := TierConfig{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
|
||||
m := v.Msgsize()
|
||||
if buf.Len() > m {
|
||||
t.Log("WARNING: TestEncodeDecodeTierConfig Msgsize() is inaccurate")
|
||||
}
|
||||
|
||||
vn := TierConfig{}
|
||||
err := msgp.Decode(&buf, &vn)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
msgp.Encode(&buf, &v)
|
||||
err = msgp.NewReader(&buf).Skip()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeTierConfig(b *testing.B) {
|
||||
v := TierConfig{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
en := msgp.NewWriter(msgp.Nowhere)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.EncodeMsg(en)
|
||||
}
|
||||
en.Flush()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeTierConfig(b *testing.B) {
|
||||
v := TierConfig{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||
dc := msgp.NewReader(rd)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := v.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +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 madmin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestUnmarshalInvalidTierConfig tests that TierConfig parsing can catch invalid tier configs
|
||||
func TestUnmarshalInvalidTierConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
cfg TierConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
cfg: TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Name: "S3TIER?",
|
||||
Type: S3,
|
||||
GCS: &TierGCS{
|
||||
Creds: "VWJ1bnR1IDIwLjA0LjEgTFRTIFxuIFxsCgo",
|
||||
Bucket: "ilmtesting",
|
||||
Endpoint: "https://storage.googleapis.com/",
|
||||
Prefix: "testprefix",
|
||||
Region: "us-west-2",
|
||||
StorageClass: "",
|
||||
},
|
||||
},
|
||||
err: ErrTierInvalidConfig,
|
||||
},
|
||||
{
|
||||
cfg: TierConfig{
|
||||
Version: "invalid-version",
|
||||
Name: "INVALIDTIER",
|
||||
Type: GCS,
|
||||
GCS: &TierGCS{
|
||||
Creds: "VWJ1bnR1IDIwLjA0LjEgTFRTIFxuIFxsCgo",
|
||||
Bucket: "ilmtesting",
|
||||
Endpoint: "https://storage.googleapis.com/",
|
||||
Prefix: "testprefix",
|
||||
Region: "us-west-2",
|
||||
StorageClass: "",
|
||||
},
|
||||
},
|
||||
err: ErrTierInvalidConfigVersion,
|
||||
},
|
||||
{
|
||||
cfg: TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Type: GCS,
|
||||
GCS: &TierGCS{
|
||||
Creds: "VWJ1bnR1IDIwLjA0LjEgTFRTIFxuIFxsCgo",
|
||||
Bucket: "ilmtesting",
|
||||
Endpoint: "https://storage.googleapis.com/",
|
||||
Prefix: "testprefix",
|
||||
Region: "us-west-2",
|
||||
StorageClass: "",
|
||||
},
|
||||
},
|
||||
err: ErrTierNameEmpty,
|
||||
},
|
||||
{
|
||||
cfg: TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Name: "GCSTIER",
|
||||
Type: GCS,
|
||||
GCS: &TierGCS{
|
||||
Creds: "VWJ1bnR1IDIwLjA0LjEgTFRTIFxuIFxsCgo",
|
||||
Bucket: "ilmtesting",
|
||||
Endpoint: "https://storage.googleapis.com/",
|
||||
Prefix: "testprefix",
|
||||
Region: "us-west-2",
|
||||
StorageClass: "",
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
data, err := json.Marshal(tc.cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to marshal tier config %v: %v", i+1, tc.cfg, err)
|
||||
}
|
||||
var cfg TierConfig
|
||||
err = json.Unmarshal(data, &cfg)
|
||||
if err != tc.err {
|
||||
t.Fatalf("Test %d: Failed in unmarshal tier config %s: expected %v got %v", i+1, data, tc.err, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid tier type
|
||||
evilJSON := []byte(`{
|
||||
"Version": "v1",
|
||||
"Type" : "not-a-type",
|
||||
"Name" : "GCSTIER3",
|
||||
"GCS" : {
|
||||
"Bucket" : "ilmtesting",
|
||||
"Prefix" : "testprefix3",
|
||||
"Endpoint" : "https://storage.googleapis.com/",
|
||||
"Creds": "VWJ1bnR1IDIwLjA0LjEgTFRTIFxuIFxsCgo",
|
||||
"Region" : "us-west-2",
|
||||
"StorageClass" : ""
|
||||
}
|
||||
}`)
|
||||
var cfg TierConfig
|
||||
err := json.Unmarshal(evilJSON, &cfg)
|
||||
if err != ErrTierTypeUnsupported {
|
||||
t.Fatalf("Expected to fail with unsupported type but got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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 madmin
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
//go:generate msgp -file $GOFILE
|
||||
|
||||
// TierGCS represents the remote tier configuration for Google Cloud Storage
|
||||
type TierGCS struct {
|
||||
Endpoint string `json:",omitempty"` // custom endpoint is not supported for GCS
|
||||
Creds string `json:",omitempty"` // base64 encoding of credentials.json
|
||||
Bucket string `json:",omitempty"`
|
||||
Prefix string `json:",omitempty"`
|
||||
Region string `json:",omitempty"`
|
||||
StorageClass string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// GCSOptions supports NewTierGCS to take variadic options
|
||||
type GCSOptions func(*TierGCS) error
|
||||
|
||||
// GCSPrefix helper to supply optional object prefix to NewTierGCS
|
||||
func GCSPrefix(prefix string) func(*TierGCS) error {
|
||||
return func(gcs *TierGCS) error {
|
||||
gcs.Prefix = prefix
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GCSRegion helper to supply optional region to NewTierGCS
|
||||
func GCSRegion(region string) func(*TierGCS) error {
|
||||
return func(gcs *TierGCS) error {
|
||||
gcs.Region = region
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GCSStorageClass helper to supply optional storage class to NewTierGCS
|
||||
func GCSStorageClass(sc string) func(*TierGCS) error {
|
||||
return func(gcs *TierGCS) error {
|
||||
gcs.StorageClass = sc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetCredentialJSON method returns the credentials JSON bytes.
|
||||
func (gcs *TierGCS) GetCredentialJSON() ([]byte, error) {
|
||||
return base64.URLEncoding.DecodeString(gcs.Creds)
|
||||
}
|
||||
|
||||
// NewTierGCS returns a TierConfig of GCS type. Returns error if the given
|
||||
// parameters are invalid like name is empty etc.
|
||||
func NewTierGCS(name string, credsJSON []byte, bucket string, options ...GCSOptions) (*TierConfig, error) {
|
||||
if name == "" {
|
||||
return nil, ErrTierNameEmpty
|
||||
}
|
||||
creds := base64.URLEncoding.EncodeToString(credsJSON)
|
||||
gcs := &TierGCS{
|
||||
Creds: creds,
|
||||
Bucket: bucket,
|
||||
// Defaults
|
||||
// endpoint is meant only for client-side display purposes
|
||||
Endpoint: "https://storage.googleapis.com/",
|
||||
Prefix: "",
|
||||
Region: "",
|
||||
StorageClass: "",
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
err := option(gcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Type: GCS,
|
||||
Name: name,
|
||||
GCS: gcs,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *TierGCS) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, err = dc.ReadMapKeyPtr()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Endpoint":
|
||||
z.Endpoint, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "Creds":
|
||||
z.Creds, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Creds")
|
||||
return
|
||||
}
|
||||
case "Bucket":
|
||||
z.Bucket, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
case "Prefix":
|
||||
z.Prefix, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
case "Region":
|
||||
z.Region, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
case "StorageClass":
|
||||
z.StorageClass, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *TierGCS) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 6
|
||||
// write "Endpoint"
|
||||
err = en.Append(0x86, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Endpoint)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
// write "Creds"
|
||||
err = en.Append(0xa5, 0x43, 0x72, 0x65, 0x64, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Creds)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Creds")
|
||||
return
|
||||
}
|
||||
// write "Bucket"
|
||||
err = en.Append(0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Bucket)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
// write "Prefix"
|
||||
err = en.Append(0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Prefix)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
// write "Region"
|
||||
err = en.Append(0xa6, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Region)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
// write "StorageClass"
|
||||
err = en.Append(0xac, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.StorageClass)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *TierGCS) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 6
|
||||
// string "Endpoint"
|
||||
o = append(o, 0x86, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
o = msgp.AppendString(o, z.Endpoint)
|
||||
// string "Creds"
|
||||
o = append(o, 0xa5, 0x43, 0x72, 0x65, 0x64, 0x73)
|
||||
o = msgp.AppendString(o, z.Creds)
|
||||
// string "Bucket"
|
||||
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
o = msgp.AppendString(o, z.Bucket)
|
||||
// string "Prefix"
|
||||
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||
o = msgp.AppendString(o, z.Prefix)
|
||||
// string "Region"
|
||||
o = append(o, 0xa6, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e)
|
||||
o = msgp.AppendString(o, z.Region)
|
||||
// string "StorageClass"
|
||||
o = append(o, 0xac, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73)
|
||||
o = msgp.AppendString(o, z.StorageClass)
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *TierGCS) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Endpoint":
|
||||
z.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "Creds":
|
||||
z.Creds, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Creds")
|
||||
return
|
||||
}
|
||||
case "Bucket":
|
||||
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
case "Prefix":
|
||||
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
case "Region":
|
||||
z.Region, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
case "StorageClass":
|
||||
z.StorageClass, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *TierGCS) Msgsize() (s int) {
|
||||
s = 1 + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 6 + msgp.StringPrefixSize + len(z.Creds) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 7 + msgp.StringPrefixSize + len(z.Region) + 13 + msgp.StringPrefixSize + len(z.StorageClass)
|
||||
return
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshalTierGCS(t *testing.T) {
|
||||
v := TierGCS{}
|
||||
bts, err := v.MarshalMsg(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
left, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||
}
|
||||
|
||||
left, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMsgTierGCS(b *testing.B) {
|
||||
v := TierGCS{}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.MarshalMsg(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendMsgTierGCS(b *testing.B) {
|
||||
v := TierGCS{}
|
||||
bts := make([]byte, 0, v.Msgsize())
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalTierGCS(b *testing.B) {
|
||||
v := TierGCS{}
|
||||
bts, _ := v.MarshalMsg(nil)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeTierGCS(t *testing.T) {
|
||||
v := TierGCS{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
|
||||
m := v.Msgsize()
|
||||
if buf.Len() > m {
|
||||
t.Log("WARNING: TestEncodeDecodeTierGCS Msgsize() is inaccurate")
|
||||
}
|
||||
|
||||
vn := TierGCS{}
|
||||
err := msgp.Decode(&buf, &vn)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
msgp.Encode(&buf, &v)
|
||||
err = msgp.NewReader(&buf).Skip()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeTierGCS(b *testing.B) {
|
||||
v := TierGCS{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
en := msgp.NewWriter(msgp.Nowhere)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.EncodeMsg(en)
|
||||
}
|
||||
en.Flush()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeTierGCS(b *testing.B) {
|
||||
v := TierGCS{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||
dc := msgp.NewReader(rd)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := v.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +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 madmin
|
||||
|
||||
//go:generate msgp -file $GOFILE
|
||||
|
||||
// TierS3 represents the remote tier configuration for AWS S3 compatible backend.
|
||||
type TierS3 struct {
|
||||
Endpoint string `json:",omitempty"`
|
||||
AccessKey string `json:",omitempty"`
|
||||
SecretKey string `json:",omitempty"`
|
||||
Bucket string `json:",omitempty"`
|
||||
Prefix string `json:",omitempty"`
|
||||
Region string `json:",omitempty"`
|
||||
StorageClass string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// S3Options supports NewTierS3 to take variadic options
|
||||
type S3Options func(*TierS3) error
|
||||
|
||||
// S3Region helper to supply optional region to NewTierS3
|
||||
func S3Region(region string) func(s3 *TierS3) error {
|
||||
return func(s3 *TierS3) error {
|
||||
s3.Region = region
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// S3Prefix helper to supply optional object prefix to NewTierS3
|
||||
func S3Prefix(prefix string) func(s3 *TierS3) error {
|
||||
return func(s3 *TierS3) error {
|
||||
s3.Prefix = prefix
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// S3Endpoint helper to supply optional endpoint to NewTierS3
|
||||
func S3Endpoint(endpoint string) func(s3 *TierS3) error {
|
||||
return func(s3 *TierS3) error {
|
||||
s3.Endpoint = endpoint
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// S3StorageClass helper to supply optional storage class to NewTierS3
|
||||
func S3StorageClass(storageClass string) func(s3 *TierS3) error {
|
||||
return func(s3 *TierS3) error {
|
||||
s3.StorageClass = storageClass
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewTierS3 returns a TierConfig of S3 type. Returns error if the given
|
||||
// parameters are invalid like name is empty etc.
|
||||
func NewTierS3(name, accessKey, secretKey, bucket string, options ...S3Options) (*TierConfig, error) {
|
||||
if name == "" {
|
||||
return nil, ErrTierNameEmpty
|
||||
}
|
||||
sc := &TierS3{
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
Bucket: bucket,
|
||||
// Defaults
|
||||
Endpoint: "https://s3.amazonaws.com",
|
||||
Region: "",
|
||||
StorageClass: "",
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
err := option(sc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Type: S3,
|
||||
Name: name,
|
||||
S3: sc,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *TierS3) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, err = dc.ReadMapKeyPtr()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Endpoint":
|
||||
z.Endpoint, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "AccessKey":
|
||||
z.AccessKey, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccessKey")
|
||||
return
|
||||
}
|
||||
case "SecretKey":
|
||||
z.SecretKey, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "SecretKey")
|
||||
return
|
||||
}
|
||||
case "Bucket":
|
||||
z.Bucket, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
case "Prefix":
|
||||
z.Prefix, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
case "Region":
|
||||
z.Region, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
case "StorageClass":
|
||||
z.StorageClass, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *TierS3) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 7
|
||||
// write "Endpoint"
|
||||
err = en.Append(0x87, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Endpoint)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
// write "AccessKey"
|
||||
err = en.Append(0xa9, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.AccessKey)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccessKey")
|
||||
return
|
||||
}
|
||||
// write "SecretKey"
|
||||
err = en.Append(0xa9, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.SecretKey)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "SecretKey")
|
||||
return
|
||||
}
|
||||
// write "Bucket"
|
||||
err = en.Append(0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Bucket)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
// write "Prefix"
|
||||
err = en.Append(0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Prefix)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
// write "Region"
|
||||
err = en.Append(0xa6, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Region)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
// write "StorageClass"
|
||||
err = en.Append(0xac, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.StorageClass)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *TierS3) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 7
|
||||
// string "Endpoint"
|
||||
o = append(o, 0x87, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||
o = msgp.AppendString(o, z.Endpoint)
|
||||
// string "AccessKey"
|
||||
o = append(o, 0xa9, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79)
|
||||
o = msgp.AppendString(o, z.AccessKey)
|
||||
// string "SecretKey"
|
||||
o = append(o, 0xa9, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79)
|
||||
o = msgp.AppendString(o, z.SecretKey)
|
||||
// string "Bucket"
|
||||
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
o = msgp.AppendString(o, z.Bucket)
|
||||
// string "Prefix"
|
||||
o = append(o, 0xa6, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78)
|
||||
o = msgp.AppendString(o, z.Prefix)
|
||||
// string "Region"
|
||||
o = append(o, 0xa6, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e)
|
||||
o = msgp.AppendString(o, z.Region)
|
||||
// string "StorageClass"
|
||||
o = append(o, 0xac, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73)
|
||||
o = msgp.AppendString(o, z.StorageClass)
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *TierS3) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "Endpoint":
|
||||
z.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Endpoint")
|
||||
return
|
||||
}
|
||||
case "AccessKey":
|
||||
z.AccessKey, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "AccessKey")
|
||||
return
|
||||
}
|
||||
case "SecretKey":
|
||||
z.SecretKey, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "SecretKey")
|
||||
return
|
||||
}
|
||||
case "Bucket":
|
||||
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Bucket")
|
||||
return
|
||||
}
|
||||
case "Prefix":
|
||||
z.Prefix, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Prefix")
|
||||
return
|
||||
}
|
||||
case "Region":
|
||||
z.Region, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Region")
|
||||
return
|
||||
}
|
||||
case "StorageClass":
|
||||
z.StorageClass, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "StorageClass")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *TierS3) Msgsize() (s int) {
|
||||
s = 1 + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 10 + msgp.StringPrefixSize + len(z.AccessKey) + 10 + msgp.StringPrefixSize + len(z.SecretKey) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 7 + msgp.StringPrefixSize + len(z.Region) + 13 + msgp.StringPrefixSize + len(z.StorageClass)
|
||||
return
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package madmin
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshalTierS3(t *testing.T) {
|
||||
v := TierS3{}
|
||||
bts, err := v.MarshalMsg(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
left, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||
}
|
||||
|
||||
left, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMsgTierS3(b *testing.B) {
|
||||
v := TierS3{}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.MarshalMsg(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendMsgTierS3(b *testing.B) {
|
||||
v := TierS3{}
|
||||
bts := make([]byte, 0, v.Msgsize())
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalTierS3(b *testing.B) {
|
||||
v := TierS3{}
|
||||
bts, _ := v.MarshalMsg(nil)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeTierS3(t *testing.T) {
|
||||
v := TierS3{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
|
||||
m := v.Msgsize()
|
||||
if buf.Len() > m {
|
||||
t.Log("WARNING: TestEncodeDecodeTierS3 Msgsize() is inaccurate")
|
||||
}
|
||||
|
||||
vn := TierS3{}
|
||||
err := msgp.Decode(&buf, &vn)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
msgp.Encode(&buf, &v)
|
||||
err = msgp.NewReader(&buf).Skip()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeTierS3(b *testing.B) {
|
||||
v := TierS3{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
en := msgp.NewWriter(msgp.Nowhere)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.EncodeMsg(en)
|
||||
}
|
||||
en.Flush()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeTierS3(b *testing.B) {
|
||||
v := TierS3{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||
dc := msgp.NewReader(rd)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := v.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// tierAPI is API path prefix for tier related admin APIs
|
||||
const tierAPI = "tier"
|
||||
|
||||
// AddTier adds a new remote tier.
|
||||
func (adm *AdminClient) AddTier(ctx context.Context, cfg *TierConfig) error {
|
||||
data, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encData, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: path.Join(adminAPIPrefix, tierAPI),
|
||||
content: encData,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/tier to add a remote tier
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListTiers returns a list of remote tiers configured.
|
||||
func (adm *AdminClient) ListTiers(ctx context.Context) ([]*TierConfig, error) {
|
||||
reqData := requestData{
|
||||
relPath: path.Join(adminAPIPrefix, tierAPI),
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/tier to list remote tiers configured.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
var tiers []*TierConfig
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return tiers, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &tiers)
|
||||
if err != nil {
|
||||
return tiers, err
|
||||
}
|
||||
|
||||
return tiers, nil
|
||||
}
|
||||
|
||||
// TierCreds is used to pass remote tier credentials in a tier-edit operation.
|
||||
type TierCreds struct {
|
||||
AccessKey string `json:"access,omitempty"`
|
||||
SecretKey string `json:"secret,omitempty"`
|
||||
CredsJSON []byte `json:"creds,omitempty"`
|
||||
}
|
||||
|
||||
// EditTier supports updating credentials for the remote tier identified by tierName.
|
||||
func (adm *AdminClient) EditTier(ctx context.Context, tierName string, creds TierCreds) error {
|
||||
data, err := json.Marshal(creds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var encData []byte
|
||||
encData, err = EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: path.Join(adminAPIPrefix, tierAPI, tierName),
|
||||
content: encData,
|
||||
}
|
||||
|
||||
// Execute POST on /minio/admin/v3/tier/tierName" to edit a tier
|
||||
// configured.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPost, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,188 +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 madmin
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ExampleNewTierS3() {
|
||||
simpleS3SC, err := NewTierS3("simple-s3", "accessKey", "secretKey", "testbucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err, "Failed to create s3 backed tier")
|
||||
}
|
||||
fmt.Println(simpleS3SC)
|
||||
|
||||
fullyCustomS3SC, err := NewTierS3("custom-s3", "accessKey", "secretKey", "testbucket",
|
||||
S3Endpoint("https://s3.amazonaws.com"), S3Prefix("testprefix"), S3Region("us-west-1"), S3StorageClass("S3_IA"))
|
||||
if err != nil {
|
||||
log.Fatalln(err, "Failed to create s3 tier")
|
||||
}
|
||||
fmt.Println(fullyCustomS3SC)
|
||||
}
|
||||
|
||||
func ExampleNewTierAzure() {
|
||||
simpleAzSC, err := NewTierAzure("simple-az", "accessKey", "secretKey", "testbucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err, "Failed to create azure backed tier")
|
||||
}
|
||||
fmt.Println(simpleAzSC)
|
||||
|
||||
fullyCustomAzSC, err := NewTierAzure("custom-az", "accessKey", "secretKey", "testbucket", AzureEndpoint("http://blob.core.windows.net"), AzurePrefix("testprefix"))
|
||||
if err != nil {
|
||||
log.Fatalln(err, "Failed to create azure backed tier")
|
||||
}
|
||||
fmt.Println(fullyCustomAzSC)
|
||||
}
|
||||
|
||||
func ExampleNewTierGCS() {
|
||||
credsJSON := []byte("credentials json content goes here")
|
||||
simpleGCSSC, err := NewTierGCS("simple-gcs", credsJSON, "testbucket")
|
||||
if err != nil {
|
||||
log.Fatalln(err, "Failed to create GCS backed tier")
|
||||
}
|
||||
fmt.Println(simpleGCSSC)
|
||||
|
||||
fullyCustomGCSSC, err := NewTierGCS("custom-gcs", credsJSON, "testbucket", GCSPrefix("testprefix"))
|
||||
if err != nil {
|
||||
log.Fatalln(err, "Failed to create GCS backed tier")
|
||||
}
|
||||
fmt.Println(fullyCustomGCSSC)
|
||||
}
|
||||
|
||||
// TestS3Tier tests S3Options helpers
|
||||
func TestS3Tier(t *testing.T) {
|
||||
scName := "test-s3"
|
||||
endpoint := "https://mys3.com"
|
||||
accessKey, secretKey := "accessKey", "secretKey"
|
||||
bucket, prefix := "testbucket", "testprefix"
|
||||
region := "us-west-1"
|
||||
storageClass := "S3_IA"
|
||||
|
||||
want := &TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Type: S3,
|
||||
Name: scName,
|
||||
S3: &TierS3{
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
Bucket: bucket,
|
||||
|
||||
// custom values
|
||||
Endpoint: endpoint,
|
||||
Prefix: prefix,
|
||||
Region: region,
|
||||
StorageClass: storageClass,
|
||||
},
|
||||
}
|
||||
options := []S3Options{
|
||||
S3Endpoint(endpoint),
|
||||
S3Prefix(prefix),
|
||||
S3Region(region),
|
||||
S3StorageClass(storageClass),
|
||||
}
|
||||
got, err := NewTierS3(scName, accessKey, secretKey, bucket, options...)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a custom s3 tier %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got != want, got = %v want = %v", *got, *want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAzTier tests AzureOptions helpers
|
||||
func TestAzTier(t *testing.T) {
|
||||
scName := "test-az"
|
||||
endpoint := "https://myazure.com"
|
||||
|
||||
accountName, accountKey := "accountName", "accountKey"
|
||||
bucket, prefix := "testbucket", "testprefix"
|
||||
region := "us-east-1"
|
||||
want := &TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Type: Azure,
|
||||
Name: scName,
|
||||
Azure: &TierAzure{
|
||||
AccountName: accountName,
|
||||
AccountKey: accountKey,
|
||||
Bucket: bucket,
|
||||
|
||||
// custom values
|
||||
Endpoint: endpoint,
|
||||
Prefix: prefix,
|
||||
Region: region,
|
||||
},
|
||||
}
|
||||
|
||||
options := []AzureOptions{
|
||||
AzureEndpoint(endpoint),
|
||||
AzurePrefix(prefix),
|
||||
AzureRegion(region),
|
||||
}
|
||||
|
||||
got, err := NewTierAzure(scName, accountName, accountKey, bucket, options...)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a custom azure tier %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got != want, got = %v want = %v", *got, *want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGCSStorageClass tests GCSOptions helpers
|
||||
func TestGCSStorageClass(t *testing.T) {
|
||||
scName := "test-gcs"
|
||||
credsJSON := []byte("test-creds-json")
|
||||
encodedCreds := base64.URLEncoding.EncodeToString(credsJSON)
|
||||
bucket, prefix := "testbucket", "testprefix"
|
||||
region := "us-west-2"
|
||||
|
||||
want := &TierConfig{
|
||||
Version: TierConfigV1,
|
||||
Type: GCS,
|
||||
Name: scName,
|
||||
GCS: &TierGCS{
|
||||
Bucket: bucket,
|
||||
Creds: encodedCreds,
|
||||
|
||||
// custom values
|
||||
Endpoint: "https://storage.googleapis.com/",
|
||||
Prefix: prefix,
|
||||
Region: region,
|
||||
},
|
||||
}
|
||||
options := []GCSOptions{
|
||||
GCSRegion(region),
|
||||
GCSPrefix(prefix),
|
||||
}
|
||||
got, err := NewTierGCS(scName, credsJSON, bucket, options...)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a custom gcs tier %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got != want, got = %v want = %v", *got, *want)
|
||||
}
|
||||
}
|
||||
@@ -1,129 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LockEntry holds information about client requesting the lock,
|
||||
// servers holding the lock, source on the client machine,
|
||||
// ID, type(read or write) and time stamp.
|
||||
type LockEntry struct {
|
||||
Timestamp time.Time `json:"time"` // When the lock was first granted
|
||||
Resource string `json:"resource"` // Resource contains info like bucket+object
|
||||
Type string `json:"type"` // Type indicates if 'Write' or 'Read' lock
|
||||
Source string `json:"source"` // Source at which lock was granted
|
||||
ServerList []string `json:"serverlist"` // List of servers participating in the lock.
|
||||
Owner string `json:"owner"` // Owner UUID indicates server owns the lock.
|
||||
ID string `json:"id"` // UID to uniquely identify request of client.
|
||||
// Represents quorum number of servers required to hold this lock, used to look for stale locks.
|
||||
Quorum int `json:"quorum"`
|
||||
}
|
||||
|
||||
// LockEntries - To sort the locks
|
||||
type LockEntries []LockEntry
|
||||
|
||||
func (l LockEntries) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l LockEntries) Less(i, j int) bool {
|
||||
return l[i].Timestamp.Before(l[j].Timestamp)
|
||||
}
|
||||
|
||||
func (l LockEntries) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// TopLockOpts top lock options
|
||||
type TopLockOpts struct {
|
||||
Count int
|
||||
Stale bool
|
||||
}
|
||||
|
||||
// ForceUnlock force unlocks input paths...
|
||||
func (adm *AdminClient) ForceUnlock(ctx context.Context, paths ...string) error {
|
||||
// Execute POST on /minio/admin/v3/force-unlock
|
||||
queryVals := make(url.Values)
|
||||
queryVals.Set("paths", strings.Join(paths, ","))
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodPost,
|
||||
requestData{
|
||||
relPath: adminAPIPrefix + "/force-unlock",
|
||||
queryValues: queryVals,
|
||||
},
|
||||
)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TopLocksWithOpts - returns the count number of oldest locks currently active on the server.
|
||||
// additionally we can also enable `stale` to get stale locks currently present on server.
|
||||
func (adm *AdminClient) TopLocksWithOpts(ctx context.Context, opts TopLockOpts) (LockEntries, error) {
|
||||
// Execute GET on /minio/admin/v3/top/locks?count=10
|
||||
// to get the 'count' number of oldest locks currently
|
||||
// active on the server.
|
||||
queryVals := make(url.Values)
|
||||
queryVals.Set("count", strconv.Itoa(opts.Count))
|
||||
queryVals.Set("stale", strconv.FormatBool(opts.Stale))
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodGet,
|
||||
requestData{
|
||||
relPath: adminAPIPrefix + "/top/locks",
|
||||
queryValues: queryVals,
|
||||
},
|
||||
)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
response, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return LockEntries{}, err
|
||||
}
|
||||
|
||||
var lockEntries LockEntries
|
||||
err = json.Unmarshal(response, &lockEntries)
|
||||
return lockEntries, err
|
||||
}
|
||||
|
||||
// TopLocks - returns top '10' oldest locks currently active on the server.
|
||||
func (adm *AdminClient) TopLocks(ctx context.Context) (LockEntries, error) {
|
||||
return adm.TopLocksWithOpts(ctx, TopLockOpts{Count: 10})
|
||||
}
|
||||
@@ -1,62 +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 madmin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultTransport - this default transport is similar to
|
||||
// http.DefaultTransport but with additional param DisableCompression
|
||||
// is set to true to avoid decompressing content with 'gzip' encoding.
|
||||
var DefaultTransport = func(secure bool) http.RoundTripper {
|
||||
tr := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 15 * time.Second,
|
||||
FallbackDelay: 100 * time.Millisecond,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 1024,
|
||||
MaxIdleConnsPerHost: 1024,
|
||||
ResponseHeaderTimeout: 60 * time.Second,
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
// Set this value so that the underlying transport round-tripper
|
||||
// doesn't try to auto decode the body of objects with
|
||||
// content-encoding set to `gzip`.
|
||||
//
|
||||
// Refer:
|
||||
// https://golang.org/src/net/http/transport.go?h=roundTrip#L1843
|
||||
DisableCompression: true,
|
||||
}
|
||||
|
||||
if secure {
|
||||
tr.TLSClientConfig = &tls.Config{
|
||||
// Can't use SSLv3 because of POODLE and BEAST
|
||||
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
|
||||
// Can't use TLSv1.1 because of RC4 cipher usage
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
@@ -1,60 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ServerUpdateStatus - contains the response of service update API
|
||||
type ServerUpdateStatus struct {
|
||||
CurrentVersion string `json:"currentVersion"`
|
||||
UpdatedVersion string `json:"updatedVersion"`
|
||||
}
|
||||
|
||||
// ServerUpdate - updates and restarts the MinIO cluster to latest version.
|
||||
// optionally takes an input URL to specify a custom update binary link
|
||||
func (adm *AdminClient) ServerUpdate(ctx context.Context, updateURL string) (us ServerUpdateStatus, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("updateURL", updateURL)
|
||||
|
||||
// Request API to Restart server
|
||||
resp, err := adm.executeMethod(ctx,
|
||||
http.MethodPost, requestData{
|
||||
relPath: adminAPIPrefix + "/update",
|
||||
queryValues: queryValues,
|
||||
},
|
||||
)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return us, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return us, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
if err = json.NewDecoder(resp.Body).Decode(&us); err != nil {
|
||||
return us, err
|
||||
}
|
||||
|
||||
return us, nil
|
||||
}
|
||||
@@ -1,483 +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 madmin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
// AccountAccess contains information about
|
||||
type AccountAccess struct {
|
||||
Read bool `json:"read"`
|
||||
Write bool `json:"write"`
|
||||
}
|
||||
|
||||
// BucketAccessInfo represents bucket usage of a bucket, and its relevant
|
||||
// access type for an account
|
||||
type BucketAccessInfo struct {
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
Created time.Time `json:"created"`
|
||||
Access AccountAccess `json:"access"`
|
||||
}
|
||||
|
||||
// AccountInfo represents the account usage info of an
|
||||
// account across buckets.
|
||||
type AccountInfo struct {
|
||||
AccountName string
|
||||
Policy iampolicy.Policy
|
||||
Buckets []BucketAccessInfo
|
||||
}
|
||||
|
||||
// AccountInfo returns the usage info for the authenticating account.
|
||||
func (adm *AdminClient) AccountInfo(ctx context.Context) (AccountInfo, error) {
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, requestData{relPath: adminAPIPrefix + "/accountinfo"})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return AccountInfo{}, err
|
||||
}
|
||||
|
||||
// Check response http status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return AccountInfo{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
// Unmarshal the server's json response
|
||||
var accountInfo AccountInfo
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return AccountInfo{}, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respBytes, &accountInfo)
|
||||
if err != nil {
|
||||
return AccountInfo{}, err
|
||||
}
|
||||
|
||||
return accountInfo, nil
|
||||
}
|
||||
|
||||
// AccountStatus - account status.
|
||||
type AccountStatus string
|
||||
|
||||
// Account status per user.
|
||||
const (
|
||||
AccountEnabled AccountStatus = "enabled"
|
||||
AccountDisabled AccountStatus = "disabled"
|
||||
)
|
||||
|
||||
// UserInfo carries information about long term users.
|
||||
type UserInfo struct {
|
||||
SecretKey string `json:"secretKey,omitempty"`
|
||||
PolicyName string `json:"policyName,omitempty"`
|
||||
Status AccountStatus `json:"status"`
|
||||
MemberOf []string `json:"memberOf,omitempty"`
|
||||
}
|
||||
|
||||
// RemoveUser - remove a user.
|
||||
func (adm *AdminClient) RemoveUser(ctx context.Context, accessKey string) error {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("accessKey", accessKey)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/remove-user",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute DELETE on /minio/admin/v3/remove-user to remove a user.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUsers - list all users.
|
||||
func (adm *AdminClient) ListUsers(ctx context.Context) (map[string]UserInfo, error) {
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/list-users",
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/list-users
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
data, err := DecryptData(adm.getSecretKey(), resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var users = make(map[string]UserInfo)
|
||||
if err = json.Unmarshal(data, &users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetUserInfo - get info on a user
|
||||
func (adm *AdminClient) GetUserInfo(ctx context.Context, name string) (u UserInfo, err error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("accessKey", name)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/user-info",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/user-info
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return u, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(b, &u); err != nil {
|
||||
return u, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// SetUser - sets a user info.
|
||||
func (adm *AdminClient) SetUser(ctx context.Context, accessKey, secretKey string, status AccountStatus) error {
|
||||
|
||||
if !auth.IsAccessKeyValid(accessKey) {
|
||||
return auth.ErrInvalidAccessKeyLength
|
||||
}
|
||||
|
||||
if !auth.IsSecretKeyValid(secretKey) {
|
||||
return auth.ErrInvalidSecretKeyLength
|
||||
}
|
||||
|
||||
data, err := json.Marshal(UserInfo{
|
||||
SecretKey: secretKey,
|
||||
Status: status,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
econfigBytes, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("accessKey", accessKey)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/add-user",
|
||||
queryValues: queryValues,
|
||||
content: econfigBytes,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/add-user to set a user.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddUser - adds a user.
|
||||
func (adm *AdminClient) AddUser(ctx context.Context, accessKey, secretKey string) error {
|
||||
return adm.SetUser(ctx, accessKey, secretKey, AccountEnabled)
|
||||
}
|
||||
|
||||
// SetUserStatus - adds a status for a user.
|
||||
func (adm *AdminClient) SetUserStatus(ctx context.Context, accessKey string, status AccountStatus) error {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("accessKey", accessKey)
|
||||
queryValues.Set("status", string(status))
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/set-user-status",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/set-user-status to set status.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddServiceAccountReq is the request options of the add service account admin call
|
||||
type AddServiceAccountReq struct {
|
||||
Policy *iampolicy.Policy `json:"policy,omitempty"`
|
||||
TargetUser string `json:"targetUser,omitempty"`
|
||||
AccessKey string `json:"accessKey,omitempty"`
|
||||
SecretKey string `json:"secretKey,omitempty"`
|
||||
}
|
||||
|
||||
// AddServiceAccountResp is the response body of the add service account admin call
|
||||
type AddServiceAccountResp struct {
|
||||
Credentials auth.Credentials `json:"credentials"`
|
||||
}
|
||||
|
||||
// AddServiceAccount - creates a new service account belonging to the user sending
|
||||
// the request while restricting the service account permission by the given policy document.
|
||||
func (adm *AdminClient) AddServiceAccount(ctx context.Context, opts AddServiceAccountReq) (auth.Credentials, error) {
|
||||
if opts.Policy != nil {
|
||||
if err := opts.Policy.Validate(); err != nil {
|
||||
return auth.Credentials{}, err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(opts)
|
||||
if err != nil {
|
||||
return auth.Credentials{}, err
|
||||
}
|
||||
|
||||
econfigBytes, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return auth.Credentials{}, err
|
||||
}
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/add-service-account",
|
||||
content: econfigBytes,
|
||||
}
|
||||
|
||||
// Execute PUT on /minio/admin/v3/add-service-account to set a user.
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPut, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return auth.Credentials{}, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return auth.Credentials{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
data, err = DecryptData(adm.getSecretKey(), resp.Body)
|
||||
if err != nil {
|
||||
return auth.Credentials{}, err
|
||||
}
|
||||
|
||||
var serviceAccountResp AddServiceAccountResp
|
||||
if err = json.Unmarshal(data, &serviceAccountResp); err != nil {
|
||||
return auth.Credentials{}, err
|
||||
}
|
||||
return serviceAccountResp.Credentials, nil
|
||||
}
|
||||
|
||||
// UpdateServiceAccountReq is the request options of the edit service account admin call
|
||||
type UpdateServiceAccountReq struct {
|
||||
NewPolicy *iampolicy.Policy `json:"newPolicy,omitempty"`
|
||||
NewSecretKey string `json:"newSecretKey,omitempty"`
|
||||
NewStatus string `json:"newStatus,omityempty"`
|
||||
}
|
||||
|
||||
// UpdateServiceAccount - edit an existing service account
|
||||
func (adm *AdminClient) UpdateServiceAccount(ctx context.Context, accessKey string, opts UpdateServiceAccountReq) error {
|
||||
if opts.NewPolicy != nil {
|
||||
if err := opts.NewPolicy.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
econfigBytes, err := EncryptData(adm.getSecretKey(), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("accessKey", accessKey)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/update-service-account",
|
||||
content: econfigBytes,
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute POST on /minio/admin/v3/update-service-account to edit a service account
|
||||
resp, err := adm.executeMethod(ctx, http.MethodPost, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListServiceAccountsResp is the response body of the list service accounts call
|
||||
type ListServiceAccountsResp struct {
|
||||
Accounts []string `json:"accounts"`
|
||||
}
|
||||
|
||||
// ListServiceAccounts - list service accounts belonging to the specified user
|
||||
func (adm *AdminClient) ListServiceAccounts(ctx context.Context, user string) (ListServiceAccountsResp, error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("user", user)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/list-service-accounts",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/list-service-accounts
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return ListServiceAccountsResp{}, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ListServiceAccountsResp{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
data, err := DecryptData(adm.getSecretKey(), resp.Body)
|
||||
if err != nil {
|
||||
return ListServiceAccountsResp{}, err
|
||||
}
|
||||
|
||||
var listResp ListServiceAccountsResp
|
||||
if err = json.Unmarshal(data, &listResp); err != nil {
|
||||
return ListServiceAccountsResp{}, err
|
||||
}
|
||||
return listResp, nil
|
||||
}
|
||||
|
||||
// InfoServiceAccountResp is the response body of the info service account call
|
||||
type InfoServiceAccountResp struct {
|
||||
ParentUser string `json:"parentUser"`
|
||||
AccountStatus string `json:"accountStatus"`
|
||||
ImpliedPolicy bool `json:"impliedPolicy"`
|
||||
Policy string `json:"policy"`
|
||||
}
|
||||
|
||||
// InfoServiceAccount - returns the info of service account belonging to the specified user
|
||||
func (adm *AdminClient) InfoServiceAccount(ctx context.Context, accessKey string) (InfoServiceAccountResp, error) {
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("accessKey", accessKey)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/info-service-account",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute GET on /minio/admin/v3/info-service-account
|
||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return InfoServiceAccountResp{}, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return InfoServiceAccountResp{}, httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
data, err := DecryptData(adm.getSecretKey(), resp.Body)
|
||||
if err != nil {
|
||||
return InfoServiceAccountResp{}, err
|
||||
}
|
||||
|
||||
var infoResp InfoServiceAccountResp
|
||||
if err = json.Unmarshal(data, &infoResp); err != nil {
|
||||
return InfoServiceAccountResp{}, err
|
||||
}
|
||||
return infoResp, nil
|
||||
}
|
||||
|
||||
// DeleteServiceAccount - delete a specified service account. The server will reject
|
||||
// the request if the service account does not belong to the user initiating the request
|
||||
func (adm *AdminClient) DeleteServiceAccount(ctx context.Context, serviceAccount string) error {
|
||||
if !auth.IsAccessKeyValid(serviceAccount) {
|
||||
return auth.ErrInvalidAccessKeyLength
|
||||
}
|
||||
|
||||
queryValues := url.Values{}
|
||||
queryValues.Set("accessKey", serviceAccount)
|
||||
|
||||
reqData := requestData{
|
||||
relPath: adminAPIPrefix + "/delete-service-account",
|
||||
queryValues: queryValues,
|
||||
}
|
||||
|
||||
// Execute DELETE on /minio/admin/v3/delete-service-account
|
||||
resp, err := adm.executeMethod(ctx, http.MethodDelete, reqData)
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return httpRespToErrorResponse(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,125 +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 madmin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
)
|
||||
|
||||
// AdminAPIVersion - admin api version used in the request.
|
||||
const (
|
||||
AdminAPIVersion = "v3"
|
||||
AdminAPIVersionV2 = "v2"
|
||||
adminAPIPrefix = "/" + AdminAPIVersion
|
||||
)
|
||||
|
||||
// jsonDecoder decode json to go type.
|
||||
func jsonDecoder(body io.Reader, v interface{}) error {
|
||||
d := json.NewDecoder(body)
|
||||
return d.Decode(v)
|
||||
}
|
||||
|
||||
// getEndpointURL - construct a new endpoint.
|
||||
func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
|
||||
if strings.Contains(endpoint, ":") {
|
||||
host, _, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
|
||||
msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
|
||||
return nil, ErrInvalidArgument(msg)
|
||||
}
|
||||
} else {
|
||||
if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
|
||||
msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
|
||||
return nil, ErrInvalidArgument(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// If secure is false, use 'http' scheme.
|
||||
scheme := "https"
|
||||
if !secure {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
// Strip the obvious :443 and :80 from the endpoint
|
||||
// to avoid the signature mismatch error.
|
||||
if secure && strings.HasSuffix(endpoint, ":443") {
|
||||
endpoint = strings.TrimSuffix(endpoint, ":443")
|
||||
}
|
||||
if !secure && strings.HasSuffix(endpoint, ":80") {
|
||||
endpoint = strings.TrimSuffix(endpoint, ":80")
|
||||
}
|
||||
|
||||
// Construct a secured endpoint URL.
|
||||
endpointURLStr := scheme + "://" + endpoint
|
||||
endpointURL, err := url.Parse(endpointURLStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate incoming endpoint URL.
|
||||
if err := isValidEndpointURL(endpointURL.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return endpointURL, nil
|
||||
}
|
||||
|
||||
// Verify if input endpoint URL is valid.
|
||||
func isValidEndpointURL(endpointURL string) error {
|
||||
if endpointURL == "" {
|
||||
return ErrInvalidArgument("Endpoint url cannot be empty.")
|
||||
}
|
||||
url, err := url.Parse(endpointURL)
|
||||
if err != nil {
|
||||
return ErrInvalidArgument("Endpoint url cannot be parsed.")
|
||||
}
|
||||
if url.Path != "/" && url.Path != "" {
|
||||
return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeResponse close non nil response with any response Body.
|
||||
// convenient wrapper to drain any remaining data on response body.
|
||||
//
|
||||
// Subsequently this allows golang http RoundTripper
|
||||
// to re-use the same connection for future requests.
|
||||
func closeResponse(resp *http.Response) {
|
||||
// Callers should close resp.Body when done reading from it.
|
||||
// If resp.Body is not closed, the Client's underlying RoundTripper
|
||||
// (typically Transport) may not be able to re-use a persistent TCP
|
||||
// connection to the server for a subsequent "keep-alive" request.
|
||||
if resp != nil && resp.Body != nil {
|
||||
// Drain any remaining Body and then close the connection.
|
||||
// Without this closing connection would disallow re-using
|
||||
// the same connection for future uses.
|
||||
// - http://stackoverflow.com/a/17961593/4465767
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
+16
-35
@@ -18,31 +18,12 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"github.com/minio/madmin-go"
|
||||
"github.com/montanaflynn/stats"
|
||||
)
|
||||
|
||||
// Latency holds latency information for read/write operations to the drive
|
||||
type Latency struct {
|
||||
Avg float64 `json:"avg_secs,omitempty"`
|
||||
Percentile50 float64 `json:"percentile50_secs,omitempty"`
|
||||
Percentile90 float64 `json:"percentile90_secs,omitempty"`
|
||||
Percentile99 float64 `json:"percentile99_secs,omitempty"`
|
||||
Min float64 `json:"min_secs,omitempty"`
|
||||
Max float64 `json:"max_secs,omitempty"`
|
||||
}
|
||||
|
||||
// Throughput holds throughput information for read/write operations to the drive
|
||||
type Throughput struct {
|
||||
Avg float64 `json:"avg_bytes_per_sec,omitempty"`
|
||||
Percentile50 float64 `json:"percentile50_bytes_per_sec,omitempty"`
|
||||
Percentile90 float64 `json:"percentile90_bytes_per_sec,omitempty"`
|
||||
Percentile99 float64 `json:"percentile99_bytes_per_sec,omitempty"`
|
||||
Min float64 `json:"min_bytes_per_sec,omitempty"`
|
||||
Max float64 `json:"max_bytes_per_sec,omitempty"`
|
||||
}
|
||||
|
||||
// ComputePerfStats takes arrays of Latency & Throughput to compute Statistics
|
||||
func ComputePerfStats(latencies, throughputs []float64) (Latency, Throughput, error) {
|
||||
func ComputePerfStats(latencies, throughputs []float64) (madmin.NetLatency, madmin.NetThroughput, error) {
|
||||
var avgLatency float64
|
||||
var percentile50Latency float64
|
||||
var percentile90Latency float64
|
||||
@@ -59,24 +40,24 @@ func ComputePerfStats(latencies, throughputs []float64) (Latency, Throughput, er
|
||||
var err error
|
||||
|
||||
if avgLatency, err = stats.Mean(latencies); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if percentile50Latency, err = stats.Percentile(latencies, 50); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if percentile90Latency, err = stats.Percentile(latencies, 90); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if percentile99Latency, err = stats.Percentile(latencies, 99); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if maxLatency, err = stats.Max(latencies); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if minLatency, err = stats.Min(latencies); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
l := Latency{
|
||||
l := madmin.NetLatency{
|
||||
Avg: avgLatency,
|
||||
Percentile50: percentile50Latency,
|
||||
Percentile90: percentile90Latency,
|
||||
@@ -86,24 +67,24 @@ func ComputePerfStats(latencies, throughputs []float64) (Latency, Throughput, er
|
||||
}
|
||||
|
||||
if avgThroughput, err = stats.Mean(throughputs); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if percentile50Throughput, err = stats.Percentile(throughputs, 50); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if percentile90Throughput, err = stats.Percentile(throughputs, 90); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if percentile99Throughput, err = stats.Percentile(throughputs, 99); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if maxThroughput, err = stats.Max(throughputs); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
if minThroughput, err = stats.Min(throughputs); err != nil {
|
||||
return Latency{}, Throughput{}, err
|
||||
return madmin.NetLatency{}, madmin.NetThroughput{}, err
|
||||
}
|
||||
t := Throughput{
|
||||
t := madmin.NetThroughput{
|
||||
Avg: avgThroughput,
|
||||
Percentile50: percentile50Throughput,
|
||||
Percentile90: percentile90Throughput,
|
||||
|
||||
+9
-8
@@ -31,13 +31,14 @@ import (
|
||||
"github.com/dswarbrick/smart/ioctl"
|
||||
"github.com/dswarbrick/smart/scsi"
|
||||
"github.com/dswarbrick/smart/utils"
|
||||
"github.com/minio/madmin-go"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// GetInfo - gets info about device
|
||||
func GetInfo(device string) (Info, error) {
|
||||
info := Info{
|
||||
func GetInfo(device string) (madmin.SmartInfo, error) {
|
||||
info := madmin.SmartInfo{
|
||||
Device: device,
|
||||
}
|
||||
|
||||
@@ -89,9 +90,9 @@ func GetInfo(device string) (Info, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func getNvmeInfo(d *NVMeDevice) (*NvmeInfo, error) {
|
||||
func getNvmeInfo(d *NVMeDevice) (*madmin.SmartNvmeInfo, error) {
|
||||
buf := make([]byte, 4096)
|
||||
nvmeInfo := &NvmeInfo{}
|
||||
nvmeInfo := &madmin.SmartNvmeInfo{}
|
||||
|
||||
cmd := nvmePassthruCommand{
|
||||
opcode: NvmeAdminIdentify,
|
||||
@@ -145,10 +146,10 @@ func getNvmeInfo(d *NVMeDevice) (*NvmeInfo, error) {
|
||||
return nvmeInfo, nil
|
||||
}
|
||||
|
||||
func getScsiInfo(d *scsi.SCSIDevice) (*ScsiInfo, error) {
|
||||
return &ScsiInfo{}, nil
|
||||
func getScsiInfo(d *scsi.SCSIDevice) (*madmin.SmartScsiInfo, error) {
|
||||
return &madmin.SmartScsiInfo{}, nil
|
||||
}
|
||||
|
||||
func getAtaInfo(d *scsi.SATDevice) (*AtaInfo, error) {
|
||||
return &AtaInfo{}, nil
|
||||
func getAtaInfo(d *scsi.SATDevice) (*madmin.SmartAtaInfo, error) {
|
||||
return &madmin.SmartAtaInfo{}, nil
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
package smart
|
||||
|
||||
import "math/big"
|
||||
|
||||
// Defined in <linux/nvme_ioctl.h>
|
||||
//nolint:structcheck,deadcode
|
||||
type nvmePassthruCommand struct {
|
||||
@@ -182,62 +180,3 @@ type NVMeDevice struct {
|
||||
Name string
|
||||
fd int
|
||||
}
|
||||
|
||||
// Info contains S.M.A.R.T data about the drive
|
||||
type Info struct {
|
||||
Device string `json:"device"`
|
||||
|
||||
Scsi *ScsiInfo `json:"scsi,omitempty"`
|
||||
Nvme *NvmeInfo `json:"nvme,omitempty"`
|
||||
Ata *AtaInfo `json:"ata,omitempty"`
|
||||
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// AtaInfo contains ATA drive info
|
||||
type AtaInfo struct {
|
||||
LUWWNDeviceID string `json:"scsiLuWWNDeviceID,omitempty"`
|
||||
SerialNum string `json:"serialNum,omitempty"`
|
||||
ModelNum string `json:"modelNum,omitempty"`
|
||||
FirmwareRevision string `json:"firmwareRevision,omitempty"`
|
||||
RotationRate string `json:"RotationRate,omitempty"`
|
||||
ATAMajorVersion string `json:"MajorVersion,omitempty"`
|
||||
ATAMinorVersion string `json:"MinorVersion,omitempty"`
|
||||
SmartSupportAvailable bool `json:"smartSupportAvailable,omitempty"`
|
||||
SmartSupportEnabled bool `json:"smartSupportEnabled,omitempty"`
|
||||
ErrorLog string `json:"smartErrorLog,omitempty"`
|
||||
Transport string `json:"transport,omitempty"`
|
||||
}
|
||||
|
||||
// ScsiInfo contains SCSI drive Info
|
||||
type ScsiInfo struct {
|
||||
CapacityBytes int64 `json:"scsiCapacityBytes,omitempty"`
|
||||
ModeSenseBuf string `json:"scsiModeSenseBuf,omitempty"`
|
||||
RespLen int64 `json:"scsirespLen,omitempty"`
|
||||
BdLen int64 `json:"scsiBdLen,omitempty"`
|
||||
Offset int64 `json:"scsiOffset,omitempty"`
|
||||
RPM int64 `json:"sciRpm,omitempty"`
|
||||
}
|
||||
|
||||
// NvmeInfo contains NVMe drive info
|
||||
type NvmeInfo struct {
|
||||
SerialNum string `json:"serialNum,omitempty"`
|
||||
VendorID string `json:"vendorId,omitempty"`
|
||||
FirmwareVersion string `json:"firmwareVersion,omitempty"`
|
||||
ModelNum string `json:"modelNum,omitempty"`
|
||||
SpareAvailable string `json:"spareAvailable,omitempty"`
|
||||
SpareThreshold string `json:"spareThreshold,omitempty"`
|
||||
Temperature string `json:"temperature,omitempty"`
|
||||
CriticalWarning string `json:"criticalWarning,omitempty"`
|
||||
|
||||
MaxDataTransferPages int `json:"maxDataTransferPages,omitempty"`
|
||||
ControllerBusyTime *big.Int `json:"controllerBusyTime,omitempty"`
|
||||
PowerOnHours *big.Int `json:"powerOnHours,omitempty"`
|
||||
PowerCycles *big.Int `json:"powerCycles,omitempty"`
|
||||
UnsafeShutdowns *big.Int `json:"unsafeShutdowns,omitempty"`
|
||||
MediaAndDataIntegrityErrors *big.Int `json:"mediaAndDataIntgerityErrors,omitempty"`
|
||||
DataUnitsReadBytes *big.Int `json:"dataUnitsReadBytes,omitempty"`
|
||||
DataUnitsWrittenBytes *big.Int `json:"dataUnitsWrittenBytes,omitempty"`
|
||||
HostReadCommands *big.Int `json:"hostReadCommands,omitempty"`
|
||||
HostWriteCommands *big.Int `json:"hostWriteCommands,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,92 +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 trace
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Type indicates the type of the tracing Info
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// HTTP tracing (MinIO S3 & Internode)
|
||||
HTTP Type = iota
|
||||
// OS tracing (Golang os package calls)
|
||||
OS
|
||||
// Storage tracing (MinIO Storage Layer)
|
||||
Storage
|
||||
)
|
||||
|
||||
// Info - represents a trace record, additionally
|
||||
// also reports errors if any while listening on trace.
|
||||
type Info struct {
|
||||
TraceType Type `json:"type"`
|
||||
|
||||
NodeName string `json:"nodename"`
|
||||
FuncName string `json:"funcname"`
|
||||
Time time.Time `json:"time"`
|
||||
|
||||
ReqInfo RequestInfo `json:"request"`
|
||||
RespInfo ResponseInfo `json:"response"`
|
||||
CallStats CallStats `json:"stats"`
|
||||
|
||||
StorageStats StorageStats `json:"storageStats"`
|
||||
OSStats OSStats `json:"osStats"`
|
||||
}
|
||||
|
||||
// StorageStats statistics on MinIO Storage layer calls
|
||||
type StorageStats struct {
|
||||
Path string `json:"path"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
}
|
||||
|
||||
// OSStats statistics on operating system specific calls.
|
||||
type OSStats struct {
|
||||
Path string `json:"path"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
}
|
||||
|
||||
// CallStats records request stats
|
||||
type CallStats struct {
|
||||
InputBytes int `json:"inputbytes"`
|
||||
OutputBytes int `json:"outputbytes"`
|
||||
Latency time.Duration `json:"latency"`
|
||||
TimeToFirstByte time.Duration `json:"timetofirstbyte"`
|
||||
}
|
||||
|
||||
// RequestInfo represents trace of http request
|
||||
type RequestInfo struct {
|
||||
Time time.Time `json:"time"`
|
||||
Proto string `json:"proto"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path,omitempty"`
|
||||
RawQuery string `json:"rawquery,omitempty"`
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
Body []byte `json:"body,omitempty"`
|
||||
Client string `json:"client"`
|
||||
}
|
||||
|
||||
// ResponseInfo represents trace of http request
|
||||
type ResponseInfo struct {
|
||||
Time time.Time `json:"time"`
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
Body []byte `json:"body,omitempty"`
|
||||
StatusCode int `json:"statuscode,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user