[#1] Add route to delete container

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-04-13 11:41:04 +03:00 committed by Alex Vanin
parent 066656ac48
commit 63fdb08f14
12 changed files with 637 additions and 96 deletions

View file

@ -13,6 +13,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"testing" "testing"
"time" "time"
@ -55,7 +56,13 @@ const (
func TestIntegration(t *testing.T) { func TestIntegration(t *testing.T) {
rootCtx := context.Background() rootCtx := context.Background()
aioImage := "nspccdev/neofs-aio-testcontainer:" aioImage := "nspccdev/neofs-aio-testcontainer:"
versions := []string{"0.24.0", "0.25.1", "0.27.5", "latest"} versions := []string{
"0.24.0",
"0.25.1",
"0.26.1",
"0.27.5",
"latest",
}
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey) key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
require.NoError(t, err) require.NoError(t, err)
@ -65,12 +72,13 @@ 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)
CID, err := createContainer(ctx, t, clientPool) cnrID := createContainer(ctx, t, clientPool, "test-container")
require.NoError(t, err, version)
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, CID) })
t.Run("rest put container"+version, func(t *testing.T) { restContainerPut(ctx, t, clientPool) }) t.Run("rest put container"+version, func(t *testing.T) { restContainerPut(ctx, t, clientPool) })
t.Run("rest get container"+version, func(t *testing.T) { restContainerGet(ctx, t, clientPool, CID) }) t.Run("rest get container"+version, func(t *testing.T) { restContainerGet(ctx, t, clientPool, cnrID) })
t.Run("rest delete container"+version, func(t *testing.T) { restContainerDelete(ctx, t, clientPool) })
cancel() cancel()
err = aioContainer.Terminate(ctx) err = aioContainer.Terminate(ctx)
@ -132,6 +140,7 @@ func getDefaultConfig() *viper.Viper {
v.SetDefault(cfgPeers+".0.weight", 1) v.SetDefault(cfgPeers+".0.weight", 1)
v.SetDefault(cfgPeers+".0.priority", 1) v.SetDefault(cfgPeers+".0.priority", 1)
v.SetDefault(restapi.FlagListenAddress, testListenAddress) v.SetDefault(restapi.FlagListenAddress, testListenAddress)
v.SetDefault(restapi.FlagWriteTimeout, 60*time.Second)
return v return v
} }
@ -292,8 +301,98 @@ func restContainerGet(ctx context.Context, t *testing.T, clientPool *pool.Pool,
err = json.NewDecoder(resp.Body).Decode(cnrInfo) err = json.NewDecoder(resp.Body).Decode(cnrInfo)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cnrID.String(), cnrInfo.ContainerID) require.Equal(t, cnrID.String(), *cnrInfo.ContainerID)
require.Equal(t, clientPool.OwnerID().String(), cnrInfo.OwnerID) require.Equal(t, clientPool.OwnerID().String(), *cnrInfo.OwnerID)
}
func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Pool) {
cnrID := createContainer(ctx, t, clientPool, "for-delete")
bearer := &models.Bearer{
Container: &models.Rule{
Verb: models.NewVerb(models.VerbDELETE),
},
}
httpClient := &http.Client{Timeout: 60 * time.Second}
bearerToken := makeAuthContainerTokenRequest(ctx, t, bearer, httpClient)
request, err := http.NewRequest(http.MethodDelete, testHost+"/v1/containers/"+cnrID.String(), nil)
require.NoError(t, err)
request = request.WithContext(ctx)
prepareBearerHeaders(request.Header, bearerToken)
resp, err := httpClient.Do(request)
require.NoError(t, err)
defer func() {
err := resp.Body.Close()
require.NoError(t, err)
}()
if resp.StatusCode != http.StatusNoContent {
fmt.Println("resp")
}
require.Equal(t, http.StatusNoContent, resp.StatusCode)
var prm pool.PrmContainerGet
prm.SetContainerID(*cnrID)
_, err = clientPool.GetContainer(ctx, prm)
require.Error(t, err)
require.Contains(t, err.Error(), "not found")
}
func makeAuthContainerTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bearer, httpClient *http.Client) *handlers.BearerToken {
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
require.NoError(t, err)
hexPubKey := hex.EncodeToString(key.PublicKey().Bytes())
data, err := json.Marshal(bearer)
require.NoError(t, err)
request, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data))
require.NoError(t, err)
request = request.WithContext(ctx)
request.Header.Add("Content-Type", "application/json")
request.Header.Add(XNeofsTokenScope, string(models.TokenTypeContainer))
request.Header.Add(XNeofsTokenSignatureKey, hexPubKey)
resp, err := httpClient.Do(request)
require.NoError(t, err)
defer func() {
err := resp.Body.Close()
require.NoError(t, err)
}()
rr, err := io.ReadAll(resp.Body)
require.NoError(t, err)
if resp.StatusCode != http.StatusOK {
fmt.Println("auth response", string(rr))
}
require.Equal(t, http.StatusOK, resp.StatusCode)
stokenResp := &models.TokenResponse{}
err = json.Unmarshal(rr, stokenResp)
require.NoError(t, err)
require.Equal(t, *stokenResp.Type, models.TokenTypeContainer)
binaryData, err := base64.StdEncoding.DecodeString(*stokenResp.Token)
require.NoError(t, err)
signatureData := signData(t, key, binaryData)
signature := base64.StdEncoding.EncodeToString(signatureData)
bt := handlers.BearerToken{
Token: *stokenResp.Token,
Signature: signature,
Key: hexPubKey,
}
fmt.Printf("container token:\n%+v\n", bt)
return &bt
} }
func signData(t *testing.T, key *keys.PrivateKey, data []byte) []byte { func signData(t *testing.T, key *keys.PrivateKey, data []byte) []byte {
@ -304,48 +403,15 @@ func signData(t *testing.T, key *keys.PrivateKey, data []byte) []byte {
} }
func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) { func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) {
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey) bearer := &models.Bearer{
require.NoError(t, err)
b := models.Bearer{
Container: &models.Rule{ Container: &models.Rule{
Verb: models.NewVerb(models.VerbPUT), Verb: models.NewVerb(models.VerbPUT),
}, },
} }
data, err := json.Marshal(&b) httpClient := &http.Client{Timeout: 30 * time.Second}
require.NoError(t, err)
request0, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data)) bearerToken := makeAuthContainerTokenRequest(ctx, t, bearer, httpClient)
require.NoError(t, err)
request0.Header.Add("Content-Type", "application/json")
request0.Header.Add(XNeofsTokenScope, "container")
request0.Header.Add(XNeofsTokenSignatureKey, hex.EncodeToString(key.PublicKey().Bytes()))
httpClient := http.Client{
Timeout: 30 * time.Second,
}
resp, err := httpClient.Do(request0)
require.NoError(t, err)
defer resp.Body.Close()
rr, err := io.ReadAll(resp.Body)
require.NoError(t, err)
fmt.Println(string(rr))
require.Equal(t, http.StatusOK, resp.StatusCode)
stokenResp := &models.TokenResponse{}
err = json.Unmarshal(rr, stokenResp)
require.NoError(t, err)
require.Equal(t, *stokenResp.Type, models.TokenTypeContainer)
bearerBase64 := stokenResp.Token
binaryData, err := base64.StdEncoding.DecodeString(*bearerBase64)
require.NoError(t, err)
signatureData := signData(t, key, binaryData)
attrKey, attrValue := "User-Attribute", "user value" attrKey, attrValue := "User-Attribute", "user value"
@ -360,15 +426,18 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool)
body, err := json.Marshal(&req) body, err := json.Marshal(&req)
require.NoError(t, err) require.NoError(t, err)
fmt.Println(base64.StdEncoding.EncodeToString(signatureData)) reqURL, err := url.Parse(testHost + "/v1/containers")
fmt.Println(hex.EncodeToString(key.PublicKey().Bytes())) require.NoError(t, err)
request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers", bytes.NewReader(body)) query := reqURL.Query()
query.Add("skip-native-name", "true")
reqURL.RawQuery = query.Encode()
request, err := http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
request.Header.Add("Content-Type", "application/json") request.Header.Add("Content-Type", "application/json")
request.Header.Add(XNeofsTokenSignature, base64.StdEncoding.EncodeToString(signatureData)) prepareBearerHeaders(request.Header, bearerToken)
request.Header.Add("Authorization", "Bearer "+*bearerBase64)
request.Header.Add(XNeofsTokenSignatureKey, hex.EncodeToString(key.PublicKey().Bytes()))
request.Header.Add("X-Attribute-"+attrKey, attrValue) request.Header.Add("X-Attribute-"+attrKey, attrValue)
resp2, err := httpClient.Do(request) resp2, err := httpClient.Do(request)
@ -406,14 +475,20 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool)
} }
} }
func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool) (*cid.ID, error) { func prepareBearerHeaders(header http.Header, bearerToken *handlers.BearerToken) {
header.Add(XNeofsTokenSignature, bearerToken.Signature)
header.Add("Authorization", "Bearer "+bearerToken.Token)
header.Add(XNeofsTokenSignatureKey, bearerToken.Key)
}
func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool, name string) *cid.ID {
pp, err := policy.Parse("REP 1") pp, err := policy.Parse("REP 1")
require.NoError(t, err) require.NoError(t, err)
cnr := container.New( cnr := container.New(
container.WithPolicy(pp), container.WithPolicy(pp),
container.WithCustomBasicACL(0x0FFFFFFF), container.WithCustomBasicACL(0x0FFFFFFF),
container.WithAttribute(container.AttributeName, "friendlyName"), container.WithAttribute(container.AttributeName, name),
container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10))) container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10)))
cnr.SetOwnerID(clientPool.OwnerID()) cnr.SetOwnerID(clientPool.OwnerID())
@ -426,12 +501,9 @@ func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool) (
prm.SetWaitParams(waitPrm) prm.SetWaitParams(waitPrm)
CID, err := clientPool.PutContainer(ctx, prm) CID, err := clientPool.PutContainer(ctx, prm)
if err != nil { require.NoError(t, err)
return nil, err
}
fmt.Println(CID.String())
return CID, err return CID
} }
func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) { func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) {

View file

@ -98,6 +98,13 @@ func init() {
"summary": "Create new container in NeoFS", "summary": "Create new container in NeoFS",
"operationId": "putContainer", "operationId": "putContainer",
"parameters": [ "parameters": [
{
"type": "boolean",
"default": false,
"description": "Provide this parameter to skip registration container name in NNS service",
"name": "skip-native-name",
"in": "query"
},
{ {
"description": "Container info", "description": "Container info",
"name": "container", "name": "container",
@ -167,15 +174,6 @@ func init() {
"security": [], "security": [],
"summary": "Get container by id", "summary": "Get container by id",
"operationId": "getContainer", "operationId": "getContainer",
"parameters": [
{
"type": "string",
"description": "Base58 encoded container id",
"name": "containerId",
"in": "path",
"required": true
}
],
"responses": { "responses": {
"200": { "200": {
"description": "Container info", "description": "Container info",
@ -190,7 +188,39 @@ func init() {
} }
} }
} }
} },
"delete": {
"summary": "Delete container by id",
"operationId": "deleteContainer",
"parameters": [
{
"$ref": "#/parameters/signatureParam"
},
{
"$ref": "#/parameters/signatureKeyParam"
}
],
"responses": {
"204": {
"description": "Successul deletion"
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"parameters": [
{
"type": "string",
"description": "Base58 encoded container id",
"name": "containerId",
"in": "path",
"required": true
}
]
}, },
"/objects": { "/objects": {
"put": { "put": {
@ -745,6 +775,13 @@ func init() {
"summary": "Create new container in NeoFS", "summary": "Create new container in NeoFS",
"operationId": "putContainer", "operationId": "putContainer",
"parameters": [ "parameters": [
{
"type": "boolean",
"default": false,
"description": "Provide this parameter to skip registration container name in NNS service",
"name": "skip-native-name",
"in": "query"
},
{ {
"description": "Container info", "description": "Container info",
"name": "container", "name": "container",
@ -822,15 +859,6 @@ func init() {
"security": [], "security": [],
"summary": "Get container by id", "summary": "Get container by id",
"operationId": "getContainer", "operationId": "getContainer",
"parameters": [
{
"type": "string",
"description": "Base58 encoded container id",
"name": "containerId",
"in": "path",
"required": true
}
],
"responses": { "responses": {
"200": { "200": {
"description": "Container info", "description": "Container info",
@ -845,7 +873,47 @@ func init() {
} }
} }
} }
} },
"delete": {
"summary": "Delete container by id",
"operationId": "deleteContainer",
"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
}
],
"responses": {
"204": {
"description": "Successul deletion"
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"parameters": [
{
"type": "string",
"description": "Base58 encoded container id",
"name": "containerId",
"in": "path",
"required": true
}
]
}, },
"/objects": { "/objects": {
"put": { "put": {

View file

@ -0,0 +1,71 @@
// 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"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
)
// DeleteContainerHandlerFunc turns a function with the right signature into a delete container handler
type DeleteContainerHandlerFunc func(DeleteContainerParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn DeleteContainerHandlerFunc) Handle(params DeleteContainerParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// DeleteContainerHandler interface for that can handle valid delete container params
type DeleteContainerHandler interface {
Handle(DeleteContainerParams, *models.Principal) middleware.Responder
}
// NewDeleteContainer creates a new http.Handler for the delete container operation
func NewDeleteContainer(ctx *middleware.Context, handler DeleteContainerHandler) *DeleteContainer {
return &DeleteContainer{Context: ctx, Handler: handler}
}
/* DeleteContainer swagger:route DELETE /containers/{containerId} deleteContainer
Delete container by id
*/
type DeleteContainer struct {
Context *middleware.Context
Handler DeleteContainerHandler
}
func (o *DeleteContainer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewDeleteContainerParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
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, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View file

@ -0,0 +1,130 @@
// 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/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewDeleteContainerParams creates a new DeleteContainerParams object
//
// There are no default values defined in the spec.
func NewDeleteContainerParams() DeleteContainerParams {
return DeleteContainerParams{}
}
// DeleteContainerParams contains all the bound params for the delete container operation
// typically these are obtained from a http.Request
//
// swagger:parameters deleteContainer
type DeleteContainerParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*Base64 encoded signature for bearer token
Required: true
In: header
*/
XNeofsTokenSignature string
/*Hex encoded the public part of the key that signed the bearer token
Required: true
In: header
*/
XNeofsTokenSignatureKey string
/*Base58 encoded container id
Required: true
In: path
*/
ContainerID 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 NewDeleteContainerParams() beforehand.
func (o *DeleteContainerParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if err := o.bindXNeofsTokenSignature(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature")], true, route.Formats); err != nil {
res = append(res, err)
}
if err := o.bindXNeofsTokenSignatureKey(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature-Key")], true, route.Formats); err != nil {
res = append(res, err)
}
rContainerID, rhkContainerID, _ := route.Params.GetOK("containerId")
if err := o.bindContainerID(rContainerID, rhkContainerID, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindXNeofsTokenSignature binds and validates parameter XNeofsTokenSignature from header.
func (o *DeleteContainerParams) bindXNeofsTokenSignature(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-Signature", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-Signature", "header", raw); err != nil {
return err
}
o.XNeofsTokenSignature = raw
return nil
}
// bindXNeofsTokenSignatureKey binds and validates parameter XNeofsTokenSignatureKey from header.
func (o *DeleteContainerParams) bindXNeofsTokenSignatureKey(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("X-Neofs-Token-Signature-Key", "header", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
if err := validate.RequiredString("X-Neofs-Token-Signature-Key", "header", raw); err != nil {
return err
}
o.XNeofsTokenSignatureKey = raw
return nil
}
// bindContainerID binds and validates parameter ContainerID from path.
func (o *DeleteContainerParams) bindContainerID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.ContainerID = raw
return nil
}

View file

@ -0,0 +1,80 @@
// 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"
)
// DeleteContainerNoContentCode is the HTTP code returned for type DeleteContainerNoContent
const DeleteContainerNoContentCode int = 204
/*DeleteContainerNoContent Successul deletion
swagger:response deleteContainerNoContent
*/
type DeleteContainerNoContent struct {
}
// NewDeleteContainerNoContent creates DeleteContainerNoContent with default headers values
func NewDeleteContainerNoContent() *DeleteContainerNoContent {
return &DeleteContainerNoContent{}
}
// WriteResponse to the client
func (o *DeleteContainerNoContent) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(204)
}
// DeleteContainerBadRequestCode is the HTTP code returned for type DeleteContainerBadRequest
const DeleteContainerBadRequestCode int = 400
/*DeleteContainerBadRequest Bad request
swagger:response deleteContainerBadRequest
*/
type DeleteContainerBadRequest struct {
/*
In: Body
*/
Payload models.Error `json:"body,omitempty"`
}
// NewDeleteContainerBadRequest creates DeleteContainerBadRequest with default headers values
func NewDeleteContainerBadRequest() *DeleteContainerBadRequest {
return &DeleteContainerBadRequest{}
}
// WithPayload adds the payload to the delete container bad request response
func (o *DeleteContainerBadRequest) WithPayload(payload models.Error) *DeleteContainerBadRequest {
o.Payload = payload
return o
}
// SetPayload sets the payload to the delete container bad request response
func (o *DeleteContainerBadRequest) SetPayload(payload models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DeleteContainerBadRequest) 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

@ -47,6 +47,9 @@ func NewNeofsRestGwAPI(spec *loads.Document) *NeofsRestGwAPI {
AuthHandler: AuthHandlerFunc(func(params AuthParams) middleware.Responder { AuthHandler: AuthHandlerFunc(func(params AuthParams) middleware.Responder {
return middleware.NotImplemented("operation Auth has not yet been implemented") return middleware.NotImplemented("operation Auth has not yet been implemented")
}), }),
DeleteContainerHandler: DeleteContainerHandlerFunc(func(params DeleteContainerParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation DeleteContainer has not yet been implemented")
}),
GetContainerHandler: GetContainerHandlerFunc(func(params GetContainerParams) middleware.Responder { GetContainerHandler: GetContainerHandlerFunc(func(params GetContainerParams) middleware.Responder {
return middleware.NotImplemented("operation GetContainer has not yet been implemented") return middleware.NotImplemented("operation GetContainer has not yet been implemented")
}), }),
@ -111,6 +114,8 @@ type NeofsRestGwAPI struct {
// AuthHandler sets the operation handler for the auth operation // AuthHandler sets the operation handler for the auth operation
AuthHandler AuthHandler AuthHandler AuthHandler
// DeleteContainerHandler sets the operation handler for the delete container operation
DeleteContainerHandler DeleteContainerHandler
// GetContainerHandler sets the operation handler for the get container operation // GetContainerHandler sets the operation handler for the get container operation
GetContainerHandler GetContainerHandler GetContainerHandler GetContainerHandler
// GetObjectInfoHandler sets the operation handler for the get object info operation // GetObjectInfoHandler sets the operation handler for the get object info operation
@ -203,6 +208,9 @@ func (o *NeofsRestGwAPI) Validate() error {
if o.AuthHandler == nil { if o.AuthHandler == nil {
unregistered = append(unregistered, "AuthHandler") unregistered = append(unregistered, "AuthHandler")
} }
if o.DeleteContainerHandler == nil {
unregistered = append(unregistered, "DeleteContainerHandler")
}
if o.GetContainerHandler == nil { if o.GetContainerHandler == nil {
unregistered = append(unregistered, "GetContainerHandler") unregistered = append(unregistered, "GetContainerHandler")
} }
@ -318,6 +326,10 @@ func (o *NeofsRestGwAPI) initHandlerCache() {
o.handlers["POST"] = make(map[string]http.Handler) o.handlers["POST"] = make(map[string]http.Handler)
} }
o.handlers["POST"]["/auth"] = NewAuth(o.context, o.AuthHandler) o.handlers["POST"]["/auth"] = NewAuth(o.context, o.AuthHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/containers/{containerId}"] = NewDeleteContainer(o.context, o.DeleteContainerHandler)
if o.handlers["GET"] == nil { if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler) o.handlers["GET"] = make(map[string]http.Handler)
} }

View file

@ -14,15 +14,23 @@ import (
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate" "github.com/go-openapi/validate"
) )
// NewPutContainerParams creates a new PutContainerParams object // NewPutContainerParams creates a new PutContainerParams object
// // with the default values initialized.
// There are no default values defined in the spec.
func NewPutContainerParams() PutContainerParams { func NewPutContainerParams() PutContainerParams {
return PutContainerParams{} var (
// initialize parameters with default values
skipNativeNameDefault = bool(false)
)
return PutContainerParams{
SkipNativeName: &skipNativeNameDefault,
}
} }
// PutContainerParams contains all the bound params for the put container operation // PutContainerParams contains all the bound params for the put container operation
@ -49,6 +57,11 @@ type PutContainerParams struct {
In: body In: body
*/ */
Container PutContainerBody Container PutContainerBody
/*Provide this parameter to skip registration container name in NNS service
In: query
Default: false
*/
SkipNativeName *bool
} }
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@ -60,6 +73,8 @@ func (o *PutContainerParams) BindRequest(r *http.Request, route *middleware.Matc
o.HTTPRequest = r o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
if err := o.bindXNeofsTokenSignature(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature")], true, route.Formats); err != nil { if err := o.bindXNeofsTokenSignature(r.Header[http.CanonicalHeaderKey("X-Neofs-Token-Signature")], true, route.Formats); err != nil {
res = append(res, err) res = append(res, err)
} }
@ -95,6 +110,11 @@ func (o *PutContainerParams) BindRequest(r *http.Request, route *middleware.Matc
} else { } else {
res = append(res, errors.Required("container", "body", "")) res = append(res, errors.Required("container", "body", ""))
} }
qSkipNativeName, qhkSkipNativeName, _ := qs.GetOK("skip-native-name")
if err := o.bindSkipNativeName(qSkipNativeName, qhkSkipNativeName, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 { if len(res) > 0 {
return errors.CompositeValidationError(res...) return errors.CompositeValidationError(res...)
} }
@ -140,3 +160,27 @@ func (o *PutContainerParams) bindXNeofsTokenSignatureKey(rawData []string, hasKe
return nil return nil
} }
// bindSkipNativeName binds and validates parameter SkipNativeName from query.
func (o *PutContainerParams) bindSkipNativeName(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 NewPutContainerParams()
return nil
}
value, err := swag.ConvertBool(raw)
if err != nil {
return errors.InvalidType("skip-native-name", "query", "bool", raw)
}
o.SkipNativeName = &value
return nil
}

2
go.mod
View file

@ -13,7 +13,7 @@ require (
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/nspcc-dev/neo-go v0.98.2 github.com/nspcc-dev/neo-go v0.98.2
github.com/nspcc-dev/neofs-api-go/v2 v2.12.1 github.com/nspcc-dev/neofs-api-go/v2 v2.12.1
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220407103316-e50e6d28280d github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220412151250-3e75660802ae
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/testcontainers/testcontainers-go v0.13.0 github.com/testcontainers/testcontainers-go v0.13.0

4
go.sum
View file

@ -750,8 +750,8 @@ github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnB
github.com/nspcc-dev/neofs-crypto v0.3.0/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw= github.com/nspcc-dev/neofs-crypto v0.3.0/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:dfMtQWmBHYpl9Dez23TGtIUKiFvCIxUZq/CkSIhEpz4= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:dfMtQWmBHYpl9Dez23TGtIUKiFvCIxUZq/CkSIhEpz4=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/jay1lr3w7NQd/VDBkEhkJmDmyPNsu4W+QV2obsUV40= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/jay1lr3w7NQd/VDBkEhkJmDmyPNsu4W+QV2obsUV40=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220407103316-e50e6d28280d h1:OHyq8+zyQtARFWj3quRPabcfQWJZEiU7HYp6QGCSjaM= github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220412151250-3e75660802ae h1:xcoEwEwZXu784Re1PPE35vm1A4+sUCVgMuGFqQPnN1Q=
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220407103316-e50e6d28280d/go.mod h1:Hl7a1l0ntZ4b1ZABpGX6fuAuFS3c6+hyMCUNVvZv/w4= github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220412151250-3e75660802ae/go.mod h1:Hl7a1l0ntZ4b1ZABpGX6fuAuFS3c6+hyMCUNVvZv/w4=
github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=

View file

@ -68,6 +68,7 @@ func (a *API) Configure(api *operations.NeofsRestGwAPI) http.Handler {
api.PutContainerHandler = operations.PutContainerHandlerFunc(a.PutContainers) api.PutContainerHandler = operations.PutContainerHandlerFunc(a.PutContainers)
api.GetContainerHandler = operations.GetContainerHandlerFunc(a.GetContainer) api.GetContainerHandler = operations.GetContainerHandlerFunc(a.GetContainer)
api.DeleteContainerHandler = operations.DeleteContainerHandlerFunc(a.DeleteContainer)
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) {
@ -99,7 +100,7 @@ func (a *API) setupGlobalMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := uuid.NewString() requestID := uuid.NewString()
a.log.Info("request", zap.String("remote", r.RemoteAddr), a.log.Info("request", zap.String("remote", r.RemoteAddr),
zap.String("method", r.Method), zap.String("uri", r.RequestURI), zap.String("method", r.Method), zap.String("url", r.URL.String()),
zap.String("id", requestID)) zap.String("id", requestID))
ctx := context.WithValue(r.Context(), ContextKeyRequestID, requestID) ctx := context.WithValue(r.Context(), ContextKeyRequestID, requestID)

View file

@ -21,6 +21,7 @@ import (
"github.com/nspcc-dev/neofs-sdk-go/policy" "github.com/nspcc-dev/neofs-sdk-go/policy"
"github.com/nspcc-dev/neofs-sdk-go/pool" "github.com/nspcc-dev/neofs-sdk-go/pool"
"github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/session"
"go.uber.org/zap"
) )
const ( const (
@ -42,7 +43,7 @@ func (a *API) PutContainers(params operations.PutContainerParams, principal *mod
userAttributes := prepareUserAttributes(params.HTTPRequest.Header) userAttributes := prepareUserAttributes(params.HTTPRequest.Header)
cnrID, err := createContainer(params.HTTPRequest.Context(), a.pool, stoken, &params.Container, userAttributes) cnrID, err := createContainer(params.HTTPRequest.Context(), a.pool, stoken, &params, userAttributes)
if err != nil { if err != nil {
return wrapError(err) return wrapError(err)
} }
@ -80,6 +81,37 @@ func (a *API) GetContainer(params operations.GetContainerParams) middleware.Resp
return operations.NewGetContainerOK().WithPayload(resp) return operations.NewGetContainerOK().WithPayload(resp)
} }
// DeleteContainer handler that returns container info.
func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal *models.Principal) middleware.Responder {
bt := &BearerToken{
Token: string(*principal),
Signature: params.XNeofsTokenSignature,
Key: params.XNeofsTokenSignatureKey,
}
stoken, err := prepareSessionToken(bt)
if err != nil {
a.log.Error("failed parse session token", zap.Error(err))
return operations.NewDeleteContainerBadRequest().WithPayload(NewError(err))
}
cnrID, err := parseContainerID(params.ContainerID)
if err != nil {
a.log.Error("failed get container id", zap.Error(err))
return operations.NewDeleteContainerBadRequest().WithPayload(NewError(err))
}
var prm pool.PrmContainerDelete
prm.SetContainerID(*cnrID)
prm.SetSessionToken(*stoken)
if err = a.pool.DeleteContainer(params.HTTPRequest.Context(), prm); err != nil {
a.log.Error("failed delete container", zap.String("container", params.ContainerID), zap.Error(err))
return operations.NewDeleteContainerBadRequest().WithPayload(NewError(err))
}
return operations.NewDeleteContainerNoContent()
}
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)
@ -88,18 +120,29 @@ func prepareUserAttributes(header http.Header) map[string]string {
} }
func getContainer(ctx context.Context, p *pool.Pool, containerID string) (*container.Container, error) { func getContainer(ctx context.Context, p *pool.Pool, containerID string) (*container.Container, error) {
cnrID, err := parseContainerID(containerID)
if err != nil {
return nil, err
}
var prm pool.PrmContainerGet
prm.SetContainerID(*cnrID)
return p.GetContainer(ctx, prm)
}
func parseContainerID(containerID string) (*cid.ID, error) {
var cnrID cid.ID var cnrID cid.ID
if err := cnrID.Parse(containerID); err != nil { if err := cnrID.Parse(containerID); err != nil {
return nil, fmt.Errorf("parse container id '%s': %w", containerID, err) return nil, fmt.Errorf("parse container id '%s': %w", containerID, err)
} }
var prm pool.PrmContainerGet return &cnrID, nil
prm.SetContainerID(cnrID)
return p.GetContainer(ctx, prm)
} }
func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, request *operations.PutContainerBody, userAttrs map[string]string) (*cid.ID, error) { func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, params *operations.PutContainerParams, userAttrs map[string]string) (*cid.ID, error) {
request := params.Container
if request.PlacementPolicy == "" { if request.PlacementPolicy == "" {
request.PlacementPolicy = defaultPlacementPolicy request.PlacementPolicy = defaultPlacementPolicy
} }
@ -131,7 +174,9 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, r
cnr.SetOwnerID(stoken.OwnerID()) cnr.SetOwnerID(stoken.OwnerID())
cnr.SetSessionToken(stoken) cnr.SetSessionToken(stoken)
container.SetNativeName(cnr, *request.ContainerName) if !*params.SkipNativeName { // we don't check for nil because there is default false value
container.SetNativeName(cnr, *request.ContainerName)
}
var prm pool.PrmContainerPut var prm pool.PrmContainerPut
prm.SetContainer(*cnr) prm.SetContainer(*cnr)

View file

@ -167,6 +167,11 @@ paths:
operationId: putContainer operationId: putContainer
summary: Create new container in NeoFS summary: Create new container in NeoFS
parameters: parameters:
- in: query
name: skip-native-name
description: Provide this parameter to skip registration container name in NNS service
type: boolean
default: false
- in: body - in: body
name: container name: container
required: true required: true
@ -204,16 +209,16 @@ paths:
$ref: '#/definitions/Error' $ref: '#/definitions/Error'
/containers/{containerId}: /containers/{containerId}:
parameters:
- in: path
name: containerId
type: string
required: true
description: Base58 encoded container id
get: get:
operationId: getContainer operationId: getContainer
summary: Get container by id summary: Get container by id
security: [ ] security: [ ]
parameters:
- in: path
name: containerId
type: string
required: true
description: Base58 encoded container id
responses: responses:
200: 200:
description: Container info description: Container info
@ -223,6 +228,19 @@ paths:
description: Bad request description: Bad request
schema: schema:
$ref: '#/definitions/Error' $ref: '#/definitions/Error'
delete:
operationId: deleteContainer
summary: Delete container by id
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
responses:
204:
description: Successul deletion
400:
description: Bad request
schema:
$ref: '#/definitions/Error'
definitions: definitions:
Bearer: Bearer: