#30 add object name resolving #43
17 changed files with 507 additions and 42 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ bin
|
|||
temp
|
||||
/plugins/
|
||||
/vendor/
|
||||
internal/frostfs/services/tree
|
||||
|
||||
.test.env
|
||||
*~
|
||||
|
|
9
Makefile
9
Makefile
|
@ -15,6 +15,7 @@ METRICS_DUMP_OUT ?= ./metrics-dump.json
|
|||
BINDIR = bin
|
||||
DIRS = $(BINDIR)
|
||||
BINS = $(BINDIR)/frostfs-http-gw
|
||||
SYNCDIR = internal/frostfs/services/tree
|
||||
|
||||
.PHONY: all $(BINS) $(DIRS) dep docker/ test cover fmt image image-push dirty-image lint docker/lint pre-commit unpre-commit version clean
|
||||
|
||||
|
@ -27,8 +28,7 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
|||
|
||||
# Make all binaries
|
||||
all: $(BINS)
|
||||
|
||||
$(BINS): $(DIRS) dep
|
||||
$(BINS): sync-tree $(DIRS) dep
|
||||
@echo "⇒ Build $@"
|
||||
CGO_ENABLED=0 \
|
||||
go build -v -trimpath \
|
||||
|
@ -39,6 +39,10 @@ $(DIRS):
|
|||
@echo "⇒ Ensure dir: $@"
|
||||
@mkdir -p $@
|
||||
|
||||
# Synchronize tree service
|
||||
sync-tree:
|
||||
@./syncTree.sh
|
||||
|
||||
# Pull go dependencies
|
||||
dep:
|
||||
@printf "⇒ Download requirements: "
|
||||
|
@ -132,6 +136,7 @@ version:
|
|||
clean:
|
||||
rm -rf vendor
|
||||
rm -rf $(BINDIR)
|
||||
rm -rf $(SYNCDIR)
|
||||
|
||||
# Package for Debian
|
||||
debpackage:
|
||||
|
|
22
api/layer/tree_service.go
Normal file
22
api/layer/tree_service.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
)
|
||||
|
||||
// TreeService provide interface to interact with tree service using s3 data models.
|
||||
type TreeService interface {
|
||||
GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*api.NodeVersion, error)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNodeNotFound is returned from Tree service in case of not found error.
|
||||
ErrNodeNotFound = errors.New("not found")
|
||||
|
||||
// ErrNodeAccessDenied is returned from Tree service in case of access denied error.
|
||||
ErrNodeAccessDenied = errors.New("access denied")
|
||||
)
|
17
api/tree.go
Normal file
17
api/tree.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
)
|
||||
|
||||
// NodeVersion represent node from tree service.
|
||||
type NodeVersion struct {
|
||||
BaseNodeVersion
|
||||
DeleteMarker bool
|
||||
}
|
||||
|
||||
// BaseNodeVersion is minimal node info from tree service.
|
||||
// Basically used for "system" object.
|
||||
type BaseNodeVersion struct {
|
||||
OID oid.ID
|
||||
}
|
41
app.go
41
app.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -14,9 +13,11 @@ import (
|
|||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/pkg/tracing"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/downloader"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/frostfs/services"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/uploader"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
|
@ -30,6 +31,8 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
"github.com/valyala/fasthttp"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -37,6 +40,7 @@ type (
|
|||
log *zap.Logger
|
||||
logLevel zap.AtomicLevel
|
||||
pool *pool.Pool
|
||||
key *keys.PrivateKey
|
||||
owner *user.ID
|
||||
cfg *viper.Viper
|
||||
webServer *fasthttp.Server
|
||||
|
@ -93,7 +97,6 @@ func WithConfig(c *viper.Viper) Option {
|
|||
|
||||
func newApp(ctx context.Context, opt ...Option) App {
|
||||
var (
|
||||
key *ecdsa.PrivateKey
|
||||
err error
|
||||
)
|
||||
|
||||
|
@ -120,17 +123,17 @@ func newApp(ctx context.Context, opt ...Option) App {
|
|||
a.webServer.DisablePreParseMultipartForm = true
|
||||
a.webServer.StreamRequestBody = a.cfg.GetBool(cfgWebStreamRequestBody)
|
||||
// -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
key, err = getFrostFSKey(a)
|
||||
a.key, err = getFrostFSKey(a)
|
||||
if err != nil {
|
||||
a.log.Fatal("failed to get frostfs credentials", zap.Error(err))
|
||||
}
|
||||
|
||||
var owner user.ID
|
||||
user.IDFromKey(&owner, key.PublicKey)
|
||||
user.IDFromKey(&owner, a.key.PrivateKey.PublicKey)
|
||||
a.owner = &owner
|
||||
|
||||
var prm pool.InitParameters
|
||||
prm.SetKey(key)
|
||||
prm.SetKey(&a.key.PrivateKey)
|
||||
prm.SetNodeDialTimeout(a.cfg.GetDuration(cfgConTimeout))
|
||||
prm.SetNodeStreamTimeout(a.cfg.GetDuration(cfgStreamTimeout))
|
||||
prm.SetHealthcheckTimeout(a.cfg.GetDuration(cfgReqTimeout))
|
||||
|
@ -277,7 +280,7 @@ func remove(list []string, element string) []string {
|
|||
return list
|
||||
}
|
||||
|
||||
func getFrostFSKey(a *app) (*ecdsa.PrivateKey, error) {
|
||||
func getFrostFSKey(a *app) (*keys.PrivateKey, error) {
|
||||
walletPath := a.cfg.GetString(cfgWalletPath)
|
||||
|
||||
if len(walletPath) == 0 {
|
||||
|
@ -286,7 +289,7 @@ func getFrostFSKey(a *app) (*ecdsa.PrivateKey, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &key.PrivateKey, nil
|
||||
return key, nil
|
||||
}
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
if err != nil {
|
||||
|
@ -304,7 +307,7 @@ func getFrostFSKey(a *app) (*ecdsa.PrivateKey, error) {
|
|||
return getKeyFromWallet(w, address, password)
|
||||
}
|
||||
|
||||
func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*ecdsa.PrivateKey, error) {
|
||||
func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*keys.PrivateKey, error) {
|
||||
var addr util.Uint160
|
||||
var err error
|
||||
|
||||
|
@ -334,7 +337,7 @@ func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*ecds
|
|||
return nil, fmt.Errorf("couldn't decrypt account: %w", err)
|
||||
}
|
||||
|
||||
return &acc.PrivateKey().PrivateKey, nil
|
||||
return acc.PrivateKey(), nil
|
||||
}
|
||||
|
||||
func (a *app) Wait() {
|
||||
|
@ -351,8 +354,9 @@ func (a *app) setHealthStatus() {
|
|||
}
|
||||
|
||||
func (a *app) Serve(ctx context.Context) {
|
||||
treeClient := a.initTree(ctx)
|
||||
uploadRoutes := uploader.New(ctx, a.AppParams(), a.settings.Uploader)
|
||||
downloadRoutes := downloader.New(ctx, a.AppParams(), a.settings.Downloader)
|
||||
downloadRoutes := downloader.New(ctx, a.AppParams(), a.settings.Downloader, treeClient)
|
||||
|
||||
// Configure router.
|
||||
a.configureRouter(uploadRoutes, downloadRoutes)
|
||||
|
@ -475,8 +479,8 @@ func (a *app) configureRouter(uploadRoutes *uploader.Uploader, downloadRoutes *d
|
|||
}
|
||||
r.POST("/upload/{cid}", a.logger(uploadRoutes.Upload))
|
||||
a.log.Info("added path /upload/{cid}")
|
||||
r.GET("/get/{cid}/{oid}", a.logger(downloadRoutes.DownloadByAddress))
|
||||
r.HEAD("/get/{cid}/{oid}", a.logger(downloadRoutes.HeadByAddress))
|
||||
r.GET("/get/{cid}/{oid:*}", a.logger(downloadRoutes.DownloadByAddressOrBucketName))
|
||||
r.HEAD("/get/{cid}/{oid:*}", a.logger(downloadRoutes.HeadByAddressOrBucketName))
|
||||
a.log.Info("added path /get/{cid}/{oid}")
|
||||
r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(downloadRoutes.DownloadByAttribute))
|
||||
r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(downloadRoutes.HeadByAttribute))
|
||||
|
@ -565,6 +569,19 @@ func (a *app) serverIndex(address string) int {
|
|||
return -1
|
||||
}
|
||||
|
||||
func (a *app) initTree(ctx context.Context) *tree.Tree {
|
||||
treeServiceEndpoint := a.cfg.GetString(cfgTreeServiceEndpoint)
|
||||
grpcDialOpt := grpc.WithTransportCredentials(insecure.NewCredentials())
|
||||
treeGRPCClient, err := services.NewTreeServiceClientGRPC(ctx, treeServiceEndpoint, a.key, grpcDialOpt)
|
||||
if err != nil {
|
||||
a.log.Fatal("failed to create tree service", zap.Error(err))
|
||||
}
|
||||
treeService := tree.NewTree(treeGRPCClient)
|
||||
a.log.Info("init tree service", zap.String("endpoint", treeServiceEndpoint))
|
||||
|
||||
return treeService
|
||||
}
|
||||
|
||||
func (a *app) initTracing(ctx context.Context) {
|
||||
instanceID := ""
|
||||
if len(a.servers) > 0 {
|
||||
|
|
|
@ -93,6 +93,9 @@ HTTP_GW_POOL_ERROR_THRESHOLD=100
|
|||
# Enable zip compression to download files by common prefix.
|
||||
HTTP_GW_ZIP_COMPRESSION=false
|
||||
|
||||
# Endpoint of the tree service. Must be provided. Can be one of the node address (from the `peers` section).
|
||||
HTTP_GW_TREE_SERVICE=grpc://s01.frostfs.devenv:8080
|
||||
|
||||
HTTP_GW_TRACING_ENABLED=true
|
||||
HTTP_GW_TRACING_ENDPOINT="localhost:4317"
|
||||
HTTP_GW_TRACING_EXPORTER="otlp_grpc"
|
|
@ -93,6 +93,10 @@ resolve_order:
|
|||
upload_header:
|
||||
use_default_timestamp: false # Create timestamp for object if it isn't provided by header.
|
||||
|
||||
# Endpoint of the tree service. Must be provided. Can be one of the node address (from the `peers` section).
|
||||
tree:
|
||||
service: 127.0.0.1:8080
|
||||
|
||||
connect_timeout: 5s # Timeout to dial node.
|
||||
stream_timeout: 10s # Timeout for individual operations in streaming RPC.
|
||||
request_timeout: 5s # Timeout to check node health during rebalance.
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
|
@ -212,6 +213,7 @@ type Downloader struct {
|
|||
pool *pool.Pool
|
||||
containerResolver *resolver.ContainerResolver
|
||||
settings *Settings
|
||||
tree *tree.Tree
|
||||
}
|
||||
|
||||
// Settings stores reloading parameters, so it has to provide atomic getters and setters.
|
||||
|
@ -228,13 +230,14 @@ func (s *Settings) SetZipCompression(val bool) {
|
|||
}
|
||||
|
||||
// New creates an instance of Downloader using specified options.
|
||||
func New(ctx context.Context, params *utils.AppParams, settings *Settings) *Downloader {
|
||||
func New(ctx context.Context, params *utils.AppParams, settings *Settings, tree *tree.Tree) *Downloader {
|
||||
return &Downloader{
|
||||
appCtx: ctx,
|
||||
log: params.Logger,
|
||||
pool: params.Pool,
|
||||
settings: settings,
|
||||
containerResolver: params.Resolver,
|
||||
tree: tree,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,9 +248,16 @@ func (d *Downloader) newRequest(ctx *fasthttp.RequestCtx, log *zap.Logger) *requ
|
|||
}
|
||||
}
|
||||
|
||||
// DownloadByAddress handles download requests using simple cid/oid format.
|
||||
func (d *Downloader) DownloadByAddress(c *fasthttp.RequestCtx) {
|
||||
d.byAddress(c, receiveFile)
|
||||
// DownloadByAddressOrBucketName handles download requests using simple cid/oid or bucketname/key format.
|
||||
func (d *Downloader) DownloadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
||||
test, _ := c.UserValue("oid").(string)
|
||||
var id oid.ID
|
||||
err := id.DecodeString(test)
|
||||
if err != nil {
|
||||
d.byBucketname(c, receiveFile)
|
||||
} else {
|
||||
d.byAddress(c, receiveFile)
|
||||
}
|
||||
}
|
||||
|
||||
// byAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
||||
|
@ -290,6 +300,47 @@ func (d *Downloader) byAddress(c *fasthttp.RequestCtx, f func(context.Context, r
|
|||
f(ctx, *d.newRequest(c, log), d.pool, addr)
|
||||
}
|
||||
|
||||
// byBucketname is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
||||
// prepares request and object address to it.
|
||||
func (d *Downloader) byBucketname(c *fasthttp.RequestCtx, f func(context.Context, request, *pool.Pool, oid.Address)) {
|
||||
var (
|
||||
bucketname = c.UserValue("cid").(string)
|
||||
key = c.UserValue("oid").(string)
|
||||
log = d.log.With(zap.String("bucketname", bucketname), zap.String("key", key))
|
||||
)
|
||||
|
||||
cnrID, err := utils.GetContainerID(d.appCtx, bucketname, d.containerResolver)
|
||||
if err != nil {
|
||||
log.Error("wrong container id", zap.Error(err))
|
||||
response.Error(c, "wrong container id", fasthttp.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, err := tokens.StoreBearerTokenAppCtx(c, d.appCtx)
|
||||
if err != nil {
|
||||
log.Error("could not fetch and store bearer token", zap.Error(err))
|
||||
response.Error(c, "could not fetch and store bearer token: "+err.Error(), fasthttp.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
foundOid, err := d.tree.GetLatestVersion(ctx, cnrID, key)
|
||||
if err != nil {
|
||||
log.Error("object wasn't found", zap.Error(err))
|
||||
response.Error(c, "object wasn't found", fasthttp.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if foundOid.DeleteMarker {
|
||||
log.Error("object was deleted")
|
||||
response.Error(c, "object deleted", fasthttp.StatusNotFound)
|
||||
return
|
||||
}
|
||||
var addr oid.Address
|
||||
addr.SetContainer(*cnrID)
|
||||
addr.SetObject(foundOid.OID)
|
||||
|
||||
f(ctx, *d.newRequest(c, log), d.pool, addr)
|
||||
}
|
||||
|
||||
// DownloadByAttribute handles attribute-based download requests.
|
||||
func (d *Downloader) DownloadByAttribute(c *fasthttp.RequestCtx) {
|
||||
d.byAttribute(c, receiveFile)
|
||||
|
|
|
@ -121,9 +121,17 @@ func idsToResponse(resp *fasthttp.Response, obj *object.Object) {
|
|||
resp.Header.Set(hdrContainerID, cnrID.String())
|
||||
}
|
||||
|
||||
// HeadByAddress handles head requests using simple cid/oid format.
|
||||
func (d *Downloader) HeadByAddress(c *fasthttp.RequestCtx) {
|
||||
d.byAddress(c, headObject)
|
||||
// HeadByAddressOrBucketName handles head requests using simple cid/oid or bucketname/key format.
|
||||
func (d *Downloader) HeadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
||||
test, _ := c.UserValue("oid").(string)
|
||||
var id oid.ID
|
||||
|
||||
err := id.DecodeString(test)
|
||||
if err != nil {
|
||||
d.byBucketname(c, headObject)
|
||||
} else {
|
||||
d.byAddress(c, headObject)
|
||||
}
|
||||
}
|
||||
|
||||
// HeadByAttribute handles attribute-based head requests.
|
||||
|
|
19
go.mod
19
go.mod
|
@ -4,6 +4,7 @@ go 1.18
|
|||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230418080822-bd44a3f47b85
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230505094539-15b4287092bd
|
||||
github.com/fasthttp/router v1.4.1
|
||||
github.com/nspcc-dev/neo-go v0.101.0
|
||||
|
@ -18,11 +19,12 @@ require (
|
|||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
go.uber.org/atomic v1.10.0
|
||||
go.uber.org/zap v1.24.0
|
||||
google.golang.org/grpc v1.53.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb // indirect
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
||||
|
@ -56,7 +58,7 @@ require (
|
|||
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.15.0 // indirect
|
||||
github.com/klauspost/compress v1.16.4 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
|
@ -95,16 +97,15 @@ require (
|
|||
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
|
26
go.sum
26
go.sum
|
@ -586,8 +586,9 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6
|
|||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
|
||||
github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -1005,8 +1006,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5
|
|||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -1099,8 +1100,8 @@ golang.org/x/net v0.0.0-20211108170745-6635138e15ea/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1226,13 +1227,13 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1242,15 +1243,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180318012157-96caea41033d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
114
internal/frostfs/services/tree_client_grpc.go
Normal file
114
internal/frostfs/services/tree_client_grpc.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
grpcService "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/frostfs/services/tree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type GetNodeByPathResponseInfoWrapper struct {
|
||||
response *grpcService.GetNodeByPathResponse_Info
|
||||
}
|
||||
|
||||
func (n GetNodeByPathResponseInfoWrapper) GetMeta() []tree.Meta {
|
||||
res := make([]tree.Meta, len(n.response.Meta))
|
||||
for i, value := range n.response.Meta {
|
||||
res[i] = value
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type GetSubTreeResponseBodyWrapper struct {
|
||||
response *grpcService.GetSubTreeResponse_Body
|
||||
}
|
||||
|
||||
func (n GetSubTreeResponseBodyWrapper) GetMeta() []tree.Meta {
|
||||
res := make([]tree.Meta, len(n.response.Meta))
|
||||
for i, value := range n.response.Meta {
|
||||
res[i] = value
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type ServiceClientGRPC struct {
|
||||
key *keys.PrivateKey
|
||||
conn *grpc.ClientConn
|
||||
service grpcService.TreeServiceClient
|
||||
}
|
||||
|
||||
func NewTreeServiceClientGRPC(ctx context.Context, addr string, key *keys.PrivateKey, grpcOpts ...grpc.DialOption) (*ServiceClientGRPC, error) {
|
||||
conn, err := grpc.Dial(addr, grpcOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("did not connect: %v", err)
|
||||
}
|
||||
|
||||
c := grpcService.NewTreeServiceClient(conn)
|
||||
if _, err = c.Healthcheck(ctx, &grpcService.HealthcheckRequest{}); err != nil {
|
||||
return nil, fmt.Errorf("healthcheck: %w", err)
|
||||
}
|
||||
|
||||
return &ServiceClientGRPC{
|
||||
key: key,
|
||||
conn: conn,
|
||||
service: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ServiceClientGRPC) GetNodes(ctx context.Context, p *tree.GetNodesParams) ([]tree.NodeResponse, error) {
|
||||
request := &grpcService.GetNodeByPathRequest{
|
||||
Body: &grpcService.GetNodeByPathRequest_Body{
|
||||
ContainerId: p.CnrID[:],
|
||||
TreeId: p.TreeID,
|
||||
Path: p.Path,
|
||||
Attributes: p.Meta,
|
||||
PathAttribute: tree.FileNameKey,
|
||||
LatestOnly: p.LatestOnly,
|
||||
AllAttributes: p.AllAttrs,
|
||||
BearerToken: getBearer(ctx),
|
||||
},
|
||||
}
|
||||
|
||||
if err := c.signRequest(request.Body, func(key, sign []byte) {
|
||||
request.Signature = &grpcService.Signature{
|
||||
Key: key,
|
||||
Sign: sign,
|
||||
}
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.service.GetNodeByPath(ctx, request)
|
||||
if err != nil {
|
||||
return nil, handleError("failed to get node by path", err)
|
||||
}
|
||||
|
||||
res := make([]tree.NodeResponse, len(resp.GetBody().GetNodes()))
|
||||
for i, info := range resp.GetBody().GetNodes() {
|
||||
res[i] = GetNodeByPathResponseInfoWrapper{info}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getBearer(ctx context.Context) []byte {
|
||||
token, err := tokens.LoadBearerToken(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return token.Marshal()
|
||||
}
|
||||
|
||||
func handleError(msg string, err error) error {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return fmt.Errorf("%w: %s", tree.ErrNodeNotFound, err.Error())
|
||||
} else if strings.Contains(err.Error(), "is denied by") {
|
||||
return fmt.Errorf("%w: %s", tree.ErrNodeAccessDenied, err.Error())
|
||||
}
|
||||
return fmt.Errorf("%s: %w", msg, err)
|
||||
}
|
29
internal/frostfs/services/tree_client_grpc_signature.go
Normal file
29
internal/frostfs/services/tree_client_grpc_signature.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*REMOVE THIS AFTER SIGNATURE WILL BE AVAILABLE IN TREE CLIENT FROM FROSTFS NODE*/
|
||||
package services
|
||||
|
||||
import (
|
||||
crypto "git.frostfs.info/TrueCloudLab/frostfs-crypto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func (c *ServiceClientGRPC) signData(buf []byte, f func(key, sign []byte)) error {
|
||||
// crypto package should not be used outside of API libraries (see neofs-node#491).
|
||||
// For now tree service does not include into SDK Client nor SDK Pool, so there is no choice.
|
||||
// When SDK library adopts Tree service client, this should be dropped.
|
||||
sign, err := crypto.Sign(&c.key.PrivateKey, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f(c.key.PublicKey().Bytes(), sign)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ServiceClientGRPC) signRequest(requestBody proto.Message, f func(key, sign []byte)) error {
|
||||
buf, err := proto.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.signData(buf, f)
|
||||
}
|
|
@ -59,6 +59,9 @@ const (
|
|||
cfgRebalance = "rebalance_timer"
|
||||
cfgPoolErrorThreshold = "pool_error_threshold"
|
||||
|
||||
// Grpc path to tree service.
|
||||
cfgTreeServiceEndpoint = "tree.service"
|
||||
|
||||
// Logger.
|
||||
cfgLoggerLevel = "logger.level"
|
||||
|
||||
|
|
21
syncTree.sh
Executable file
21
syncTree.sh
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
mkdir -p internal/frostfs/services/tree 2>/dev/null
|
||||
|
||||
REVISION="f07d4158f50ed5c7f44cc0bc224c3d03edf27f3b"
|
||||
|
||||
echo "tree service revision ${REVISION}"
|
||||
|
||||
# regexp below find all link to source code files which end with ".pb.go" and retrieve the file names
|
||||
# we use `[^.]*` as non greedy workaround for `.*`
|
||||
FILES=$(curl -s https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/${REVISION}/pkg/services/tree | sed -n "s,.*\"/TrueCloudLab/frostfs-node/src/commit/${REVISION}/pkg/services/tree/\([^.]*\.pb\.go\)\".*,\1,p")
|
||||
|
||||
for file in $FILES; do
|
||||
if [[ $file == *"frostfs"* ]]; then
|
||||
echo "skip '$file'"
|
||||
continue
|
||||
else
|
||||
echo "sync '$file' in tree service"
|
||||
fi
|
||||
curl -s "https://git.frostfs.info/TrueCloudLab/frostfs-node/raw/commit/${REVISION}/pkg/services/tree/${file}" -o "./internal/frostfs/services/tree/${file}"
|
||||
done
|
|
@ -60,6 +60,17 @@ func StoreBearerToken(ctx *fasthttp.RequestCtx) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// StoreBearerTokenAppCtx extracts a bearer token from the header or cookie and stores
|
||||
// it in the application context.
|
||||
func StoreBearerTokenAppCtx(ctx *fasthttp.RequestCtx, appCtx context.Context) (context.Context, error) {
|
||||
tkn, err := fetchBearerToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newCtx := context.WithValue(appCtx, bearerTokenKey, tkn)
|
||||
return newCtx, nil
|
||||
}
|
||||
|
||||
// LoadBearerToken returns a bearer token stored in the context given (if it's
|
||||
// present there).
|
||||
func LoadBearerToken(ctx context.Context) (*bearer.Token, error) {
|
||||
|
|
156
tree/tree.go
Normal file
156
tree/tree.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api/layer"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
)
|
||||
|
||||
type (
|
||||
Tree struct {
|
||||
service ServiceClient
|
||||
}
|
||||
|
||||
// ServiceClient is a client to interact with tree service.
|
||||
// Each method must return ErrNodeNotFound or ErrNodeAccessDenied if relevant.
|
||||
ServiceClient interface {
|
||||
GetNodes(ctx context.Context, p *GetNodesParams) ([]NodeResponse, error)
|
||||
}
|
||||
|
||||
treeNode struct {
|
||||
ObjID oid.ID
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
GetNodesParams struct {
|
||||
CnrID cid.ID
|
||||
TreeID string
|
||||
Path []string
|
||||
Meta []string
|
||||
LatestOnly bool
|
||||
AllAttrs bool
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNodeNotFound is returned from ServiceClient in case of not found error.
|
||||
ErrNodeNotFound = layer.ErrNodeNotFound
|
||||
|
||||
// ErrNodeAccessDenied is returned from ServiceClient service in case of access denied error.
|
||||
ErrNodeAccessDenied = layer.ErrNodeAccessDenied
|
||||
)
|
||||
|
||||
const (
|
||||
FileNameKey = "FileName"
|
||||
)
|
||||
|
||||
const (
|
||||
oidKV = "OID"
|
||||
|
||||
// keys for delete marker nodes.
|
||||
isDeleteMarkerKV = "IsDeleteMarker"
|
||||
|
||||
// versionTree -- ID of a tree with object versions.
|
||||
versionTree = "version"
|
||||
|
||||
separator = "/"
|
||||
)
|
||||
|
||||
// NewTree creates instance of Tree using provided address and create grpc connection.
|
||||
func NewTree(service ServiceClient) *Tree {
|
||||
return &Tree{service: service}
|
||||
}
|
||||
|
||||
type Meta interface {
|
||||
GetKey() string
|
||||
GetValue() []byte
|
||||
}
|
||||
|
||||
type NodeResponse interface {
|
||||
GetMeta() []Meta
|
||||
}
|
||||
|
||||
func newTreeNode(nodeInfo NodeResponse) (*treeNode, error) {
|
||||
treeNode := &treeNode{
|
||||
Meta: make(map[string]string, len(nodeInfo.GetMeta())),
|
||||
}
|
||||
|
||||
for _, kv := range nodeInfo.GetMeta() {
|
||||
switch kv.GetKey() {
|
||||
case oidKV:
|
||||
if err := treeNode.ObjID.DecodeString(string(kv.GetValue())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
treeNode.Meta[kv.GetKey()] = string(kv.GetValue())
|
||||
}
|
||||
}
|
||||
|
||||
return treeNode, nil
|
||||
}
|
||||
|
||||
func (n *treeNode) Get(key string) (string, bool) {
|
||||
value, ok := n.Meta[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (n *treeNode) FileName() (string, bool) {
|
||||
value, ok := n.Meta[FileNameKey]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func newNodeVersion(node NodeResponse) (*api.NodeVersion, error) {
|
||||
treeNode, err := newTreeNode(node)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tree node: %w", err)
|
||||
}
|
||||
|
||||
return newNodeVersionFromTreeNode(treeNode), nil
|
||||
}
|
||||
|
||||
func newNodeVersionFromTreeNode(treeNode *treeNode) *api.NodeVersion {
|
||||
_, isDeleteMarker := treeNode.Get(isDeleteMarkerKV)
|
||||
|
||||
version := &api.NodeVersion{
|
||||
BaseNodeVersion: api.BaseNodeVersion{
|
||||
OID: treeNode.ObjID,
|
||||
},
|
||||
DeleteMarker: isDeleteMarker,
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
func (c *Tree) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*api.NodeVersion, error) {
|
||||
meta := []string{oidKV, isDeleteMarkerKV}
|
||||
path := pathFromName(objectName)
|
||||
|
||||
p := &GetNodesParams{
|
||||
CnrID: *cnrID,
|
||||
TreeID: versionTree,
|
||||
Path: path,
|
||||
Meta: meta,
|
||||
LatestOnly: true,
|
||||
AllAttrs: false,
|
||||
}
|
||||
nodes, err := c.service.GetNodes(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return nil, layer.ErrNodeNotFound
|
||||
}
|
||||
|
||||
return newNodeVersion(nodes[0])
|
||||
}
|
||||
|
||||
// pathFromName splits name by '/'.
|
||||
func pathFromName(objectName string) []string {
|
||||
return strings.Split(objectName, separator)
|
||||
}
|
Loading…
Reference in a new issue