[#25] Synchronize container get and put params

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-07-19 17:48:28 +03:00 committed by Kirillov Denis
parent bacf909594
commit 665bcfb52d
9 changed files with 270 additions and 164 deletions

View file

@ -322,7 +322,7 @@ func mixTokens(ctx context.Context, t *testing.T, cnrID cid.ID) {
func checkPutContainerWithError(t *testing.T, httpClient *http.Client, token *handlers.BearerToken) { func checkPutContainerWithError(t *testing.T, httpClient *http.Client, token *handlers.BearerToken) {
reqURL, err := url.Parse(testHost + "/v1/containers") reqURL, err := url.Parse(testHost + "/v1/containers")
require.NoError(t, err) require.NoError(t, err)
body, err := json.Marshal(&operations.PutContainerBody{ContainerName: "container"}) body, err := json.Marshal(&models.ContainerPutInfo{ContainerName: "container"})
require.NoError(t, err) require.NoError(t, err)
request, err := http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body)) request, err := http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
@ -692,6 +692,7 @@ func restContainerGet(ctx context.Context, t *testing.T, owner user.ID, cnrID ci
require.Equal(t, cnrID.EncodeToString(), *cnrInfo.ContainerID) require.Equal(t, cnrID.EncodeToString(), *cnrInfo.ContainerID)
require.Equal(t, owner.EncodeToString(), *cnrInfo.OwnerID) require.Equal(t, owner.EncodeToString(), *cnrInfo.OwnerID)
require.Equal(t, containerName, *cnrInfo.ContainerName)
} }
func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Pool, owner user.ID) { func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Pool, owner user.ID) {
@ -953,7 +954,7 @@ func restContainerPutInvalid(ctx context.Context, t *testing.T) {
query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect))
reqURL.RawQuery = query.Encode() reqURL.RawQuery = query.Encode()
body, err := json.Marshal(&operations.PutContainerBody{ContainerName: "nameWithCapitalLetters"}) body, err := json.Marshal(&models.ContainerPutInfo{ContainerName: "nameWithCapitalLetters"})
require.NoError(t, err) require.NoError(t, err)
request, err := http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body)) request, err := http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
@ -982,7 +983,7 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool)
} }
// try to create container without name but with name-scope-global // try to create container without name but with name-scope-global
body, err := json.Marshal(&operations.PutContainerBody{}) body, err := json.Marshal(&models.ContainerPutInfo{})
require.NoError(t, err) require.NoError(t, err)
reqURL, err := url.Parse(testHost + "/v1/containers") reqURL, err := url.Parse(testHost + "/v1/containers")
@ -999,7 +1000,13 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool)
doRequest(t, httpClient, request, http.StatusBadRequest, nil) doRequest(t, httpClient, request, http.StatusBadRequest, nil)
// create container with name in local scope // create container with name in local scope
body, err = json.Marshal(&operations.PutContainerBody{}) containerPutInfo := &models.ContainerPutInfo{
Attributes: []*models.Attribute{{
Key: util.NewString(attrKey),
Value: util.NewString(attrValue),
}},
}
body, err = json.Marshal(containerPutInfo)
require.NoError(t, err) require.NoError(t, err)
reqURL, err = url.Parse(testHost + "/v1/containers") reqURL, err = url.Parse(testHost + "/v1/containers")
@ -1011,7 +1018,6 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool)
request, err = http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body)) request, err = http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
prepareCommonHeaders(request.Header, bearerToken) prepareCommonHeaders(request.Header, bearerToken)
request.Header.Add("X-Attribute-"+attrKey, attrValue)
addr := &operations.PutContainerOKBody{} addr := &operations.PutContainerOKBody{}
doRequest(t, httpClient, request, http.StatusOK, addr) doRequest(t, httpClient, request, http.StatusOK, addr)

View file

@ -33,6 +33,10 @@ type ContainerInfo struct {
// Required: true // Required: true
ContainerID *string `json:"containerId"` ContainerID *string `json:"containerId"`
// container name
// Required: true
ContainerName *string `json:"containerName"`
// owner Id // owner Id
// Required: true // Required: true
OwnerID *string `json:"ownerId"` OwnerID *string `json:"ownerId"`
@ -62,6 +66,10 @@ func (m *ContainerInfo) Validate(formats strfmt.Registry) error {
res = append(res, err) res = append(res, err)
} }
if err := m.validateContainerName(formats); err != nil {
res = append(res, err)
}
if err := m.validateOwnerID(formats); err != nil { if err := m.validateOwnerID(formats); err != nil {
res = append(res, err) res = append(res, err)
} }
@ -125,6 +133,15 @@ func (m *ContainerInfo) validateContainerID(formats strfmt.Registry) error {
return nil return nil
} }
func (m *ContainerInfo) validateContainerName(formats strfmt.Registry) error {
if err := validate.Required("containerName", "body", m.ContainerName); err != nil {
return err
}
return nil
}
func (m *ContainerInfo) validateOwnerID(formats strfmt.Registry) error { func (m *ContainerInfo) validateOwnerID(formats strfmt.Registry) error {
if err := validate.Required("ownerId", "body", m.OwnerID); err != nil { if err := validate.Required("ownerId", "body", m.OwnerID); err != nil {

View file

@ -0,0 +1,126 @@
// 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"
)
// ContainerPutInfo Represent request body to create container. To specify container name use appropriate property (name provided in attributes will be ignored).
// Example: {"attributes":[{"key":"Custom-Attribute","value":"value"}],"basicAcl":"public-read-write","containerName":"container","placementPolicy":"REP 3"}
//
// swagger:model ContainerPutInfo
type ContainerPutInfo struct {
// attributes
Attributes []*Attribute `json:"attributes"`
// basic Acl
BasicACL string `json:"basicAcl,omitempty"`
// container name
ContainerName string `json:"containerName,omitempty"`
// placement policy
PlacementPolicy string `json:"placementPolicy,omitempty"`
}
// Validate validates this container put info
func (m *ContainerPutInfo) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAttributes(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ContainerPutInfo) validateAttributes(formats strfmt.Registry) error {
if swag.IsZero(m.Attributes) { // not required
return nil
}
for i := 0; i < len(m.Attributes); i++ {
if swag.IsZero(m.Attributes[i]) { // not required
continue
}
if m.Attributes[i] != nil {
if err := m.Attributes[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("attributes" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("attributes" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this container put info based on the context it is used
func (m *ContainerPutInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAttributes(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ContainerPutInfo) contextValidateAttributes(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Attributes); i++ {
if m.Attributes[i] != nil {
if err := m.Attributes[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("attributes" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("attributes" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ContainerPutInfo) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ContainerPutInfo) UnmarshalBinary(b []byte) error {
var res ContainerPutInfo
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View file

@ -159,23 +159,7 @@ func init() {
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/ContainerPutInfo"
"properties": {
"basicAcl": {
"type": "string"
},
"containerName": {
"type": "string"
},
"placementPolicy": {
"type": "string"
}
},
"example": {
"basicAcl": "public-read-write",
"containerName": "container",
"placementPolicy": "REP 3"
}
} }
} }
], ],
@ -574,6 +558,7 @@ func init() {
"type": "object", "type": "object",
"required": [ "required": [
"containerId", "containerId",
"containerName",
"version", "version",
"ownerId", "ownerId",
"basicAcl", "basicAcl",
@ -593,6 +578,9 @@ func init() {
"containerId": { "containerId": {
"type": "string" "type": "string"
}, },
"containerName": {
"type": "string"
},
"ownerId": { "ownerId": {
"type": "string" "type": "string"
}, },
@ -639,6 +627,38 @@ func init() {
} }
} }
}, },
"ContainerPutInfo": {
"description": "Represent request body to create container. To specify container name use appropriate property (name provided in attributes will be ignored).",
"type": "object",
"properties": {
"attributes": {
"type": "array",
"items": {
"$ref": "#/definitions/Attribute"
}
},
"basicAcl": {
"type": "string"
},
"containerName": {
"type": "string"
},
"placementPolicy": {
"type": "string"
}
},
"example": {
"attributes": [
{
"key": "Custom-Attribute",
"value": "value"
}
],
"basicAcl": "public-read-write",
"containerName": "container",
"placementPolicy": "REP 3"
}
},
"Eacl": { "Eacl": {
"type": "object", "type": "object",
"required": [ "required": [
@ -1252,23 +1272,7 @@ func init() {
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"type": "object", "$ref": "#/definitions/ContainerPutInfo"
"properties": {
"basicAcl": {
"type": "string"
},
"containerName": {
"type": "string"
},
"placementPolicy": {
"type": "string"
}
},
"example": {
"basicAcl": "public-read-write",
"containerName": "container",
"placementPolicy": "REP 3"
}
} }
} }
], ],
@ -1750,6 +1754,7 @@ func init() {
"type": "object", "type": "object",
"required": [ "required": [
"containerId", "containerId",
"containerName",
"version", "version",
"ownerId", "ownerId",
"basicAcl", "basicAcl",
@ -1769,6 +1774,9 @@ func init() {
"containerId": { "containerId": {
"type": "string" "type": "string"
}, },
"containerName": {
"type": "string"
},
"ownerId": { "ownerId": {
"type": "string" "type": "string"
}, },
@ -1815,6 +1823,38 @@ func init() {
} }
} }
}, },
"ContainerPutInfo": {
"description": "Represent request body to create container. To specify container name use appropriate property (name provided in attributes will be ignored).",
"type": "object",
"properties": {
"attributes": {
"type": "array",
"items": {
"$ref": "#/definitions/Attribute"
}
},
"basicAcl": {
"type": "string"
},
"containerName": {
"type": "string"
},
"placementPolicy": {
"type": "string"
}
},
"example": {
"attributes": [
{
"key": "Custom-Attribute",
"value": "value"
}
],
"basicAcl": "public-read-write",
"containerName": "container",
"placementPolicy": "REP 3"
}
},
"Eacl": { "Eacl": {
"type": "object", "type": "object",
"required": [ "required": [

View file

@ -75,50 +75,6 @@ func (o *PutContainer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
} }
// PutContainerBody put container body
// Example: {"basicAcl":"public-read-write","containerName":"container","placementPolicy":"REP 3"}
//
// swagger:model PutContainerBody
type PutContainerBody struct {
// basic Acl
BasicACL string `json:"basicAcl,omitempty"`
// container name
ContainerName string `json:"containerName,omitempty"`
// placement policy
PlacementPolicy string `json:"placementPolicy,omitempty"`
}
// Validate validates this put container body
func (o *PutContainerBody) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this put container body based on context it is used
func (o *PutContainerBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (o *PutContainerBody) MarshalBinary() ([]byte, error) {
if o == nil {
return nil, nil
}
return swag.WriteJSON(o)
}
// UnmarshalBinary interface implementation
func (o *PutContainerBody) UnmarshalBinary(b []byte) error {
var res PutContainerBody
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*o = res
return nil
}
// PutContainerOKBody put container o k body // PutContainerOKBody put container o k body
// Example: {"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv"} // Example: {"containerId":"5HZTn5qkRnmgSz9gSrw22CEdPPk6nQhkwf2Mgzyvkikv"}
// //

View file

@ -16,6 +16,8 @@ import (
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/go-openapi/swag" "github.com/go-openapi/swag"
"github.com/go-openapi/validate" "github.com/go-openapi/validate"
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
) )
// NewPutContainerParams creates a new PutContainerParams object // NewPutContainerParams creates a new PutContainerParams object
@ -59,7 +61,7 @@ type PutContainerParams struct {
Required: true Required: true
In: body In: body
*/ */
Container PutContainerBody Container *models.ContainerPutInfo
/*Provide this parameter to register container name in NNS service /*Provide this parameter to register container name in NNS service
In: query In: query
Default: false Default: false
@ -93,7 +95,7 @@ func (o *PutContainerParams) BindRequest(r *http.Request, route *middleware.Matc
if runtime.HasBody(r) { if runtime.HasBody(r) {
defer r.Body.Close() defer r.Body.Close()
var body PutContainerBody var body models.ContainerPutInfo
if err := route.Consumer.Consume(r.Body, &body); err != nil { if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF { if err == io.EOF {
res = append(res, errors.Required("container", "body", "")) res = append(res, errors.Required("container", "body", ""))
@ -112,7 +114,7 @@ func (o *PutContainerParams) BindRequest(r *http.Request, route *middleware.Matc
} }
if len(res) == 0 { if len(res) == 0 {
o.Container = body o.Container = &body
} }
} }
} else { } else {

View file

@ -6,12 +6,12 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"net/http"
"strings" "strings"
"time" "time"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
containerv2 "github.com/nspcc-dev/neofs-api-go/v2/container"
"github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/refs"
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-rest-gw/gen/models" "github.com/nspcc-dev/neofs-rest-gw/gen/models"
@ -50,9 +50,7 @@ func (a *API) PutContainers(params operations.PutContainerParams, principal *mod
return operations.NewPutContainerBadRequest().WithPayload(resp) return operations.NewPutContainerBadRequest().WithPayload(resp)
} }
userAttributes := prepareUserAttributes(params.HTTPRequest.Header) cnrID, err := createContainer(params.HTTPRequest.Context(), a.pool, stoken, &params)
cnrID, err := createContainer(params.HTTPRequest.Context(), a.pool, stoken, &params, userAttributes)
if err != nil { if err != nil {
resp := a.logAndGetErrorResponse("create container", err) resp := a.logAndGetErrorResponse("create container", err)
return operations.NewPutContainerBadRequest().WithPayload(resp) return operations.NewPutContainerBadRequest().WithPayload(resp)
@ -237,6 +235,7 @@ func getContainerInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID) (*models.
return &models.ContainerInfo{ return &models.ContainerInfo{
ContainerID: util.NewString(cnrID.String()), ContainerID: util.NewString(cnrID.String()),
ContainerName: util.NewString(container.Name(*cnr)),
OwnerID: util.NewString(cnr.Owner().String()), OwnerID: util.NewString(cnr.Owner().String()),
BasicACL: util.NewString(cnr.BasicACL().EncodeToString()), BasicACL: util.NewString(cnr.BasicACL().EncodeToString()),
PlacementPolicy: util.NewString(sb.String()), PlacementPolicy: util.NewString(sb.String()),
@ -244,13 +243,6 @@ func getContainerInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID) (*models.
}, nil }, nil
} }
func prepareUserAttributes(header http.Header) map[string]string {
filtered := filterHeaders(header)
delete(filtered, attributeName)
delete(filtered, attributeTimestamp)
return filtered
}
func parseContainerID(containerID string) (cid.ID, error) { func parseContainerID(containerID string) (cid.ID, error) {
var cnrID cid.ID var cnrID cid.ID
if err := cnrID.DecodeString(containerID); err != nil { if err := cnrID.DecodeString(containerID); err != nil {
@ -300,7 +292,7 @@ func getContainerEACL(ctx context.Context, p *pool.Pool, cnrID cid.ID) (*models.
return tableResp, nil return tableResp, nil
} }
func createContainer(ctx context.Context, p *pool.Pool, stoken session.Container, params *operations.PutContainerParams, userAttrs map[string]string) (cid.ID, error) { func createContainer(ctx context.Context, p *pool.Pool, stoken session.Container, params *operations.PutContainerParams) (cid.ID, error) {
request := params.Container request := params.Container
if request.PlacementPolicy == "" { if request.PlacementPolicy == "" {
@ -333,8 +325,13 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken session.Container
container.SetName(&cnr, request.ContainerName) container.SetName(&cnr, request.ContainerName)
} }
for key, val := range userAttrs { for _, attr := range request.Attributes {
cnr.SetAttribute(key, val) switch *attr.Key {
case attributeName, attributeTimestamp,
containerv2.SysAttributeName, containerv2.SysAttributeZone:
default:
cnr.SetAttribute(*attr.Key, *attr.Value)
}
} }
if *params.NameScopeGlobal { // we don't check for nil because there is default false value if *params.NameScopeGlobal { // we don't check for nil because there is default false value

View file

@ -3,9 +3,7 @@ package handlers
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"strings"
"time" "time"
objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object" objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object"
@ -27,7 +25,6 @@ type epochDurations struct {
} }
const ( const (
UserAttributeHeaderPrefix = "X-Attribute-"
SystemAttributePrefix = "__NEOFS__" SystemAttributePrefix = "__NEOFS__"
ExpirationDurationAttr = SystemAttributePrefix + "EXPIRATION_DURATION" ExpirationDurationAttr = SystemAttributePrefix + "EXPIRATION_DURATION"
@ -35,54 +32,6 @@ const (
ExpirationRFC3339Attr = SystemAttributePrefix + "EXPIRATION_RFC3339" ExpirationRFC3339Attr = SystemAttributePrefix + "EXPIRATION_RFC3339"
) )
var neofsAttributeHeaderPrefixes = [...]string{"Neofs-", "NEOFS-", "neofs-"}
func systemTranslator(key, prefix string) string {
// replace specified prefix with `__NEOFS__`
key = strings.Replace(key, prefix, SystemAttributePrefix, 1)
// replace `-` with `_`
key = strings.ReplaceAll(key, "-", "_")
// replace with uppercase
return strings.ToUpper(key)
}
func filterHeaders(header http.Header) map[string]string {
result := make(map[string]string)
prefix := UserAttributeHeaderPrefix
for key, vals := range header {
if len(key) == 0 || len(vals) == 0 || len(vals[0]) == 0 {
continue
}
// checks that key has attribute prefix
if !strings.HasPrefix(key, prefix) {
continue
}
// removing attribute prefix
key = strings.TrimPrefix(key, prefix)
// checks that it's a system NeoFS header
for _, system := range neofsAttributeHeaderPrefixes {
if strings.HasPrefix(key, system) {
key = systemTranslator(key, system)
break
}
}
// checks that attribute key not empty
if len(key) == 0 {
continue
}
result[key] = vals[0]
}
return result
}
// GetObjectAttributes forms object attributes from request headers. // GetObjectAttributes forms object attributes from request headers.
func GetObjectAttributes(ctx context.Context, pool *pool.Pool, attrs []*models.Attribute, prm PrmAttributes) ([]object.Attribute, error) { func GetObjectAttributes(ctx context.Context, pool *pool.Pool, attrs []*models.Attribute, prm PrmAttributes) ([]object.Attribute, error) {
headers := make(map[string]string, len(attrs)) headers := make(map[string]string, len(attrs))

View file

@ -229,18 +229,7 @@ paths:
required: true required: true
description: Container info description: Container info
schema: schema:
type: object $ref: '#/definitions/ContainerPutInfo'
properties:
containerName:
type: string
placementPolicy:
type: string
basicAcl:
type: string
example:
containerName: container
placementPolicy: "REP 3"
basicAcl: public-read-write
responses: responses:
200: 200:
description: Address of uploaded objects description: Address of uploaded objects
@ -506,11 +495,34 @@ definitions:
enum: enum:
- object - object
- container - container
ContainerPutInfo:
description: Represent request body to create container. To specify container name use appropriate property (name provided in attributes will be ignored).
type: object
properties:
containerName:
type: string
placementPolicy:
type: string
basicAcl:
type: string
attributes:
type: array
items:
$ref: '#/definitions/Attribute'
example:
containerName: container
placementPolicy: "REP 3"
basicAcl: public-read-write
attributes:
- key: Custom-Attribute
value: value
ContainerInfo: ContainerInfo:
type: object type: object
properties: properties:
containerId: containerId:
type: string type: string
containerName:
type: string
version: version:
type: string type: string
ownerId: ownerId:
@ -525,6 +537,7 @@ definitions:
$ref: '#/definitions/Attribute' $ref: '#/definitions/Attribute'
required: required:
- containerId - containerId
- containerName
- version - version
- ownerId - ownerId
- basicAcl - basicAcl