[#1] Add route to list containers

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-04-13 18:23:03 +03:00 committed by Alex Vanin
parent d1cb88672c
commit 26f0ae93f4
12 changed files with 913 additions and 30 deletions

View file

@ -43,6 +43,7 @@ const (
testListenAddress = "localhost:8082" testListenAddress = "localhost:8082"
testHost = "http://" + testListenAddress testHost = "http://" + testListenAddress
testNode = "localhost:8080" testNode = "localhost:8080"
containerName = "test-container"
// XNeofsTokenSignature header contains base64 encoded signature of the token body. // XNeofsTokenSignature header contains base64 encoded signature of the token body.
XNeofsTokenSignature = "X-Neofs-Token-Signature" XNeofsTokenSignature = "X-Neofs-Token-Signature"
@ -72,7 +73,7 @@ func TestIntegration(t *testing.T) {
aioContainer := createDockerContainer(ctx, t, aioImage+version) aioContainer := createDockerContainer(ctx, t, aioImage+version)
cancel := runServer(ctx, t) cancel := runServer(ctx, t)
clientPool := getPool(ctx, t, key) clientPool := getPool(ctx, t, key)
cnrID := createContainer(ctx, t, clientPool, "test-container") cnrID := createContainer(ctx, t, clientPool, containerName)
t.Run("rest put object "+version, func(t *testing.T) { restObjectPut(ctx, t, clientPool, cnrID) }) t.Run("rest put object "+version, func(t *testing.T) { restObjectPut(ctx, t, clientPool, cnrID) })
t.Run("rest get object "+version, func(t *testing.T) { restObjectGet(ctx, t, clientPool, cnrID) }) t.Run("rest get object "+version, func(t *testing.T) { restObjectGet(ctx, t, clientPool, cnrID) })
@ -82,6 +83,7 @@ func TestIntegration(t *testing.T) {
t.Run("rest delete container"+version, func(t *testing.T) { restContainerDelete(ctx, t, clientPool) }) t.Run("rest delete container"+version, func(t *testing.T) { restContainerDelete(ctx, t, clientPool) })
t.Run("rest put container eacl "+version, func(t *testing.T) { restContainerEACLPut(ctx, t, clientPool) }) t.Run("rest put container eacl "+version, func(t *testing.T) { restContainerEACLPut(ctx, t, clientPool) })
t.Run("rest get container eacl "+version, func(t *testing.T) { restContainerEACLGet(ctx, t, clientPool) }) t.Run("rest get container eacl "+version, func(t *testing.T) { restContainerEACLGet(ctx, t, clientPool) })
t.Run("rest list containers "+version, func(t *testing.T) { restContainerList(ctx, t, clientPool, cnrID) })
cancel() cancel()
err = aioContainer.Terminate(ctx) err = aioContainer.Terminate(ctx)
@ -407,6 +409,35 @@ func restContainerEACLGet(ctx context.Context, t *testing.T, clientPool *pool.Po
require.True(t, eacl.EqualTables(*expectedTable, *actualTable)) require.True(t, eacl.EqualTables(*expectedTable, *actualTable))
} }
func restContainerList(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID) {
var prm pool.PrmContainerList
prm.SetOwnerID(*p.OwnerID())
ids, err := p.ListContainers(ctx, prm)
require.NoError(t, err)
httpClient := defaultHTTPClient()
query := make(url.Values)
query.Add("ownerId", p.OwnerID().String())
request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers?"+query.Encode(), nil)
require.NoError(t, err)
request = request.WithContext(ctx)
list := &models.ContainerList{}
doRequest(t, httpClient, request, http.StatusOK, list)
require.Equal(t, len(ids), int(*list.Size))
expected := &models.ContainerBaseInfo{
ContainerID: handlers.NewString(cnrID.String()),
Name: containerName,
}
require.Contains(t, list.Containers, expected)
}
func makeAuthContainerTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bearer, httpClient *http.Client) *handlers.BearerToken { func makeAuthContainerTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bearer, httpClient *http.Client) *handlers.BearerToken {
return makeAuthTokenRequest(ctx, t, bearer, httpClient, models.TokenTypeContainer) return makeAuthTokenRequest(ctx, t, bearer, httpClient, models.TokenTypeContainer)
} }

View file

@ -0,0 +1,74 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ContainerBaseInfo container base info
//
// swagger:model ContainerBaseInfo
type ContainerBaseInfo struct {
// container Id
// Required: true
ContainerID *string `json:"containerId"`
// name
Name string `json:"name,omitempty"`
}
// Validate validates this container base info
func (m *ContainerBaseInfo) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateContainerID(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ContainerBaseInfo) validateContainerID(formats strfmt.Registry) error {
if err := validate.Required("containerId", "body", m.ContainerID); err != nil {
return err
}
return nil
}
// ContextValidate validates this container base info based on context it is used
func (m *ContainerBaseInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *ContainerBaseInfo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ContainerBaseInfo) UnmarshalBinary(b []byte) error {
var res ContainerBaseInfo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -0,0 +1,136 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// ContainerList container list
//
// swagger:model ContainerList
type ContainerList struct {
// containers
// Required: true
Containers []*ContainerBaseInfo `json:"containers"`
// size
// Required: true
Size *int64 `json:"size"`
}
// Validate validates this container list
func (m *ContainerList) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateContainers(formats); err != nil {
res = append(res, err)
}
if err := m.validateSize(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ContainerList) validateContainers(formats strfmt.Registry) error {
if err := validate.Required("containers", "body", m.Containers); err != nil {
return err
}
for i := 0; i < len(m.Containers); i++ {
if swag.IsZero(m.Containers[i]) { // not required
continue
}
if m.Containers[i] != nil {
if err := m.Containers[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("containers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("containers" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *ContainerList) validateSize(formats strfmt.Registry) error {
if err := validate.Required("size", "body", m.Size); err != nil {
return err
}
return nil
}
// ContextValidate validate this container list based on the context it is used
func (m *ContainerList) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateContainers(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ContainerList) contextValidateContainers(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Containers); i++ {
if m.Containers[i] != nil {
if err := m.Containers[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("containers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("containers" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ContainerList) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ContainerList) UnmarshalBinary(b []byte) error {
var res ContainerList
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -94,10 +94,60 @@ func init() {
} }
}, },
"/containers": { "/containers": {
"get": {
"security": [],
"summary": "Get list of containers",
"operationId": "listContainers",
"parameters": [
{
"type": "string",
"description": "Base58 encoded owner id",
"name": "ownerId",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 0,
"description": "The number of containers to skip before starting to collect the result set.",
"name": "offset",
"in": "query"
},
{
"maximum": 10000,
"minimum": 1,
"type": "integer",
"default": 100,
"description": "The numbers of containers to return.",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "Containers info",
"schema": {
"$ref": "#/definitions/ContainerList"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"put": { "put": {
"summary": "Create new container in NeoFS", "summary": "Create new container in NeoFS",
"operationId": "putContainer", "operationId": "putContainer",
"parameters": [ "parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
},
{ {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
@ -159,15 +209,7 @@ func init() {
} }
} }
} }
},
"parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
} }
]
}, },
"/containers/{containerId}": { "/containers/{containerId}": {
"get": { "get": {
@ -433,6 +475,20 @@ func init() {
} }
} }
}, },
"ContainerBaseInfo": {
"type": "object",
"required": [
"containerId"
],
"properties": {
"containerId": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"ContainerInfo": { "ContainerInfo": {
"type": "object", "type": "object",
"required": [ "required": [
@ -484,6 +540,24 @@ func init() {
"version": "2.11" "version": "2.11"
} }
}, },
"ContainerList": {
"type": "object",
"required": [
"size",
"containers"
],
"properties": {
"containers": {
"type": "array",
"items": {
"$ref": "#/definitions/ContainerBaseInfo"
}
},
"size": {
"type": "integer"
}
}
},
"Eacl": { "Eacl": {
"type": "object", "type": "object",
"required": [ "required": [
@ -853,10 +927,69 @@ func init() {
} }
}, },
"/containers": { "/containers": {
"get": {
"security": [],
"summary": "Get list of containers",
"operationId": "listContainers",
"parameters": [
{
"type": "string",
"description": "Base58 encoded owner id",
"name": "ownerId",
"in": "query",
"required": true
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "The number of containers to skip before starting to collect the result set.",
"name": "offset",
"in": "query"
},
{
"maximum": 10000,
"minimum": 1,
"type": "integer",
"default": 100,
"description": "The numbers of containers to return.",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "Containers info",
"schema": {
"$ref": "#/definitions/ContainerList"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"put": { "put": {
"summary": "Create new container in NeoFS", "summary": "Create new container in NeoFS",
"operationId": "putContainer", "operationId": "putContainer",
"parameters": [ "parameters": [
{
"type": "string",
"description": "Base64 encoded signature for bearer token",
"name": "X-Neofs-Token-Signature",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Hex encoded the public part of the key that signed the bearer token",
"name": "X-Neofs-Token-Signature-Key",
"in": "header",
"required": true
},
{ {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
@ -918,23 +1051,7 @@ func init() {
} }
} }
} }
},
"parameters": [
{
"type": "string",
"description": "Base64 encoded signature for bearer token",
"name": "X-Neofs-Token-Signature",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Hex encoded the public part of the key that signed the bearer token",
"name": "X-Neofs-Token-Signature-Key",
"in": "header",
"required": true
} }
]
}, },
"/containers/{containerId}": { "/containers/{containerId}": {
"get": { "get": {
@ -1244,6 +1361,20 @@ func init() {
} }
} }
}, },
"ContainerBaseInfo": {
"type": "object",
"required": [
"containerId"
],
"properties": {
"containerId": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"ContainerInfo": { "ContainerInfo": {
"type": "object", "type": "object",
"required": [ "required": [
@ -1295,6 +1426,24 @@ func init() {
"version": "2.11" "version": "2.11"
} }
}, },
"ContainerList": {
"type": "object",
"required": [
"size",
"containers"
],
"properties": {
"containers": {
"type": "array",
"items": {
"$ref": "#/definitions/ContainerBaseInfo"
}
},
"size": {
"type": "integer"
}
}
},
"Eacl": { "Eacl": {
"type": "object", "type": "object",
"required": [ "required": [

View file

@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// ListContainersHandlerFunc turns a function with the right signature into a list containers handler
type ListContainersHandlerFunc func(ListContainersParams) middleware.Responder
// Handle executing the request and returning a response
func (fn ListContainersHandlerFunc) Handle(params ListContainersParams) middleware.Responder {
return fn(params)
}
// ListContainersHandler interface for that can handle valid list containers params
type ListContainersHandler interface {
Handle(ListContainersParams) middleware.Responder
}
// NewListContainers creates a new http.Handler for the list containers operation
func NewListContainers(ctx *middleware.Context, handler ListContainersHandler) *ListContainers {
return &ListContainers{Context: ctx, Handler: handler}
}
/* ListContainers swagger:route GET /containers listContainers
Get list of containers
*/
type ListContainers struct {
Context *middleware.Context
Handler ListContainersHandler
}
func (o *ListContainers) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewListContainersParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,196 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// NewListContainersParams creates a new ListContainersParams object
// with the default values initialized.
func NewListContainersParams() ListContainersParams {
var (
// initialize parameters with default values
limitDefault = int64(100)
offsetDefault = int64(0)
)
return ListContainersParams{
Limit: &limitDefault,
Offset: &offsetDefault,
}
}
// ListContainersParams contains all the bound params for the list containers operation
// typically these are obtained from a http.Request
//
// swagger:parameters listContainers
type ListContainersParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*The numbers of containers to return.
Maximum: 10000
Minimum: 1
In: query
Default: 100
*/
Limit *int64
/*The number of containers to skip before starting to collect the result set.
Minimum: 0
In: query
Default: 0
*/
Offset *int64
/*Base58 encoded owner id
Required: true
In: query
*/
OwnerID string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewListContainersParams() beforehand.
func (o *ListContainersParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
qLimit, qhkLimit, _ := qs.GetOK("limit")
if err := o.bindLimit(qLimit, qhkLimit, route.Formats); err != nil {
res = append(res, err)
}
qOffset, qhkOffset, _ := qs.GetOK("offset")
if err := o.bindOffset(qOffset, qhkOffset, route.Formats); err != nil {
res = append(res, err)
}
qOwnerID, qhkOwnerID, _ := qs.GetOK("ownerId")
if err := o.bindOwnerID(qOwnerID, qhkOwnerID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindLimit binds and validates parameter Limit from query.
func (o *ListContainersParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewListContainersParams()
return nil
}
value, err := swag.ConvertInt64(raw)
if err != nil {
return errors.InvalidType("limit", "query", "int64", raw)
}
o.Limit = &value
if err := o.validateLimit(formats); err != nil {
return err
}
return nil
}
// validateLimit carries on validations for parameter Limit
func (o *ListContainersParams) validateLimit(formats strfmt.Registry) error {
if err := validate.MinimumInt("limit", "query", *o.Limit, 1, false); err != nil {
return err
}
if err := validate.MaximumInt("limit", "query", *o.Limit, 10000, false); err != nil {
return err
}
return nil
}
// bindOffset binds and validates parameter Offset from query.
func (o *ListContainersParams) bindOffset(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
// Default values have been previously initialized by NewListContainersParams()
return nil
}
value, err := swag.ConvertInt64(raw)
if err != nil {
return errors.InvalidType("offset", "query", "int64", raw)
}
o.Offset = &value
if err := o.validateOffset(formats); err != nil {
return err
}
return nil
}
// validateOffset carries on validations for parameter Offset
func (o *ListContainersParams) validateOffset(formats strfmt.Registry) error {
if err := validate.MinimumInt("offset", "query", *o.Offset, 0, false); err != nil {
return err
}
return nil
}
// bindOwnerID binds and validates parameter OwnerID from query.
func (o *ListContainersParams) bindOwnerID(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("ownerId", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("ownerId", "query", raw); err != nil {
return err
}
o.OwnerID = raw
return nil
}

View file

@ -0,0 +1,100 @@
// Code generated by go-swagger; DO NOT EDIT.
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// ListContainersOKCode is the HTTP code returned for type ListContainersOK
const ListContainersOKCode int = 200
/*ListContainersOK Containers info
swagger:response listContainersOK
*/
type ListContainersOK struct {
/*
In: Body
*/
Payload *models.ContainerList `json:"body,omitempty"`
}
// NewListContainersOK creates ListContainersOK with default headers values
func NewListContainersOK() *ListContainersOK {
return &ListContainersOK{}
}
// WithPayload adds the payload to the list containers o k response
func (o *ListContainersOK) WithPayload(payload *models.ContainerList) *ListContainersOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the list containers o k response
func (o *ListContainersOK) SetPayload(payload *models.ContainerList) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ListContainersOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
// ListContainersBadRequestCode is the HTTP code returned for type ListContainersBadRequest
const ListContainersBadRequestCode int = 400
/*ListContainersBadRequest Bad request
swagger:response listContainersBadRequest
*/
type ListContainersBadRequest struct {
/*
In: Body
*/
Payload models.Error `json:"body,omitempty"`
}
// NewListContainersBadRequest creates ListContainersBadRequest with default headers values
func NewListContainersBadRequest() *ListContainersBadRequest {
return &ListContainersBadRequest{}
}
// WithPayload adds the payload to the list containers bad request response
func (o *ListContainersBadRequest) WithPayload(payload models.Error) *ListContainersBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the list containers bad request response
func (o *ListContainersBadRequest) SetPayload(payload models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *ListContainersBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(400)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}

View file

@ -59,6 +59,9 @@ func NewNeofsRestGwAPI(spec *loads.Document) *NeofsRestGwAPI {
GetObjectInfoHandler: GetObjectInfoHandlerFunc(func(params GetObjectInfoParams, principal *models.Principal) middleware.Responder { GetObjectInfoHandler: GetObjectInfoHandlerFunc(func(params GetObjectInfoParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation GetObjectInfo has not yet been implemented") return middleware.NotImplemented("operation GetObjectInfo has not yet been implemented")
}), }),
ListContainersHandler: ListContainersHandlerFunc(func(params ListContainersParams) middleware.Responder {
return middleware.NotImplemented("operation ListContainers has not yet been implemented")
}),
PutContainerHandler: PutContainerHandlerFunc(func(params PutContainerParams, principal *models.Principal) middleware.Responder { PutContainerHandler: PutContainerHandlerFunc(func(params PutContainerParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation PutContainer has not yet been implemented") return middleware.NotImplemented("operation PutContainer has not yet been implemented")
}), }),
@ -128,6 +131,8 @@ type NeofsRestGwAPI struct {
GetContainerEACLHandler GetContainerEACLHandler GetContainerEACLHandler GetContainerEACLHandler
// GetObjectInfoHandler sets the operation handler for the get object info operation // GetObjectInfoHandler sets the operation handler for the get object info operation
GetObjectInfoHandler GetObjectInfoHandler GetObjectInfoHandler GetObjectInfoHandler
// ListContainersHandler sets the operation handler for the list containers operation
ListContainersHandler ListContainersHandler
// PutContainerHandler sets the operation handler for the put container operation // PutContainerHandler sets the operation handler for the put container operation
PutContainerHandler PutContainerHandler PutContainerHandler PutContainerHandler
// PutContainerEACLHandler sets the operation handler for the put container e ACL operation // PutContainerEACLHandler sets the operation handler for the put container e ACL operation
@ -230,6 +235,9 @@ func (o *NeofsRestGwAPI) Validate() error {
if o.GetObjectInfoHandler == nil { if o.GetObjectInfoHandler == nil {
unregistered = append(unregistered, "GetObjectInfoHandler") unregistered = append(unregistered, "GetObjectInfoHandler")
} }
if o.ListContainersHandler == nil {
unregistered = append(unregistered, "ListContainersHandler")
}
if o.PutContainerHandler == nil { if o.PutContainerHandler == nil {
unregistered = append(unregistered, "PutContainerHandler") unregistered = append(unregistered, "PutContainerHandler")
} }
@ -358,6 +366,10 @@ func (o *NeofsRestGwAPI) initHandlerCache() {
o.handlers["GET"] = make(map[string]http.Handler) o.handlers["GET"] = make(map[string]http.Handler)
} }
o.handlers["GET"]["/objects/{containerId}/{objectId}"] = NewGetObjectInfo(o.context, o.GetObjectInfoHandler) o.handlers["GET"]["/objects/{containerId}/{objectId}"] = NewGetObjectInfo(o.context, o.GetObjectInfoHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/containers"] = NewListContainers(o.context, o.ListContainersHandler)
if o.handlers["PUT"] == nil { if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler) o.handlers["PUT"] = make(map[string]http.Handler)
} }

View file

@ -71,6 +71,7 @@ func (a *API) Configure(api *operations.NeofsRestGwAPI) http.Handler {
api.DeleteContainerHandler = operations.DeleteContainerHandlerFunc(a.DeleteContainer) api.DeleteContainerHandler = operations.DeleteContainerHandlerFunc(a.DeleteContainer)
api.PutContainerEACLHandler = operations.PutContainerEACLHandlerFunc(a.PutContainerEACL) api.PutContainerEACLHandler = operations.PutContainerEACLHandlerFunc(a.PutContainerEACL)
api.GetContainerEACLHandler = operations.GetContainerEACLHandlerFunc(a.GetContainerEACL) api.GetContainerEACLHandler = operations.GetContainerEACLHandlerFunc(a.GetContainerEACL)
api.ListContainersHandler = operations.ListContainersHandlerFunc(a.ListContainer)
api.BearerAuthAuth = func(s string) (*models.Principal, error) { api.BearerAuthAuth = func(s string) (*models.Principal, error) {
if !strings.HasPrefix(s, BearerPrefix) { if !strings.HasPrefix(s, BearerPrefix) {

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -124,6 +125,57 @@ func (a *API) GetContainerEACL(params operations.GetContainerEACLParams) middlew
return operations.NewGetContainerEACLOK().WithPayload(resp) return operations.NewGetContainerEACLOK().WithPayload(resp)
} }
// ListContainer handler that returns containers.
func (a *API) ListContainer(params operations.ListContainersParams) middleware.Responder {
ctx := params.HTTPRequest.Context()
var ownerID owner.ID
if err := ownerID.Parse(params.OwnerID); err != nil {
a.log.Error("invalid owner id", zap.Error(err))
return operations.NewListContainersBadRequest().WithPayload("invalid owner id")
}
var prm pool.PrmContainerList
prm.SetOwnerID(ownerID)
ids, err := a.pool.ListContainers(ctx, prm)
if err != nil {
a.log.Error("list containers", zap.Error(err))
return operations.NewListContainersBadRequest().WithPayload("failed to get containers")
}
offset := int(*params.Offset)
size := int(*params.Limit)
if offset > len(ids)-1 {
res := &models.ContainerList{
Size: NewInteger(0),
Containers: []*models.ContainerBaseInfo{},
}
return operations.NewListContainersOK().WithPayload(res)
}
if offset+size > len(ids) {
size = len(ids) - offset
}
res := &models.ContainerList{
Size: NewInteger(int64(size)),
Containers: make([]*models.ContainerBaseInfo, 0, size),
}
for _, id := range ids[offset : offset+size] {
baseInfo, err := getContainerBaseInfo(ctx, a.pool, id)
if err != nil {
a.log.Error("get container", zap.String("cid", id.String()), zap.Error(err))
return operations.NewListContainersBadRequest().WithPayload("failed to get container")
}
res.Containers = append(res.Containers, baseInfo)
}
return operations.NewListContainersOK().WithPayload(res)
}
// DeleteContainer handler that returns container info. // DeleteContainer handler that returns container info.
func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal *models.Principal) middleware.Responder { func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal *models.Principal) middleware.Responder {
bt := &BearerToken{ bt := &BearerToken{
@ -155,6 +207,26 @@ func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal
return operations.NewDeleteContainerNoContent() return operations.NewDeleteContainerNoContent()
} }
func getContainerBaseInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID) (*models.ContainerBaseInfo, error) {
var prm pool.PrmContainerGet
prm.SetContainerID(cnrID)
cnr, err := p.GetContainer(ctx, prm)
if err != nil {
return nil, err
}
baseInfo := &models.ContainerBaseInfo{ContainerID: NewString(cnrID.String())}
for _, attr := range cnr.Attributes() {
if attr.Key() == container.AttributeName {
baseInfo.Name = attr.Value()
}
}
return baseInfo, nil
}
func prepareUserAttributes(header http.Header) map[string]string { func prepareUserAttributes(header http.Header) map[string]string {
filtered := filterHeaders(header) filtered := filterHeaders(header)
delete(filtered, container.AttributeName) delete(filtered, container.AttributeName)

View file

@ -219,6 +219,10 @@ func NewString(val string) *string {
return &val return &val
} }
func NewInteger(val int64) *int64 {
return &val
}
func NewError(err error) models.Error { func NewError(err error) models.Error {
return models.Error(err.Error()) return models.Error(err.Error())
} }

View file

@ -168,13 +168,12 @@ paths:
$ref: '#/definitions/Error' $ref: '#/definitions/Error'
/containers: /containers:
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
put: put:
operationId: putContainer operationId: putContainer
summary: Create new container in NeoFS summary: Create new container in NeoFS
parameters: parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
- in: query - in: query
name: skip-native-name name: skip-native-name
description: Provide this parameter to skip registration container name in NNS service description: Provide this parameter to skip registration container name in NNS service
@ -215,6 +214,38 @@ paths:
description: Bad request description: Bad request
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Error'
get:
operationId: listContainers
summary: Get list of containers
security: [ ]
parameters:
- in: query
name: ownerId
required: true
type: string
description: Base58 encoded owner id
- in: query
name: offset
type: integer
default: 0
minimum: 0
description: The number of containers to skip before starting to collect the result set.
- in: query
name: limit
type: integer
default: 100
minimum: 1
maximum: 10000
description: The numbers of containers to return.
responses:
200:
description: Containers info
schema:
$ref: '#/definitions/ContainerList'
400:
description: Bad request
schema:
$ref: '#/definitions/Error'
/containers/{containerId}: /containers/{containerId}:
parameters: parameters:
@ -457,6 +488,27 @@ definitions:
value: "1648810072" value: "1648810072"
- key: Name - key: Name
value: container value: container
ContainerList:
type: object
properties:
size:
type: integer
containers:
type: array
items:
$ref: '#/definitions/ContainerBaseInfo'
required:
- size
- containers
ContainerBaseInfo:
type: object
properties:
containerId:
type: string
name:
type: string
required:
- containerId
ObjectInfo: ObjectInfo:
type: object type: object
properties: properties: