forked from TrueCloudLab/frostfs-s3-gw
[#405] English Check
Signed-off-by: Elizaveta Chichindaeva <elizaveta@nspcc.ru>
This commit is contained in:
parent
a0a04a73bd
commit
bf38007692
42 changed files with 205 additions and 205 deletions
|
@ -252,7 +252,7 @@ func hmacSHA256(key []byte, data []byte) []byte {
|
|||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// MultipartFormValue get value by key from multipart form.
|
||||
// MultipartFormValue gets value by key from multipart form.
|
||||
func MultipartFormValue(r *http.Request, key string) string {
|
||||
if r.MultipartForm == nil {
|
||||
return ""
|
||||
|
|
8
api/cache/accessbox.go
vendored
8
api/cache/accessbox.go
vendored
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
// AccessBoxCache stores access box by its address.
|
||||
// AccessBoxCache stores an access box by its address.
|
||||
AccessBoxCache struct {
|
||||
cache gcache.Cache
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ type (
|
|||
const (
|
||||
// DefaultAccessBoxCacheSize is a default maximum number of entries in cache.
|
||||
DefaultAccessBoxCacheSize = 100
|
||||
// DefaultAccessBoxCacheLifetime is a default lifetime of entries in cache.
|
||||
// DefaultAccessBoxCacheLifetime is a default lifetime of entries in cache.
|
||||
DefaultAccessBoxCacheLifetime = 10 * time.Minute
|
||||
)
|
||||
|
||||
// DefaultAccessBoxConfig return new default cache expiration values.
|
||||
// DefaultAccessBoxConfig returns new default cache expiration values.
|
||||
func DefaultAccessBoxConfig() *Config {
|
||||
return &Config{Size: DefaultAccessBoxCacheSize, Lifetime: DefaultAccessBoxCacheLifetime}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func NewAccessBoxCache(config *Config) *AccessBoxCache {
|
|||
return &AccessBoxCache{cache: gc}
|
||||
}
|
||||
|
||||
// Get returns cached object.
|
||||
// Get returns a cached object.
|
||||
func (o *AccessBoxCache) Get(address *address.Address) *accessbox.Box {
|
||||
entry, err := o.cache.Get(address.String())
|
||||
if err != nil {
|
||||
|
|
8
api/cache/buckets.go
vendored
8
api/cache/buckets.go
vendored
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/nspcc-dev/neofs-s3-gw/api/data"
|
||||
)
|
||||
|
||||
// BucketCache contains cache with objects and lifetime of cache entries.
|
||||
// BucketCache contains cache with objects and the lifetime of cache entries.
|
||||
type BucketCache struct {
|
||||
cache gcache.Cache
|
||||
}
|
||||
|
@ -15,11 +15,11 @@ type BucketCache struct {
|
|||
const (
|
||||
// DefaultBucketCacheSize is a default maximum number of entries in cache.
|
||||
DefaultBucketCacheSize = 1e3
|
||||
// DefaultBucketCacheLifetime is a default lifetime of entries in cache.
|
||||
// DefaultBucketCacheLifetime is a default lifetime of entries in cache.
|
||||
DefaultBucketCacheLifetime = time.Minute
|
||||
)
|
||||
|
||||
// DefaultBucketConfig return new default cache expiration values.
|
||||
// DefaultBucketConfig returns new default cache expiration values.
|
||||
func DefaultBucketConfig() *Config {
|
||||
return &Config{Size: DefaultBucketCacheSize, Lifetime: DefaultBucketCacheLifetime}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ func NewBucketCache(config *Config) *BucketCache {
|
|||
return &BucketCache{cache: gc}
|
||||
}
|
||||
|
||||
// Get returns cached object.
|
||||
// Get returns a cached object.
|
||||
func (o *BucketCache) Get(key string) *data.BucketInfo {
|
||||
entry, err := o.cache.Get(key)
|
||||
if err != nil {
|
||||
|
|
10
api/cache/names.go
vendored
10
api/cache/names.go
vendored
|
@ -7,8 +7,8 @@ import (
|
|||
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||
)
|
||||
|
||||
// ObjectsNameCache provides for lru cache for objects.
|
||||
// This cache contains mapping nice name to object addresses.
|
||||
// ObjectsNameCache provides lru cache for objects.
|
||||
// This cache contains mapping nice names to object addresses.
|
||||
// Key is bucketName+objectName.
|
||||
type ObjectsNameCache struct {
|
||||
cache gcache.Cache
|
||||
|
@ -17,11 +17,11 @@ type ObjectsNameCache struct {
|
|||
const (
|
||||
// DefaultObjectsNameCacheSize is a default maximum number of entries in cache.
|
||||
DefaultObjectsNameCacheSize = 1e4
|
||||
// DefaultObjectsNameCacheLifetime is a default lifetime of entries in cache.
|
||||
// DefaultObjectsNameCacheLifetime is a default lifetime of entries in cache.
|
||||
DefaultObjectsNameCacheLifetime = time.Minute
|
||||
)
|
||||
|
||||
// DefaultObjectsNameConfig return new default cache expiration values.
|
||||
// DefaultObjectsNameConfig returns new default cache expiration values.
|
||||
func DefaultObjectsNameConfig() *Config {
|
||||
return &Config{Size: DefaultObjectsNameCacheSize, Lifetime: DefaultObjectsNameCacheLifetime}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func NewObjectsNameCache(config *Config) *ObjectsNameCache {
|
|||
return &ObjectsNameCache{cache: gc}
|
||||
}
|
||||
|
||||
// Get returns cached object.
|
||||
// Get returns a cached object.
|
||||
func (o *ObjectsNameCache) Get(key string) *address.Address {
|
||||
entry, err := o.cache.Get(key)
|
||||
if err != nil {
|
||||
|
|
6
api/cache/objects.go
vendored
6
api/cache/objects.go
vendored
|
@ -16,11 +16,11 @@ type ObjectsCache struct {
|
|||
const (
|
||||
// DefaultObjectsCacheLifetime is a default lifetime of entries in objects' cache.
|
||||
DefaultObjectsCacheLifetime = time.Minute * 5
|
||||
// DefaultObjectsCacheSize is a default maximum number of entries in objects' in cache.
|
||||
// DefaultObjectsCacheSize is a default maximum number of entries in objects' cache.
|
||||
DefaultObjectsCacheSize = 1e6
|
||||
)
|
||||
|
||||
// DefaultObjectsConfig return new default cache expiration values.
|
||||
// DefaultObjectsConfig returns new default cache expiration values.
|
||||
func DefaultObjectsConfig() *Config {
|
||||
return &Config{Size: DefaultObjectsCacheSize, Lifetime: DefaultObjectsCacheLifetime}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ func New(config *Config) *ObjectsCache {
|
|||
return &ObjectsCache{cache: gc}
|
||||
}
|
||||
|
||||
// Get returns cached object.
|
||||
// Get returns a cached object.
|
||||
func (o *ObjectsCache) Get(address *address.Address) *object.Object {
|
||||
entry, err := o.cache.Get(address.String())
|
||||
if err != nil {
|
||||
|
|
16
api/cache/objectslist.go
vendored
16
api/cache/objectslist.go
vendored
|
@ -11,16 +11,16 @@ import (
|
|||
)
|
||||
|
||||
/*
|
||||
This is an implementation of a cache which keeps unsorted lists of objects' IDs (all versions)
|
||||
This is an implementation of cache which keeps unsorted lists of objects' IDs (all versions)
|
||||
for a specified bucket and a prefix.
|
||||
|
||||
The cache contains gcache whose entries have a key: ObjectsListKey struct and a value: list of ids.
|
||||
After putting a record it lives for a while (default value is 60 seconds).
|
||||
After putting a record, it lives for a while (default value is 60 seconds).
|
||||
|
||||
When we receive a request from the user we try to find the suitable and non-expired cache entry, go through the list
|
||||
When we receive a request from a user, we try to find the suitable and non-expired cache entry, go through the list
|
||||
and get ObjectInfos from common object cache or with a request to NeoFS.
|
||||
|
||||
When we put an object into a container we invalidate entries with prefixes that are prefixes of the object's name.
|
||||
When we put an object into a container, we invalidate entries with prefixes that are prefixes of the object's name.
|
||||
*/
|
||||
|
||||
type (
|
||||
|
@ -43,18 +43,18 @@ const (
|
|||
DefaultObjectsListCacheSize = 1e5
|
||||
)
|
||||
|
||||
// DefaultObjectsListConfig return new default cache expiration values.
|
||||
// DefaultObjectsListConfig returns new default cache expiration values.
|
||||
func DefaultObjectsListConfig() *Config {
|
||||
return &Config{Size: DefaultObjectsListCacheSize, Lifetime: DefaultObjectsListCacheLifetime}
|
||||
}
|
||||
|
||||
// NewObjectsListCache is a constructor which creates an object of ListObjectsCache with given lifetime of entries.
|
||||
// NewObjectsListCache is a constructor which creates an object of ListObjectsCache with the given lifetime of entries.
|
||||
func NewObjectsListCache(config *Config) *ObjectsListCache {
|
||||
gc := gcache.New(config.Size).LRU().Expiration(config.Lifetime).Build()
|
||||
return &ObjectsListCache{cache: gc}
|
||||
}
|
||||
|
||||
// Get return list of ObjectInfo.
|
||||
// Get returns a list of ObjectInfo.
|
||||
func (l *ObjectsListCache) Get(key ObjectsListKey) []oid.ID {
|
||||
entry, err := l.cache.Get(key)
|
||||
if err != nil {
|
||||
|
@ -93,7 +93,7 @@ func (l *ObjectsListCache) CleanCacheEntriesContainingObject(objectName string,
|
|||
}
|
||||
}
|
||||
|
||||
// CreateObjectsListCacheKey returns ObjectsListKey with given CID and prefix.
|
||||
// CreateObjectsListCacheKey returns ObjectsListKey with the given CID and prefix.
|
||||
func CreateObjectsListCacheKey(cid *cid.ID, prefix string) ObjectsListKey {
|
||||
p := ObjectsListKey{
|
||||
cid: cid.String(),
|
||||
|
|
6
api/cache/system.go
vendored
6
api/cache/system.go
vendored
|
@ -17,11 +17,11 @@ type SystemCache struct {
|
|||
const (
|
||||
// DefaultSystemCacheSize is a default maximum number of entries in cache.
|
||||
DefaultSystemCacheSize = 1e4
|
||||
// DefaultSystemCacheLifetime is a default lifetime of entries in cache.
|
||||
// DefaultSystemCacheLifetime is a default lifetime of entries in cache.
|
||||
DefaultSystemCacheLifetime = 5 * time.Minute
|
||||
)
|
||||
|
||||
// DefaultSystemConfig return new default cache expiration values.
|
||||
// DefaultSystemConfig returns new default cache expiration values.
|
||||
func DefaultSystemConfig() *Config {
|
||||
return &Config{Size: DefaultSystemCacheSize, Lifetime: DefaultSystemCacheLifetime}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func NewSystemCache(config *Config) *SystemCache {
|
|||
return &SystemCache{cache: gc}
|
||||
}
|
||||
|
||||
// GetObject returns cached object.
|
||||
// GetObject returns a cached object.
|
||||
func (o *SystemCache) GetObject(key string) *data.ObjectInfo {
|
||||
entry, err := o.cache.Get(key)
|
||||
if err != nil {
|
||||
|
|
|
@ -68,10 +68,10 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// SettingsObjectName is system name for bucket settings file.
|
||||
// SettingsObjectName is a system name for a bucket settings file.
|
||||
func (b *BucketInfo) SettingsObjectName() string { return bktSettingsObject }
|
||||
|
||||
// CORSObjectName returns system name for bucket CORS configuration file.
|
||||
// CORSObjectName returns a system name for a bucket CORS configuration file.
|
||||
func (b *BucketInfo) CORSObjectName() string { return bktCORSConfigurationObject }
|
||||
|
||||
func (b *BucketInfo) NotificationConfigurationObjectName() string {
|
||||
|
@ -82,7 +82,7 @@ func (b *BucketInfo) NotificationConfigurationObjectName() string {
|
|||
func (o *ObjectInfo) Version() string { return o.ID.String() }
|
||||
|
||||
// NullableVersion returns object version from ObjectInfo.
|
||||
// Return "null" if "S3-Versions-unversioned" header present.
|
||||
// Return "null" if "S3-Versions-unversioned" header is present.
|
||||
func (o *ObjectInfo) NullableVersion() string {
|
||||
if _, ok := o.Headers["S3-Versions-unversioned"]; ok {
|
||||
return "null"
|
||||
|
@ -102,11 +102,11 @@ func (o *ObjectInfo) Address() *address.Address {
|
|||
return addr
|
||||
}
|
||||
|
||||
// TagsObject returns name of system object for tags.
|
||||
// TagsObject returns the name of a system object for tags.
|
||||
func (o *ObjectInfo) TagsObject() string { return ".tagset." + o.Name + "." + o.Version() }
|
||||
|
||||
// LegalHoldObject returns name of system object for lock object.
|
||||
// LegalHoldObject returns the name of a system object for a lock object.
|
||||
func (o *ObjectInfo) LegalHoldObject() string { return ".lock." + o.Name + "." + o.Version() }
|
||||
|
||||
// RetentionObject returns name of system object for retention lock object.
|
||||
// RetentionObject returns the name of a system object for a retention lock object.
|
||||
func (o *ObjectInfo) RetentionObject() string { return ".retention." + o.Name + "." + o.Version() }
|
||||
|
|
|
@ -28,7 +28,7 @@ type (
|
|||
Value string `xml:"Value" json:"Value"`
|
||||
}
|
||||
|
||||
// TopicConfiguration and LambdaFunctionConfiguration -- we don't support these configurations
|
||||
// TopicConfiguration and LambdaFunctionConfiguration -- we don't support these configurations,
|
||||
// but we need them to detect in notification configurations in requests.
|
||||
TopicConfiguration struct{}
|
||||
LambdaFunctionConfiguration struct{}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
// ErrorCode type of error status.
|
||||
// ErrorCode type of an error status.
|
||||
ErrorCode int
|
||||
|
||||
errorCodeMap map[ErrorCode]Error
|
||||
|
@ -20,7 +20,7 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||
// Error codes, non exhaustive list -- http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||
const (
|
||||
_ ErrorCode = iota
|
||||
ErrAccessDenied
|
||||
|
@ -273,7 +273,7 @@ const (
|
|||
)
|
||||
|
||||
// error code to Error structure, these fields carry respective
|
||||
// descriptions for all the error responses.
|
||||
// descriptions for all error responses.
|
||||
var errorCodes = errorCodeMap{
|
||||
ErrInvalidCopyDest: {
|
||||
ErrCode: ErrInvalidCopyDest,
|
||||
|
@ -768,7 +768,7 @@ var errorCodes = errorCodeMap{
|
|||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// FIXME: Actual XML error response also contains the header which missed in list of signed header parameters.
|
||||
// FIXME: Actual XML error response also contains the header which is missed in the list of signed header parameters.
|
||||
ErrUnsignedHeaders: {
|
||||
ErrCode: ErrUnsignedHeaders,
|
||||
Code: "AccessDenied",
|
||||
|
@ -1663,7 +1663,7 @@ var errorCodes = errorCodeMap{
|
|||
// Add your error structure here.
|
||||
}
|
||||
|
||||
// IsS3Error check if the provided error is a specific s3 error.
|
||||
// IsS3Error checks if the provided error is a specific s3 error.
|
||||
func IsS3Error(err error, code ErrorCode) bool {
|
||||
e, ok := err.(Error)
|
||||
return ok && e.ErrCode == code
|
||||
|
@ -1688,7 +1688,7 @@ func (e Error) Error() string {
|
|||
return fmt.Sprintf("%s: %d => %s", e.Code, e.HTTPStatusCode, e.Description)
|
||||
}
|
||||
|
||||
// GetAPIError provides API Error for input API error code.
|
||||
// GetAPIError provides API Error for an input API error code.
|
||||
func GetAPIError(code ErrorCode) Error {
|
||||
if apiErr, ok := errorCodes[code]; ok {
|
||||
return apiErr
|
||||
|
@ -1696,12 +1696,12 @@ func GetAPIError(code ErrorCode) Error {
|
|||
return errorCodes.toAPIErr(ErrInternalError)
|
||||
}
|
||||
|
||||
// GetAPIErrorWithError provides API Error with additional error message for input API error code.
|
||||
// GetAPIErrorWithError provides API Error with additional error message for an input API error code.
|
||||
func GetAPIErrorWithError(code ErrorCode, err error) Error {
|
||||
return errorCodes.toAPIErrWithErr(code, err)
|
||||
}
|
||||
|
||||
// ObjectError - error that linked to specific object.
|
||||
// ObjectError -- error that is linked to a specific object.
|
||||
type ObjectError struct {
|
||||
Err error
|
||||
Object string
|
||||
|
@ -1712,7 +1712,7 @@ func (e ObjectError) Error() string {
|
|||
return fmt.Sprintf("%s (%s:%s)", e.Err, e.Object, e.Version)
|
||||
}
|
||||
|
||||
// ObjectVersion get "object:version" string.
|
||||
// ObjectVersion gets "object:version" string.
|
||||
func (e ObjectError) ObjectVersion() string {
|
||||
return e.Object + ":" + e.Version
|
||||
}
|
||||
|
|
|
@ -357,7 +357,7 @@ func checkOwner(info *data.BucketInfo, owner string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// maybe need to convert owner to appropriate format
|
||||
// may need to convert owner to appropriate format
|
||||
if info.Owner.String() != owner {
|
||||
return errors.GetAPIError(errors.ErrAccessDenied)
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ type (
|
|||
cfg *Config
|
||||
}
|
||||
|
||||
// Config contains data which handler need to keep.
|
||||
// Config contains data which handler needs to keep.
|
||||
Config struct {
|
||||
DefaultPolicy *netmap.PlacementPolicy
|
||||
DefaultMaxAge int
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultPolicy is a default policy of placing container in NeoFS if it's not set at the request.
|
||||
// DefaultPolicy is a default policy of placing containers in NeoFS if it's not set at the request.
|
||||
const DefaultPolicy = "REP 3"
|
||||
|
||||
var _ api.Handler = (*handler)(nil)
|
||||
|
|
|
@ -20,7 +20,7 @@ type copyObjectArgs struct {
|
|||
|
||||
const replaceMetadataDirective = "REPLACE"
|
||||
|
||||
// path2BucketObject returns bucket and object.
|
||||
// path2BucketObject returns a bucket and an object.
|
||||
func path2BucketObject(path string) (bucket, prefix string) {
|
||||
path = strings.TrimPrefix(path, api.SlashSeparator)
|
||||
m := strings.Index(path, api.SlashSeparator)
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// DefaultMaxAge -- default value of Access-Control-Max-Age if this value is not set in a rule.
|
||||
// DefaultMaxAge is a default value of Access-Control-Max-Age if this value is not set in a rule.
|
||||
DefaultMaxAge = 600
|
||||
wildcard = "*"
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// DeleteObjectsRequest - xml carrying the object key names which needs to be deleted.
|
||||
// DeleteObjectsRequest -- xml carrying the object key names which should be deleted.
|
||||
type DeleteObjectsRequest struct {
|
||||
// Element to enable quiet mode for the request
|
||||
Quiet bool
|
||||
|
@ -23,13 +23,13 @@ type DeleteObjectsRequest struct {
|
|||
Objects []ObjectIdentifier `xml:"Object"`
|
||||
}
|
||||
|
||||
// ObjectIdentifier carries key name for the object to delete.
|
||||
// ObjectIdentifier carries the key name for the object to delete.
|
||||
type ObjectIdentifier struct {
|
||||
ObjectName string `xml:"Key"`
|
||||
VersionID string `xml:"VersionId,omitempty"`
|
||||
}
|
||||
|
||||
// DeletedObject carries key name for the object to delete.
|
||||
// DeletedObject carries the key name for the object to delete.
|
||||
type DeletedObject struct {
|
||||
ObjectIdentifier
|
||||
DeleteMarker bool `xml:"DeleteMarker,omitempty"`
|
||||
|
@ -144,7 +144,7 @@ func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
reqInfo := api.GetReqInfo(r.Context())
|
||||
|
||||
// Content-Md5 is requied should be set
|
||||
// Content-Md5 is required and should be set
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||
if _, ok := r.Header[api.ContentMD5]; !ok {
|
||||
h.logAndSendError(w, "missing Content-MD5", reqInfo, errors.GetAPIError(errors.ErrMissingContentMD5))
|
||||
|
|
|
@ -480,7 +480,7 @@ func TestObjectLegalHold(t *testing.T) {
|
|||
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
||||
assertLegalHold(t, w, legalHoldOn)
|
||||
|
||||
// to make sure put hold is idempotent operation
|
||||
// to make sure put hold is an idempotent operation
|
||||
w, r = prepareTestRequest(t, bktName, objName, &data.LegalHold{Status: legalHoldOn})
|
||||
hc.Handler().PutObjectLegalHoldHandler(w, r)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
@ -493,7 +493,7 @@ func TestObjectLegalHold(t *testing.T) {
|
|||
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
||||
assertLegalHold(t, w, legalHoldOff)
|
||||
|
||||
// to make sure put hold is idempotent operation
|
||||
// to make sure put hold is an idempotent operation
|
||||
w, r = prepareTestRequest(t, bktName, objName, &data.LegalHold{Status: legalHoldOff})
|
||||
hc.Handler().PutObjectLegalHoldHandler(w, r)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
|
|
@ -2,7 +2,7 @@ package handler
|
|||
|
||||
import "encoding/xml"
|
||||
|
||||
// ListBucketsResponse - format for list buckets response.
|
||||
// ListBucketsResponse -- format for list buckets response.
|
||||
type ListBucketsResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult" json:"-"`
|
||||
|
||||
|
@ -85,7 +85,7 @@ func NewGrantee(t GranteeType) *Grantee {
|
|||
}
|
||||
}
|
||||
|
||||
// Owner - bucket owner/principal.
|
||||
// Owner -- bucket owner/principal.
|
||||
type Owner struct {
|
||||
ID string
|
||||
DisplayName string
|
||||
|
@ -106,7 +106,7 @@ type Object struct {
|
|||
// Owner of the object.
|
||||
Owner *Owner `xml:"Owner,omitempty"`
|
||||
|
||||
// The class of storage used to store the object.
|
||||
// Class of storage used to store the object.
|
||||
StorageClass string `xml:"StorageClass,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ type DeleteMarkerEntry struct {
|
|||
// StringMap is a map[string]string.
|
||||
type StringMap map[string]string
|
||||
|
||||
// LocationResponse - format for location response.
|
||||
// LocationResponse -- format for location response.
|
||||
type LocationResponse struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"`
|
||||
Location string `xml:",chardata"`
|
||||
|
@ -182,13 +182,13 @@ type PostResponse struct {
|
|||
ETag string `xml:"Etag"`
|
||||
}
|
||||
|
||||
// Tag is AWS key-value tag.
|
||||
// Tag is an AWS key-value tag.
|
||||
type Tag struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// MarshalXML - StringMap marshals into XML.
|
||||
// MarshalXML -- StringMap marshals into XML.
|
||||
func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
tokens := []xml.Token{start}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ func shouldEscape(c byte) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// s3URLEncode is based on url.QueryEscape() code,
|
||||
// s3URLEncode is based on url.QueryEscape() code
|
||||
// while considering some S3 exceptions.
|
||||
func s3URLEncode(s string, mode encoding) string {
|
||||
spaceCount, hexCount := 0, 0
|
||||
|
|
|
@ -270,8 +270,8 @@ func DefaultCachesConfigs() *CachesConfig {
|
|||
}
|
||||
}
|
||||
|
||||
// NewLayer creates instance of layer. It checks credentials
|
||||
// and establishes gRPC connection with node.
|
||||
// NewLayer creates an instance of a layer. It checks credentials
|
||||
// and establishes gRPC connection with the node.
|
||||
func NewLayer(log *zap.Logger, neoFS neofs.NeoFS, config *Config) Client {
|
||||
return &layer{
|
||||
neoFS: neoFS,
|
||||
|
@ -307,7 +307,7 @@ func (n *layer) IsNotificationEnabled() bool {
|
|||
return n.ncontroller != nil
|
||||
}
|
||||
|
||||
// IsAuthenticatedRequest check if access box exists in current request.
|
||||
// IsAuthenticatedRequest checks if access box exists in the current request.
|
||||
func IsAuthenticatedRequest(ctx context.Context) bool {
|
||||
_, ok := ctx.Value(api.BoxData).(*accessbox.Box)
|
||||
return ok
|
||||
|
@ -364,12 +364,12 @@ func (n *layer) GetBucketACL(ctx context.Context, bktInfo *data.BucketInfo) (*Bu
|
|||
}, nil
|
||||
}
|
||||
|
||||
// PutBucketACL put bucket acl by name.
|
||||
// PutBucketACL puts bucket acl by name.
|
||||
func (n *layer) PutBucketACL(ctx context.Context, param *PutBucketACLParams) error {
|
||||
return n.setContainerEACLTable(ctx, param.BktInfo.CID, param.EACL)
|
||||
}
|
||||
|
||||
// ListBuckets returns all user containers. Name of the bucket is a container
|
||||
// ListBuckets returns all user containers. The name of the bucket is a container
|
||||
// id. Timestamp is omitted since it is not saved in neofs container.
|
||||
func (n *layer) ListBuckets(ctx context.Context) ([]*data.BucketInfo, error) {
|
||||
return n.containerList(ctx)
|
||||
|
@ -532,7 +532,7 @@ func (n *layer) CopyObject(ctx context.Context, p *CopyObjectParams) (*data.Obje
|
|||
})
|
||||
}
|
||||
|
||||
// DeleteObject removes all objects with passed nice name.
|
||||
// DeleteObject removes all objects with the passed nice name.
|
||||
func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings *data.BucketSettings, obj *VersionedObject) *VersionedObject {
|
||||
var (
|
||||
err error
|
||||
|
|
|
@ -255,7 +255,7 @@ func (n *layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
|||
zap.String("uploadID", p.Info.UploadID),
|
||||
zap.String("uploadKey", p.Info.Key),
|
||||
)
|
||||
// we return InternalError because if we are here it means we've checked InitPart before in handler and
|
||||
// we return InternalError because if we are here it means we've checked InitPart in handler before and
|
||||
// received successful result, it's strange we didn't get the InitPart again
|
||||
return nil, errors.GetAPIError(errors.ErrInternalError)
|
||||
}
|
||||
|
@ -284,7 +284,7 @@ func (n *layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
|||
initMetadata[api.ContentType] = objects[0].ContentType
|
||||
}
|
||||
|
||||
/* We will keep "S3-Upload-Id" attribute in completed object to determine is it "common" object or completed object.
|
||||
/* We will keep "S3-Upload-Id" attribute in a completed object to determine if it is a "common" object or a completed object.
|
||||
We will need to differ these objects if something goes wrong during completing multipart upload.
|
||||
I.e. we had completed the object but didn't put tagging/acl for some reason */
|
||||
delete(initMetadata, UploadPartNumberAttributeName)
|
||||
|
|
|
@ -57,7 +57,7 @@ type PrmObjectSelect struct {
|
|||
// Container to select the objects from.
|
||||
Container cid.ID
|
||||
|
||||
// Key-value object attribute which should exactly be
|
||||
// Key-value object attribute which should be
|
||||
// presented in selected objects. Optional, empty key means any.
|
||||
ExactAttribute [2]string
|
||||
|
||||
|
@ -141,53 +141,53 @@ var ErrAccessDenied = errors.New("access denied")
|
|||
// NeoFS represents virtual connection to NeoFS network.
|
||||
type NeoFS interface {
|
||||
// CreateContainer creates and saves parameterized container in NeoFS.
|
||||
// It sets 'Timestamp' attribute to current time.
|
||||
// Returns ID of the saved container.
|
||||
// It sets 'Timestamp' attribute to the current time.
|
||||
// It returns the ID of the saved container.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the container to be created.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the container from being created.
|
||||
CreateContainer(context.Context, PrmContainerCreate) (*cid.ID, error)
|
||||
|
||||
// Container reads container from NeoFS by ID.
|
||||
// Container reads a container from NeoFS by ID.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the container to be read.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the container from being read.
|
||||
Container(context.Context, cid.ID) (*container.Container, error)
|
||||
|
||||
// UserContainers reads list of the containers owned by specified user.
|
||||
// UserContainers reads a list of the containers owned by the specified user.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the containers to be listed.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the containers from being listed.
|
||||
UserContainers(context.Context, owner.ID) ([]cid.ID, error)
|
||||
|
||||
// SetContainerEACL saves eACL table of the container in NeoFS.
|
||||
// SetContainerEACL saves the eACL table of the container in NeoFS.
|
||||
//
|
||||
// Returns any error encountered which prevented the eACL to be saved.
|
||||
// It returns any error encountered which prevented the eACL from being saved.
|
||||
SetContainerEACL(context.Context, eacl.Table) error
|
||||
|
||||
// ContainerEACL reads container eACL from NeoFS by container ID.
|
||||
// ContainerEACL reads the container eACL from NeoFS by the container ID.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the eACL to be read.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the eACL from being read.
|
||||
ContainerEACL(context.Context, cid.ID) (*eacl.Table, error)
|
||||
|
||||
// DeleteContainer marks the container to be removed from NeoFS by ID.
|
||||
// Request is sent within session if the session token is specified.
|
||||
// Successful return does not guarantee the actual removal.
|
||||
// Successful return does not guarantee actual removal.
|
||||
//
|
||||
// Returns any error encountered which prevented the removal request to be sent.
|
||||
// It returns any error encountered which prevented the removal request from being sent.
|
||||
DeleteContainer(context.Context, cid.ID, *session.Token) error
|
||||
|
||||
// SelectObjects perform object selection from the NeoFS container according
|
||||
// to specified parameters. Selects user objects only.
|
||||
// SelectObjects performs object selection from the NeoFS container according
|
||||
// to the specified parameters. It selects user's objects only.
|
||||
//
|
||||
// Returns ErrAccessDenied on selection access violation.
|
||||
// It returns ErrAccessDenied on selection access violation.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the objects to be selected.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the objects from being selected.
|
||||
SelectObjects(context.Context, PrmObjectSelect) ([]oid.ID, error)
|
||||
|
||||
// ReadObject reads part of the object from the NeoFS container by identifier.
|
||||
// ReadObject reads a part of the object from the NeoFS container by identifier.
|
||||
// Exact part is returned according to the parameters:
|
||||
// * with header only: empty payload (both in-mem and reader parts are nil);
|
||||
// * with payload only: header is nil (zero range means full payload);
|
||||
|
@ -197,37 +197,37 @@ type NeoFS interface {
|
|||
//
|
||||
// Payload reader should be closed if it is no longer needed.
|
||||
//
|
||||
// Returns ErrAccessDenied on read access violation.
|
||||
// It returns ErrAccessDenied on read access violation.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the object header to be read.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the object header from being read.
|
||||
ReadObject(context.Context, PrmObjectRead) (*ObjectPart, error)
|
||||
|
||||
// CreateObject creates and saves parameterized object in the NeoFS container.
|
||||
// It sets 'Timestamp' attribute to current time.
|
||||
// Returns ID of the saved object.
|
||||
// CreateObject creates and saves a parameterized object in the NeoFS container.
|
||||
// It sets 'Timestamp' attribute to the current time.
|
||||
// It returns the ID of the saved object.
|
||||
//
|
||||
// Creation time should be written into object (UTC).
|
||||
// Creation time should be written into the object (UTC).
|
||||
//
|
||||
// Returns ErrAccessDenied on write access violation.
|
||||
// It returns ErrAccessDenied on write access violation.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the container to be created.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the container from being created.
|
||||
CreateObject(context.Context, PrmObjectCreate) (*oid.ID, error)
|
||||
|
||||
// DeleteObject marks the object to be removed from the NeoFS container by identifier.
|
||||
// Successful return does not guarantee the actual removal.
|
||||
// Successful return does not guarantee actual removal.
|
||||
//
|
||||
// Returns ErrAccessDenied on remove access violation.
|
||||
// It returns ErrAccessDenied on remove access violation.
|
||||
//
|
||||
// Returns any error encountered which prevented the removal request to be sent.
|
||||
// It returns any error encountered which prevented the removal request from being sent.
|
||||
DeleteObject(context.Context, PrmObjectDelete) error
|
||||
|
||||
// TimeToEpoch compute current epoch and epoch that corresponds provided time.
|
||||
// TimeToEpoch computes current epoch and the epoch that corresponds to the provided time.
|
||||
// Note:
|
||||
// * time must be in the future
|
||||
// * time will be ceil rounded to match epoch
|
||||
//
|
||||
// Returns any error encountered which prevented computing epochs.
|
||||
// It returns any error encountered which prevented computing epochs.
|
||||
TimeToEpoch(context.Context, time.Time) (uint64, uint64, error)
|
||||
}
|
||||
|
|
|
@ -210,7 +210,7 @@ func (n *layer) SendNotifications(ctx context.Context, p *SendNotificationParams
|
|||
return n.ncontroller.SendNotifications(topics, p)
|
||||
}
|
||||
|
||||
// checkBucketConfiguration checks notification configuration and generates ID for configurations with empty ids.
|
||||
// checkBucketConfiguration checks notification configuration and generates an ID for configurations with empty ids.
|
||||
func (n *layer) checkBucketConfiguration(conf *data.NotificationConfiguration, r *api.ReqInfo) (completed bool, err error) {
|
||||
if conf == nil {
|
||||
return
|
||||
|
@ -248,7 +248,7 @@ func filterSubjects(conf *data.NotificationConfiguration, eventType, objName str
|
|||
for _, t := range conf.QueueConfigurations {
|
||||
event := false
|
||||
for _, e := range t.Events {
|
||||
// the second condition is comparison with events ending with *:
|
||||
// the second condition is comparison with the events ending with *:
|
||||
// s3:ObjectCreated:*, s3:ObjectRemoved:* etc without the last char
|
||||
if eventType == e || strings.HasPrefix(eventType, e[:len(e)-1]) {
|
||||
event = true
|
||||
|
|
|
@ -40,7 +40,7 @@ type (
|
|||
IsLatest bool
|
||||
}
|
||||
|
||||
// ListObjectVersionsInfo stores info and list of objects' versions.
|
||||
// ListObjectVersionsInfo stores info and list of objects versions.
|
||||
ListObjectVersionsInfo struct {
|
||||
CommonPrefixes []string
|
||||
IsTruncated bool
|
||||
|
@ -138,7 +138,7 @@ func filenameFromObject(o *object.Object) string {
|
|||
return name
|
||||
}
|
||||
|
||||
// NameFromString splits name into base file name and directory path.
|
||||
// NameFromString splits name into a base file name and a directory path.
|
||||
func NameFromString(name string) (string, string) {
|
||||
ind := strings.LastIndex(name, PathSeparator)
|
||||
return name[ind+1:], name[:ind+1]
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
// MaxClients provides HTTP handler wrapper with client limit.
|
||||
// MaxClients provides HTTP handler wrapper with the client limit.
|
||||
MaxClients interface {
|
||||
Handle(http.HandlerFunc) http.HandlerFunc
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ type (
|
|||
const defaultRequestDeadline = time.Second * 30
|
||||
|
||||
// NewMaxClientsMiddleware returns MaxClients interface with handler wrapper based on
|
||||
// provided count and timeout limits.
|
||||
// the provided count and the timeout limits.
|
||||
func NewMaxClientsMiddleware(count int, timeout time.Duration) MaxClients {
|
||||
if timeout <= 0 {
|
||||
timeout = defaultRequestDeadline
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
type (
|
||||
// HTTPAPIStats holds statistics information about
|
||||
// a given API in the requests.
|
||||
// the API given in the requests.
|
||||
HTTPAPIStats struct {
|
||||
apiStats map[string]int
|
||||
sync.RWMutex
|
||||
|
@ -64,7 +64,7 @@ var (
|
|||
)
|
||||
|
||||
// Collects HTTP metrics for NeoFS S3 Gate in Prometheus specific format
|
||||
// and sends to given channel.
|
||||
// and sends to the given channel.
|
||||
func collectHTTPMetrics(ch chan<- prometheus.Metric) {
|
||||
for api, value := range httpStatsMetric.currentS3Requests.Load() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
|
@ -122,7 +122,7 @@ func APIStats(api string, f http.HandlerFunc) http.HandlerFunc {
|
|||
f.ServeHTTP(statsWriter, r)
|
||||
|
||||
// Time duration in secs since the call started.
|
||||
// We don't need to do nanosecond precision in this
|
||||
// We don't need to do nanosecond precision here
|
||||
// simply for the fact that it is not human readable.
|
||||
durationSecs := time.Since(statsWriter.startTime).Seconds()
|
||||
|
||||
|
@ -201,7 +201,7 @@ func (st *HTTPStats) updateStats(api string, w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
}
|
||||
|
||||
// WriteHeader - writes http status code.
|
||||
// WriteHeader -- writes http status code.
|
||||
func (w *responseWrapper) WriteHeader(code int) {
|
||||
w.Do(func() {
|
||||
w.statusCode = code
|
||||
|
@ -209,7 +209,7 @@ func (w *responseWrapper) WriteHeader(code int) {
|
|||
})
|
||||
}
|
||||
|
||||
// Flush - Calls the underlying Flush.
|
||||
// Flush -- calls the underlying Flush.
|
||||
func (w *responseWrapper) Flush() {
|
||||
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
// KeyVal - appended to ReqInfo.Tags.
|
||||
// KeyVal -- appended to ReqInfo.Tags.
|
||||
KeyVal struct {
|
||||
Key string
|
||||
Val string
|
||||
|
@ -27,7 +27,7 @@ type (
|
|||
UserAgent string // User Agent
|
||||
DeploymentID string // random generated s3-deployment-id
|
||||
RequestID string // x-amz-request-id
|
||||
API string // API name - GetObject PutObject NewMultipartUpload etc.
|
||||
API string // API name -- GetObject PutObject NewMultipartUpload etc.
|
||||
BucketName string // Bucket name
|
||||
ObjectName string // Object name
|
||||
URL *url.URL // Request url
|
||||
|
@ -64,15 +64,15 @@ var (
|
|||
)
|
||||
|
||||
// GetSourceIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
|
||||
// Forwarded headers (in that order), falls back to r.RemoteAddr when all
|
||||
// Forwarded headers (in that order), falls back to r.RemoteAddr when everything
|
||||
// else fails.
|
||||
func GetSourceIP(r *http.Request) string {
|
||||
var addr string
|
||||
|
||||
if fwd := r.Header.Get(xForwardedFor); fwd != "" {
|
||||
// Only grab the first (client) address. Note that '192.168.0.1,
|
||||
// Only grabs the first (client) address. Note that '192.168.0.1,
|
||||
// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
|
||||
// the first may represent forwarding proxies earlier in the chain.
|
||||
// the first one may represent forwarding proxies earlier in the chain.
|
||||
s := strings.Index(fwd, ", ")
|
||||
if s == -1 {
|
||||
s = len(fwd)
|
||||
|
@ -141,7 +141,7 @@ func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest) *ReqI
|
|||
}
|
||||
}
|
||||
|
||||
// AppendTags - appends key/val to ReqInfo.tags.
|
||||
// AppendTags -- appends key/val to ReqInfo.tags.
|
||||
func (r *ReqInfo) AppendTags(key string, val string) *ReqInfo {
|
||||
if r == nil {
|
||||
return nil
|
||||
|
@ -152,14 +152,14 @@ func (r *ReqInfo) AppendTags(key string, val string) *ReqInfo {
|
|||
return r
|
||||
}
|
||||
|
||||
// SetTags - sets key/val to ReqInfo.tags.
|
||||
// SetTags -- sets key/val to ReqInfo.tags.
|
||||
func (r *ReqInfo) SetTags(key string, val string) *ReqInfo {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
// Search of tag key already exists in tags
|
||||
// Search for a tag key already existing in tags
|
||||
var updated bool
|
||||
for _, tag := range r.tags {
|
||||
if tag.Key == key {
|
||||
|
@ -175,7 +175,7 @@ func (r *ReqInfo) SetTags(key string, val string) *ReqInfo {
|
|||
return r
|
||||
}
|
||||
|
||||
// GetTags - returns the user defined tags.
|
||||
// GetTags -- returns the user defined tags.
|
||||
func (r *ReqInfo) GetTags() []KeyVal {
|
||||
if r == nil {
|
||||
return nil
|
||||
|
|
|
@ -18,8 +18,8 @@ const (
|
|||
type NeoFS interface {
|
||||
// SystemDNS reads system DNS network parameters of the NeoFS.
|
||||
//
|
||||
// Returns exactly on non-zero value. Returns any error encountered
|
||||
// which prevented the parameter to be read.
|
||||
// It returns exactly on non-zero value. It returns any error encountered
|
||||
// which prevented the parameter from being read.
|
||||
SystemDNS(context.Context) (string, error)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
// ErrorResponse - error response format.
|
||||
// ErrorResponse -- error response format.
|
||||
ErrorResponse struct {
|
||||
XMLName xml.Name `xml:"Error" json:"-"`
|
||||
Code string
|
||||
|
@ -24,7 +24,7 @@ type (
|
|||
RequestID string `xml:"RequestId" json:"RequestId"`
|
||||
HostID string `xml:"HostId" json:"HostId"`
|
||||
|
||||
// Region where the bucket is located. This header is returned
|
||||
// The region where the bucket is located. This header is returned
|
||||
// only in HEAD bucket and ListObjects response.
|
||||
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
|
||||
|
||||
|
@ -126,7 +126,7 @@ func WriteErrorResponse(w http.ResponseWriter, reqInfo *ReqInfo, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Generate error response.
|
||||
// Generates error response.
|
||||
errorResponse := getAPIErrorResponse(reqInfo, err)
|
||||
encodedErrorResponse := EncodeResponse(errorResponse)
|
||||
WriteResponse(w, code, encodedErrorResponse, MimeXML)
|
||||
|
@ -152,7 +152,7 @@ func setCommonHeaders(w http.ResponseWriter) {
|
|||
}
|
||||
|
||||
// removeSensitiveHeaders removes confidential encryption
|
||||
// information - e.g. the SSE-C key - from the HTTP headers.
|
||||
// information -- e.g. the SSE-C key -- from the HTTP headers.
|
||||
// It has the same semantics as RemoveSensitiveEntries.
|
||||
func removeSensitiveHeaders(h http.Header) {
|
||||
h.Del(hdrSSECustomerKey)
|
||||
|
@ -212,7 +212,7 @@ func WriteSuccessResponseHeadersOnly(w http.ResponseWriter) {
|
|||
WriteResponse(w, http.StatusOK, nil, MimeNone)
|
||||
}
|
||||
|
||||
// Error - Returns S3 error string.
|
||||
// Error -- Returns S3 error string.
|
||||
func (e ErrorResponse) Error() string {
|
||||
if e.Message == "" {
|
||||
msg, ok := s3ErrorResponseMap[e.Code]
|
||||
|
@ -225,7 +225,7 @@ func (e ErrorResponse) Error() string {
|
|||
}
|
||||
|
||||
// getErrorResponse gets in standard error and resource value and
|
||||
// provides a encodable populated response values.
|
||||
// provides an encodable populated response values.
|
||||
func getAPIErrorResponse(info *ReqInfo, err error) ErrorResponse {
|
||||
code := "InternalError"
|
||||
desc := err.Error()
|
||||
|
|
|
@ -83,7 +83,7 @@ type (
|
|||
ListMultipartUploadsHandler(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// mimeType represents various MIME type used API responses.
|
||||
// mimeType represents various MIME types used in API responses.
|
||||
mimeType string
|
||||
|
||||
logResponseWriter struct {
|
||||
|
@ -95,7 +95,7 @@ type (
|
|||
)
|
||||
|
||||
const (
|
||||
// SlashSeparator - slash separator.
|
||||
// SlashSeparator -- slash separator.
|
||||
SlashSeparator = "/"
|
||||
|
||||
// MimeNone means no response type.
|
||||
|
@ -172,7 +172,7 @@ func logErrorResponse(l *zap.Logger) mux.MiddlewareFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// GetRequestID returns request ID from response writer or context.
|
||||
// GetRequestID returns the request ID from the response writer or the context.
|
||||
func GetRequestID(v interface{}) string {
|
||||
switch t := v.(type) {
|
||||
case context.Context:
|
||||
|
@ -244,11 +244,11 @@ func Attach(r *mux.Router, domains []string, m MaxClients, h Handler, center aut
|
|||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("listmultipartuploads", h.ListMultipartUploadsHandler))).Queries("uploads", "").
|
||||
Name("ListMultipartUploads")
|
||||
// GetObjectACL - this is a dummy call.
|
||||
// GetObjectACL -- this is a dummy call.
|
||||
bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||
m.Handle(metrics.APIStats("getobjectacl", h.GetObjectACLHandler))).Queries("acl", "").
|
||||
Name("GetObjectACL")
|
||||
// PutObjectACL - this is a dummy call.
|
||||
// PutObjectACL -- this is a dummy call.
|
||||
bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||
m.Handle(metrics.APIStats("putobjectacl", h.PutObjectACLHandler))).Queries("acl", "").
|
||||
Name("PutObjectACL")
|
||||
|
@ -336,27 +336,27 @@ func Attach(r *mux.Router, domains []string, m MaxClients, h Handler, center aut
|
|||
bucket.Methods(http.MethodPut).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("putbucketacl", h.PutBucketACLHandler))).Queries("acl", "").
|
||||
Name("PutBucketACL")
|
||||
// GetBucketWebsiteHandler - this is a dummy call.
|
||||
// GetBucketWebsiteHandler -- this is a dummy call.
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("getbucketwebsite", h.GetBucketWebsiteHandler))).Queries("website", "").
|
||||
Name("GetBucketWebsite")
|
||||
// GetBucketAccelerateHandler - this is a dummy call.
|
||||
// GetBucketAccelerateHandler -- this is a dummy call.
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("getbucketaccelerate", h.GetBucketAccelerateHandler))).Queries("accelerate", "").
|
||||
Name("GetBucketAccelerate")
|
||||
// GetBucketRequestPaymentHandler - this is a dummy call.
|
||||
// GetBucketRequestPaymentHandler -- this is a dummy call.
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("getbucketrequestpayment", h.GetBucketRequestPaymentHandler))).Queries("requestPayment", "").
|
||||
Name("GetBucketRequestPayment")
|
||||
// GetBucketLoggingHandler - this is a dummy call.
|
||||
// GetBucketLoggingHandler -- this is a dummy call.
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("getbucketlogging", h.GetBucketLoggingHandler))).Queries("logging", "").
|
||||
Name("GetBucketLogging")
|
||||
// GetBucketLifecycleHandler - this is a dummy call.
|
||||
// GetBucketLifecycleHandler -- this is a dummy call.
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("getbucketlifecycle", h.GetBucketLifecycleHandler))).Queries("lifecycle", "").
|
||||
Name("GetBucketLifecycle")
|
||||
// GetBucketReplicationHandler - this is a dummy call.
|
||||
// GetBucketReplicationHandler -- this is a dummy call.
|
||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||
m.Handle(metrics.APIStats("getbucketreplication", h.GetBucketReplicationHandler))).Queries("replication", "").
|
||||
Name("GetBucketReplication")
|
||||
|
@ -480,7 +480,7 @@ func Attach(r *mux.Router, domains []string, m MaxClients, h Handler, center aut
|
|||
m.Handle(metrics.APIStats("listbuckets", h.ListBucketsHandler))).
|
||||
Name("ListBuckets")
|
||||
|
||||
// If none of the routes match add default error handler routes
|
||||
// If none of the routes match, add default error handler routes
|
||||
api.NotFoundHandler = metrics.APIStats("notfound", errorResponseHandler)
|
||||
api.MethodNotAllowedHandler = metrics.APIStats("methodnotallowed", errorResponseHandler)
|
||||
}
|
||||
|
|
|
@ -54,26 +54,26 @@ type NeoFS interface {
|
|||
tokens.NeoFS
|
||||
|
||||
// ContainerExists checks container presence in NeoFS by identifier.
|
||||
// Returns nil iff container exists.
|
||||
// Returns nil if container exists.
|
||||
ContainerExists(context.Context, cid.ID) error
|
||||
|
||||
// CreateContainer creates and saves parameterized container in NeoFS.
|
||||
// It sets 'Timestamp' attribute to current time.
|
||||
// Returns ID of the saved container.
|
||||
// It sets 'Timestamp' attribute to the current time.
|
||||
// It returns the ID of the saved container.
|
||||
//
|
||||
// The container must be private with GET access of OTHERS group.
|
||||
// The container must be private with GET access for OTHERS group.
|
||||
// Creation time should also be stamped.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the container to be created.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the container from being created.
|
||||
CreateContainer(context.Context, PrmContainerCreate) (*cid.ID, error)
|
||||
|
||||
// TimeToEpoch compute current epoch and epoch that corresponds provided time.
|
||||
// TimeToEpoch computes the current epoch and the epoch that corresponds to the provided time.
|
||||
// Note:
|
||||
// * time must be in the future
|
||||
// * time will be ceil rounded to match epoch
|
||||
//
|
||||
// Returns any error encountered which prevented computing epochs.
|
||||
// It returns any error encountered which prevented computing epochs.
|
||||
TimeToEpoch(context.Context, time.Time) (uint64, uint64, error)
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// lifetimeOptions holds NeoFS epochs, iat -- epoch, which a token was issued at, exp -- epoch, when the token expires.
|
||||
// lifetimeOptions holds NeoFS epochs, iat -- epoch which the token was issued at, exp -- epoch when the token expires.
|
||||
type lifetimeOptions struct {
|
||||
Iat uint64
|
||||
Exp uint64
|
||||
|
@ -141,7 +141,7 @@ type (
|
|||
|
||||
func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner *owner.ID) (*cid.ID, error) {
|
||||
if opts.ID != nil {
|
||||
// check that container exists
|
||||
// check that the container exists
|
||||
return opts.ID, a.neoFS.ContainerExists(ctx, *opts.ID)
|
||||
}
|
||||
|
||||
|
|
|
@ -345,7 +345,7 @@ func getJSONRules(val string) ([]byte, error) {
|
|||
}
|
||||
|
||||
// getSessionRules reads json session rules.
|
||||
// Returns true if rules must be skipped.
|
||||
// It returns true if rules must be skipped.
|
||||
func getSessionRules(r string) ([]byte, bool, error) {
|
||||
if r == "none" {
|
||||
return nil, true, nil
|
||||
|
|
|
@ -194,11 +194,11 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App {
|
|||
}
|
||||
}
|
||||
|
||||
// Wait waits for application to finish.
|
||||
// Wait waits for an application to finish.
|
||||
//
|
||||
// Pre-logs a message about the launch of the application mentioning its
|
||||
// version (version.Version) and name (neofs-s3-gw). At the end writes to the
|
||||
// log about the stop.
|
||||
// version (version.Version) and its name (neofs-s3-gw). At the end, it writes
|
||||
// about the stop to the log.
|
||||
func (a *App) Wait() {
|
||||
a.log.Info("application started",
|
||||
zap.String("name", "neofs-s3-gw"),
|
||||
|
|
|
@ -99,7 +99,7 @@ const ( // Settings.
|
|||
cmdVersion = "version"
|
||||
cmdConfig = "config"
|
||||
|
||||
// envPrefix is environment variables prefix used for configuration.
|
||||
// envPrefix is an environment variables prefix used for configuration.
|
||||
envPrefix = "S3_GW"
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// newLogger constructs a zap.Logger instance for current application.
|
||||
// newLogger constructs a zap.Logger instance for the current application.
|
||||
// Panics on failure.
|
||||
//
|
||||
// Logger is built from zap's production logging configuration with:
|
||||
|
|
|
@ -40,22 +40,22 @@ type GateData struct {
|
|||
GateKey *keys.PublicKey
|
||||
}
|
||||
|
||||
// NewGateData returns GateData from provided bearer token and public gate key.
|
||||
// NewGateData returns GateData from the provided bearer token and the public gate key.
|
||||
func NewGateData(gateKey *keys.PublicKey, bearerTkn *token.BearerToken) *GateData {
|
||||
return &GateData{GateKey: gateKey, BearerToken: bearerTkn}
|
||||
}
|
||||
|
||||
// SessionTokenForPut return the first suitable container session context for PUT operation.
|
||||
// SessionTokenForPut returns the first suitable container session context for PUT operation.
|
||||
func (g *GateData) SessionTokenForPut() *session.Token {
|
||||
return g.containerSessionToken(apisession.ContainerVerbPut)
|
||||
}
|
||||
|
||||
// SessionTokenForDelete return the first suitable container session context for DELETE operation.
|
||||
// SessionTokenForDelete returns the first suitable container session context for DELETE operation.
|
||||
func (g *GateData) SessionTokenForDelete() *session.Token {
|
||||
return g.containerSessionToken(apisession.ContainerVerbDelete)
|
||||
}
|
||||
|
||||
// SessionTokenForSetEACL return the first suitable container session context for SetEACL operation.
|
||||
// SessionTokenForSetEACL returns the first suitable container session context for SetEACL operation.
|
||||
func (g *GateData) SessionTokenForSetEACL() *session.Token {
|
||||
return g.containerSessionToken(apisession.ContainerVerbSetEACL)
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func isAppropriateContainerContext(ctx *session.ContainerContext, verb apisessio
|
|||
verb == apisession.ContainerVerbSetEACL && ctx.IsForSetEACL()
|
||||
}
|
||||
|
||||
// Secrets represents AccessKey and key to encrypt gate tokens.
|
||||
// Secrets represents AccessKey and the key to encrypt gate tokens.
|
||||
type Secrets struct {
|
||||
AccessKey string
|
||||
EphemeralKey *keys.PrivateKey
|
||||
|
@ -94,7 +94,7 @@ func (x *AccessBox) Unmarshal(data []byte) error {
|
|||
return proto.Unmarshal(data, x)
|
||||
}
|
||||
|
||||
// PackTokens adds a bearer and session tokens to BearerTokens and SessionToken lists respectively.
|
||||
// PackTokens adds bearer and session tokens to BearerTokens and SessionToken lists respectively.
|
||||
// Session token can be nil.
|
||||
func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) {
|
||||
box := &AccessBox{}
|
||||
|
@ -156,7 +156,7 @@ func (x *AccessBox) GetPlacementPolicy() ([]*ContainerPolicy, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// GetBox parse AccessBox to Box.
|
||||
// GetBox parses AccessBox to Box.
|
||||
func (x *AccessBox) GetBox(owner *keys.PrivateKey) (*Box, error) {
|
||||
tokens, err := x.GetTokens(owner)
|
||||
if err != nil {
|
||||
|
|
|
@ -51,18 +51,18 @@ type PrmObjectCreate struct {
|
|||
// NeoFS represents virtual connection to NeoFS network.
|
||||
type NeoFS interface {
|
||||
// CreateObject creates and saves a parameterized object in the specified
|
||||
// NeoFS container from a specific user. It sets 'Timestamp' attribute to current time.
|
||||
// Returns ID of the saved object.
|
||||
// NeoFS container from a specific user. It sets 'Timestamp' attribute to the current time.
|
||||
// It returns the ID of the saved object.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the object to be created.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the object from being created.
|
||||
CreateObject(context.Context, PrmObjectCreate) (*oid.ID, error)
|
||||
|
||||
// ReadObjectPayload reads payload of the object from NeoFS network by address
|
||||
// into memory.
|
||||
//
|
||||
// Returns exactly one non-nil value. Returns any error encountered which
|
||||
// prevented the object payload to be read.
|
||||
// It returns exactly one non-nil value. It returns any error encountered which
|
||||
// prevented the object payload from being read.
|
||||
ReadObjectPayload(context.Context, address.Address) ([]byte, error)
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ var (
|
|||
|
||||
var _ = New
|
||||
|
||||
// New creates new Credentials instance using given cli and key.
|
||||
// New creates a new Credentials instance using the given cli and key.
|
||||
func New(neoFS NeoFS, key *keys.PrivateKey, config *cache.Config) Credentials {
|
||||
return &cred{neoFS: neoFS, key: key, cache: cache.NewAccessBoxCache(config)}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
Authmate is a tool to create gateway AWS credentials. AWS users
|
||||
are authenticated with access key IDs and secrets, while NeoFS users are
|
||||
authenticated with key pairs. To complicate things further we have S3 gateway
|
||||
that usually acts on behalf of some user, but user doesn't necessarily want to
|
||||
authenticated with key pairs. To complicate things further, we have S3 gateway
|
||||
that usually acts on behalf of some user, but the user doesn't necessarily want to
|
||||
give their keys to the gateway.
|
||||
|
||||
To solve this, we use NeoFS bearer tokens that are signed by the owner (NeoFS
|
||||
"user") and that can implement any kind of policy for NeoFS requests allowed
|
||||
using this token. However, tokens can't be used as AWS credentials directly, thus
|
||||
they're stored on NeoFS as regular objects, and access key ID is just an
|
||||
address of this object while secret is generated randomly.
|
||||
to use this token. However, tokens can't be used as AWS credentials directly. Thus,
|
||||
they're stored on NeoFS as regular objects, and an access key ID is just an
|
||||
address of this object while a secret is generated randomly.
|
||||
|
||||
Tokens are not stored on NeoFS in plaintext, they're encrypted with a set of
|
||||
gateway keys. So in order for a gateway to be able to successfully extract bearer
|
||||
gateway keys. So, in order for a gateway to be able to successfully extract bearer
|
||||
token, the object needs to be stored in a container available for the gateway
|
||||
to read, and it needs to be encrypted with this gateway's key (among others
|
||||
potentially).
|
||||
|
@ -67,10 +67,10 @@ Confirm passphrase >
|
|||
}
|
||||
}
|
||||
|
||||
wallet successfully created, file location is wallet.json
|
||||
wallet is successfully created, the file location is wallet.json
|
||||
```
|
||||
|
||||
To get public key from the wallet:
|
||||
To get the public key from the wallet:
|
||||
```shell
|
||||
$ ./bin/neo-go wallet dump-keys -w wallet.json
|
||||
|
||||
|
@ -80,23 +80,23 @@ NhLQpDnerpviUWDF77j5qyjFgavCmasJ4p (simple signature contract):
|
|||
|
||||
## Issuance of a secret
|
||||
|
||||
To issue a secret means to create a Bearer and, optionally, Session tokens and
|
||||
To issue a secret means to create Bearer and, optionally, Session tokens and
|
||||
put them as an object into a container on the NeoFS network.
|
||||
|
||||
### CLI parameters
|
||||
|
||||
**Required parameters:**
|
||||
* `--wallet` - a path to a wallet `.json` file. You can provide a passphrase to decrypt
|
||||
* `--wallet` is a path to a wallet `.json` file. You can provide a passphrase to decrypt
|
||||
a wallet via environment variable `AUTHMATE_WALLET_PASSPHRASE`, or you will be asked to enter a passphrase
|
||||
interactively. You can also specify an account address to use from a wallet using the `--address` parameter.
|
||||
* `--peer` - address of a NeoFS peer to connect to
|
||||
* `--gate-public-key` -- public `secp256r1` 33-byte short key of a gate (use flags repeatedly for multiple gates). The tokens are encrypted
|
||||
* `--peer` is an address of a NeoFS peer to connect to
|
||||
* `--gate-public-key` is a public `secp256r1` 33-byte short key of a gate (use flags repeatedly for multiple gates). The tokens are encrypted
|
||||
by a set of gateway keys, so you need to pass them as well.
|
||||
|
||||
You can issue a secret using the parameters above only. The tool will
|
||||
1. create a new container
|
||||
1. without a friendly name
|
||||
2. with ACL `0x3c8c8cce` - all operations are forbidden for `OTHERS` and `BEARER` user groups, except for `GET`
|
||||
2. with ACL `0x3c8c8cce` -- all operations are forbidden for `OTHERS` and `BEARER` user groups, except for `GET`
|
||||
3. with policy `REP 2 IN X CBF 3 SELECT 2 FROM * AS X`
|
||||
2. put bearer and session tokens with default rules (details in [Bearer tokens](#Bearer tokens) and
|
||||
[Session tokens](#Session tokens))
|
||||
|
@ -135,9 +135,9 @@ the secret. Format of `access_key_id`: `%cid0%oid`, where 0(zero) is a delimiter
|
|||
|
||||
### Bearer tokens
|
||||
|
||||
Creation of the bearer tokens is mandatory.
|
||||
Creation of bearer tokens is mandatory.
|
||||
|
||||
Rules for bearer token can be set via parameter `--bearer-rules` (json-string and file path allowed):
|
||||
Rules for a bearer token can be set via parameter `--bearer-rules` (json-string and file path allowed):
|
||||
```shell
|
||||
$ neofs-authmate issue-secret --wallet wallet.json \
|
||||
--peer 192.168.130.71:8080 \
|
||||
|
@ -186,7 +186,7 @@ If bearer rules are not set, a token will be auto-generated with a value:
|
|||
|
||||
### Session tokens
|
||||
|
||||
With session token, there are 3 options:
|
||||
With a session token, there are 3 options:
|
||||
1. append `--session-tokens` parameter with your custom rules in json format (as a string or file path). E.g.:
|
||||
```shell
|
||||
$ neofs-authmate issue-secret --wallet wallet.json \
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
### Credentials
|
||||
|
||||
To configure basic settings that the AWS CLI uses to interact with the Gateway, follow steps below:
|
||||
To configure basic settings that the AWS CLI uses to interact with the Gateway, follow the steps below:
|
||||
|
||||
1. issue a secret with neofs-authmate tool (see [NeoFS Authmate] (#neofs-authmate))
|
||||
2. execute the command
|
||||
|
|
|
@ -30,7 +30,7 @@ Reference:
|
|||
## ACL
|
||||
|
||||
For now there are some limitations:
|
||||
* [Bucket policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html) support only one `Principal` (type `AWS`) per `Statement`. To refer all users use `"AWS": "*"`
|
||||
* [Bucket policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html) supports only one `Principal` (type `AWS`) per `Statement`. To refer all users use `"AWS": "*"`
|
||||
* AWS conditions and wildcard are not supported in [resources](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-arn-format.html)
|
||||
* Only `CanonicalUser` (with hex encoded public key) and `All Users Group` are supported in [ACL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html)
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ a gateway spread requests equally among them (using weight 1 for every node):
|
|||
$ neofs-s3-gw -p 192.168.130.72:8080 -p 192.168.130.71:8080
|
||||
```
|
||||
If you want some specific load distribution proportions, use weights and priorities, they
|
||||
can only be specified via environment variables or configuration file.
|
||||
can only be specified via environment variables or a configuration file.
|
||||
|
||||
### Wallet
|
||||
|
||||
|
@ -49,8 +49,8 @@ Gateway listens on `0.0.0.0:8080` by default, and you can change that with the `
|
|||
|
||||
It can also provide TLS interface for its users, just specify paths to the key and
|
||||
certificate files via `--tls.key_file` and `--tls.cert_file` parameters. Note
|
||||
that using these options makes gateway TLS-only, if you need to serve both TLS
|
||||
and plain text you either have to run two gateway instances or use some
|
||||
that using these options makes gateway TLS-only. If you need to serve both TLS
|
||||
and plain text, you either have to run two gateway instances or use some
|
||||
external redirecting solution.
|
||||
|
||||
Example to bind to `192.168.130.130:443` and serve TLS there (keys and nodes are
|
||||
|
@ -100,7 +100,7 @@ default. To enable them, use `--pprof` and `--metrics` flags or
|
|||
|
||||
## YAML file and environment variables
|
||||
|
||||
Example of YAML configuration file: [.yaml-example](/config/config.yaml)
|
||||
Example of a YAML configuration file: [.yaml-example](/config/config.yaml)
|
||||
Examples of environment variables: [.env-example](/config/config.env).
|
||||
|
||||
A path to a configuration file can be specified with `--config` parameter:
|
||||
|
@ -109,7 +109,7 @@ A path to a configuration file can be specified with `--config` parameter:
|
|||
$ neofs-s3-gw --config your-config.yaml
|
||||
```
|
||||
|
||||
Parameters of the following groups can be configured via `.yaml` file or environment variables only:
|
||||
Parameters of the following groups can be configured via a `.yaml` file or environment variables only:
|
||||
1. logging -- logging level
|
||||
2. caching -- lifetime and size for each cache
|
||||
3. notifications
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# S3 compatibility test results
|
||||
|
||||
To update this file using tests result run:
|
||||
To update this file using tests result, run:
|
||||
```sh
|
||||
./updateTestsResult.sh ceph_tests_result.txt
|
||||
```
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// GetPassword gets passphrase for wallet.
|
||||
// GetPassword gets the passphrase for a wallet.
|
||||
func GetPassword(v *viper.Viper, variable string) *string {
|
||||
var password *string
|
||||
if v.IsSet(variable) {
|
||||
|
@ -21,7 +21,7 @@ func GetPassword(v *viper.Viper, variable string) *string {
|
|||
return password
|
||||
}
|
||||
|
||||
// GetKeyFromPath reads wallet and gets private key.
|
||||
// GetKeyFromPath reads a wallet and gets the private key.
|
||||
func GetKeyFromPath(walletPath, addrStr string, password *string) (*keys.PrivateKey, error) {
|
||||
if len(walletPath) == 0 {
|
||||
return nil, fmt.Errorf("wallet path must not be empty")
|
||||
|
|
Loading…
Reference in a new issue