forked from TrueCloudLab/frostfs-rest-gw
[#1] Add route to delete container
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
066656ac48
commit
63fdb08f14
12 changed files with 637 additions and 96 deletions
|
@ -13,6 +13,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -55,7 +56,13 @@ const (
|
|||
func TestIntegration(t *testing.T) {
|
||||
rootCtx := context.Background()
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -65,12 +72,13 @@ func TestIntegration(t *testing.T) {
|
|||
aioContainer := createDockerContainer(ctx, t, aioImage+version)
|
||||
cancel := runServer(ctx, t)
|
||||
clientPool := getPool(ctx, t, key)
|
||||
CID, err := createContainer(ctx, t, clientPool)
|
||||
require.NoError(t, err, version)
|
||||
cnrID := createContainer(ctx, t, clientPool, "test-container")
|
||||
|
||||
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 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()
|
||||
err = aioContainer.Terminate(ctx)
|
||||
|
@ -132,6 +140,7 @@ func getDefaultConfig() *viper.Viper {
|
|||
v.SetDefault(cfgPeers+".0.weight", 1)
|
||||
v.SetDefault(cfgPeers+".0.priority", 1)
|
||||
v.SetDefault(restapi.FlagListenAddress, testListenAddress)
|
||||
v.SetDefault(restapi.FlagWriteTimeout, 60*time.Second)
|
||||
|
||||
return v
|
||||
}
|
||||
|
@ -292,8 +301,98 @@ func restContainerGet(ctx context.Context, t *testing.T, clientPool *pool.Pool,
|
|||
err = json.NewDecoder(resp.Body).Decode(cnrInfo)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, cnrID.String(), cnrInfo.ContainerID)
|
||||
require.Equal(t, clientPool.OwnerID().String(), cnrInfo.OwnerID)
|
||||
require.Equal(t, cnrID.String(), *cnrInfo.ContainerID)
|
||||
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 {
|
||||
|
@ -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) {
|
||||
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
b := models.Bearer{
|
||||
bearer := &models.Bearer{
|
||||
Container: &models.Rule{
|
||||
Verb: models.NewVerb(models.VerbPUT),
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(&b)
|
||||
require.NoError(t, err)
|
||||
httpClient := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
request0, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data))
|
||||
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)
|
||||
bearerToken := makeAuthContainerTokenRequest(ctx, t, bearer, httpClient)
|
||||
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Println(base64.StdEncoding.EncodeToString(signatureData))
|
||||
fmt.Println(hex.EncodeToString(key.PublicKey().Bytes()))
|
||||
reqURL, err := url.Parse(testHost + "/v1/containers")
|
||||
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)
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
request.Header.Add(XNeofsTokenSignature, base64.StdEncoding.EncodeToString(signatureData))
|
||||
request.Header.Add("Authorization", "Bearer "+*bearerBase64)
|
||||
request.Header.Add(XNeofsTokenSignatureKey, hex.EncodeToString(key.PublicKey().Bytes()))
|
||||
prepareBearerHeaders(request.Header, bearerToken)
|
||||
request.Header.Add("X-Attribute-"+attrKey, attrValue)
|
||||
|
||||
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")
|
||||
require.NoError(t, err)
|
||||
|
||||
cnr := container.New(
|
||||
container.WithPolicy(pp),
|
||||
container.WithCustomBasicACL(0x0FFFFFFF),
|
||||
container.WithAttribute(container.AttributeName, "friendlyName"),
|
||||
container.WithAttribute(container.AttributeName, name),
|
||||
container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10)))
|
||||
cnr.SetOwnerID(clientPool.OwnerID())
|
||||
|
||||
|
@ -426,12 +501,9 @@ func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool) (
|
|||
prm.SetWaitParams(waitPrm)
|
||||
|
||||
CID, err := clientPool.PutContainer(ctx, prm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println(CID.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
return CID, err
|
||||
return CID
|
||||
}
|
||||
|
||||
func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) {
|
||||
|
|
|
@ -98,6 +98,13 @@ func init() {
|
|||
"summary": "Create new container in NeoFS",
|
||||
"operationId": "putContainer",
|
||||
"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",
|
||||
"name": "container",
|
||||
|
@ -167,15 +174,6 @@ func init() {
|
|||
"security": [],
|
||||
"summary": "Get container by id",
|
||||
"operationId": "getContainer",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Base58 encoded container id",
|
||||
"name": "containerId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"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": {
|
||||
"put": {
|
||||
|
@ -745,6 +775,13 @@ func init() {
|
|||
"summary": "Create new container in NeoFS",
|
||||
"operationId": "putContainer",
|
||||
"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",
|
||||
"name": "container",
|
||||
|
@ -822,15 +859,6 @@ func init() {
|
|||
"security": [],
|
||||
"summary": "Get container by id",
|
||||
"operationId": "getContainer",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Base58 encoded container id",
|
||||
"name": "containerId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"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": {
|
||||
"put": {
|
||||
|
|
71
gen/restapi/operations/delete_container.go
Normal file
71
gen/restapi/operations/delete_container.go
Normal 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)
|
||||
|
||||
}
|
130
gen/restapi/operations/delete_container_parameters.go
Normal file
130
gen/restapi/operations/delete_container_parameters.go
Normal 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
|
||||
}
|
80
gen/restapi/operations/delete_container_responses.go
Normal file
80
gen/restapi/operations/delete_container_responses.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -47,6 +47,9 @@ func NewNeofsRestGwAPI(spec *loads.Document) *NeofsRestGwAPI {
|
|||
AuthHandler: AuthHandlerFunc(func(params AuthParams) middleware.Responder {
|
||||
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 {
|
||||
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 AuthHandler
|
||||
// DeleteContainerHandler sets the operation handler for the delete container operation
|
||||
DeleteContainerHandler DeleteContainerHandler
|
||||
// GetContainerHandler sets the operation handler for the get container operation
|
||||
GetContainerHandler GetContainerHandler
|
||||
// GetObjectInfoHandler sets the operation handler for the get object info operation
|
||||
|
@ -203,6 +208,9 @@ func (o *NeofsRestGwAPI) Validate() error {
|
|||
if o.AuthHandler == nil {
|
||||
unregistered = append(unregistered, "AuthHandler")
|
||||
}
|
||||
if o.DeleteContainerHandler == nil {
|
||||
unregistered = append(unregistered, "DeleteContainerHandler")
|
||||
}
|
||||
if o.GetContainerHandler == nil {
|
||||
unregistered = append(unregistered, "GetContainerHandler")
|
||||
}
|
||||
|
@ -318,6 +326,10 @@ func (o *NeofsRestGwAPI) initHandlerCache() {
|
|||
o.handlers["POST"] = make(map[string]http.Handler)
|
||||
}
|
||||
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 {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
}
|
||||
|
|
|
@ -14,15 +14,23 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
||||
// NewPutContainerParams creates a new PutContainerParams object
|
||||
//
|
||||
// There are no default values defined in the spec.
|
||||
// with the default values initialized.
|
||||
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
|
||||
|
@ -49,6 +57,11 @@ type PutContainerParams struct {
|
|||
In: body
|
||||
*/
|
||||
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
|
||||
|
@ -60,6 +73,8 @@ func (o *PutContainerParams) BindRequest(r *http.Request, route *middleware.Matc
|
|||
|
||||
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 {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
@ -95,6 +110,11 @@ func (o *PutContainerParams) BindRequest(r *http.Request, route *middleware.Matc
|
|||
} else {
|
||||
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 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
|
@ -140,3 +160,27 @@ func (o *PutContainerParams) bindXNeofsTokenSignatureKey(rawData []string, hasKe
|
|||
|
||||
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
2
go.mod
|
@ -13,7 +13,7 @@ require (
|
|||
github.com/google/uuid v1.3.0
|
||||
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-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/stretchr/testify v1.7.0
|
||||
github.com/testcontainers/testcontainers-go v0.13.0
|
||||
|
|
4
go.sum
4
go.sum
|
@ -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-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 v1.0.0-rc.3.0.20220407103316-e50e6d28280d h1:OHyq8+zyQtARFWj3quRPabcfQWJZEiU7HYp6QGCSjaM=
|
||||
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 h1:xcoEwEwZXu784Re1PPE35vm1A4+sUCVgMuGFqQPnN1Q=
|
||||
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.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
|
||||
|
|
|
@ -68,6 +68,7 @@ func (a *API) Configure(api *operations.NeofsRestGwAPI) http.Handler {
|
|||
|
||||
api.PutContainerHandler = operations.PutContainerHandlerFunc(a.PutContainers)
|
||||
api.GetContainerHandler = operations.GetContainerHandlerFunc(a.GetContainer)
|
||||
api.DeleteContainerHandler = operations.DeleteContainerHandlerFunc(a.DeleteContainer)
|
||||
|
||||
api.BearerAuthAuth = func(s string) (*models.Principal, error) {
|
||||
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) {
|
||||
requestID := uuid.NewString()
|
||||
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))
|
||||
|
||||
ctx := context.WithValue(r.Context(), ContextKeyRequestID, requestID)
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/nspcc-dev/neofs-sdk-go/policy"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/pool"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -42,7 +43,7 @@ func (a *API) PutContainers(params operations.PutContainerParams, principal *mod
|
|||
|
||||
userAttributes := prepareUserAttributes(params.HTTPRequest.Header)
|
||||
|
||||
cnrID, err := createContainer(params.HTTPRequest.Context(), a.pool, stoken, ¶ms.Container, userAttributes)
|
||||
cnrID, err := createContainer(params.HTTPRequest.Context(), a.pool, stoken, ¶ms, userAttributes)
|
||||
if err != nil {
|
||||
return wrapError(err)
|
||||
}
|
||||
|
@ -80,6 +81,37 @@ func (a *API) GetContainer(params operations.GetContainerParams) middleware.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 {
|
||||
filtered := filterHeaders(header)
|
||||
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) {
|
||||
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
|
||||
if err := cnrID.Parse(containerID); err != nil {
|
||||
return nil, fmt.Errorf("parse container id '%s': %w", containerID, err)
|
||||
}
|
||||
|
||||
var prm pool.PrmContainerGet
|
||||
prm.SetContainerID(cnrID)
|
||||
|
||||
return p.GetContainer(ctx, prm)
|
||||
return &cnrID, nil
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
request.PlacementPolicy = defaultPlacementPolicy
|
||||
}
|
||||
|
@ -131,7 +174,9 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, r
|
|||
cnr.SetOwnerID(stoken.OwnerID())
|
||||
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
|
||||
prm.SetContainer(*cnr)
|
||||
|
|
|
@ -167,6 +167,11 @@ paths:
|
|||
operationId: putContainer
|
||||
summary: Create new container in NeoFS
|
||||
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
|
||||
name: container
|
||||
required: true
|
||||
|
@ -204,16 +209,16 @@ paths:
|
|||
$ref: '#/definitions/Error'
|
||||
|
||||
/containers/{containerId}:
|
||||
parameters:
|
||||
- in: path
|
||||
name: containerId
|
||||
type: string
|
||||
required: true
|
||||
description: Base58 encoded container id
|
||||
get:
|
||||
operationId: getContainer
|
||||
summary: Get container by id
|
||||
security: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: containerId
|
||||
type: string
|
||||
required: true
|
||||
description: Base58 encoded container id
|
||||
responses:
|
||||
200:
|
||||
description: Container info
|
||||
|
@ -223,6 +228,19 @@ paths:
|
|||
description: Bad request
|
||||
schema:
|
||||
$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:
|
||||
Bearer:
|
||||
|
|
Loading…
Reference in a new issue