diff --git a/models/bucket_object.go b/models/bucket_object.go new file mode 100644 index 0000000000..fa871f315c --- /dev/null +++ b/models/bucket_object.go @@ -0,0 +1,69 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +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 ( + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// BucketObject bucket object +// +// swagger:model bucketObject +type BucketObject struct { + + // content type + ContentType string `json:"content_type,omitempty"` + + // last modified + LastModified string `json:"last_modified,omitempty"` + + // name + Name string `json:"name,omitempty"` + + // size + Size int64 `json:"size,omitempty"` +} + +// Validate validates this bucket object +func (m *BucketObject) Validate(formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *BucketObject) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *BucketObject) UnmarshalBinary(b []byte) error { + var res BucketObject + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/list_objects_response.go b/models/list_objects_response.go new file mode 100644 index 0000000000..8f3815f501 --- /dev/null +++ b/models/list_objects_response.go @@ -0,0 +1,100 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +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 ( + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ListObjectsResponse list objects response +// +// swagger:model listObjectsResponse +type ListObjectsResponse struct { + + // list of resulting objects + Objects []*BucketObject `json:"objects"` + + // number of objects + Total int64 `json:"total,omitempty"` +} + +// Validate validates this list objects response +func (m *ListObjectsResponse) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateObjects(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ListObjectsResponse) validateObjects(formats strfmt.Registry) error { + + if swag.IsZero(m.Objects) { // not required + return nil + } + + for i := 0; i < len(m.Objects); i++ { + if swag.IsZero(m.Objects[i]) { // not required + continue + } + + if m.Objects[i] != nil { + if err := m.Objects[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("objects" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ListObjectsResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ListObjectsResponse) UnmarshalBinary(b []byte) error { + var res ListObjectsResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/restapi/client.go b/restapi/client.go index 08f075595d..176b152a48 100644 --- a/restapi/client.go +++ b/restapi/client.go @@ -53,6 +53,7 @@ type MinioClient interface { removeBucket(ctx context.Context, bucketName string) error getBucketNotification(ctx context.Context, bucketName string) (config notification.Configuration, err error) getBucketPolicy(ctx context.Context, bucketName string) (string, error) + listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo } // Interface implementation @@ -110,6 +111,11 @@ func (c minioClient) getBucketReplication(ctx context.Context, bucketName string return c.client.GetBucketReplication(ctx, bucketName) } +// implements minio.listObjects(ctx) +func (c minioClient) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo { + return c.client.ListObjects(ctx, bucket, opts) +} + // MCClient interface with all functions to be implemented // by mock when testing, it should include all mc/S3Client respective api calls // that are used within this project. diff --git a/restapi/configure_console.go b/restapi/configure_console.go index 86a159fde1..94cda68a22 100644 --- a/restapi/configure_console.go +++ b/restapi/configure_console.go @@ -118,6 +118,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler { registerNodesHandlers(api) // Register Parity' handlers registerParityHandlers(api) + // Register Object's Handlers + registerObjectsHandlers(api) api.PreServerShutdown = func() {} diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 6ef4379de3..957ba14da5 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -341,6 +341,47 @@ func init() { } } }, + "/buckets/{bucket_name}/objects": { + "get": { + "tags": [ + "UserAPI" + ], + "summary": "List Objects", + "operationId": "ListObjects", + "parameters": [ + { + "type": "string", + "name": "bucket_name", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "prefix", + "in": "query" + }, + { + "type": "boolean", + "name": "recursive", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/listObjectsResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/buckets/{bucket_name}/replication": { "get": { "tags": [ @@ -2413,6 +2454,24 @@ func init() { } } }, + "bucketObject": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "last_modified": { + "type": "string" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer", + "format": "int64" + } + } + }, "bucketReplicationDestination": { "type": "object", "properties": { @@ -2978,6 +3037,23 @@ func init() { } } }, + "listObjectsResponse": { + "type": "object", + "properties": { + "objects": { + "type": "array", + "title": "list of resulting objects", + "items": { + "$ref": "#/definitions/bucketObject" + } + }, + "total": { + "type": "integer", + "format": "int64", + "title": "number of objects" + } + } + }, "listPoliciesResponse": { "type": "object", "properties": { @@ -4505,6 +4581,47 @@ func init() { } } }, + "/buckets/{bucket_name}/objects": { + "get": { + "tags": [ + "UserAPI" + ], + "summary": "List Objects", + "operationId": "ListObjects", + "parameters": [ + { + "type": "string", + "name": "bucket_name", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "prefix", + "in": "query" + }, + { + "type": "boolean", + "name": "recursive", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/listObjectsResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/buckets/{bucket_name}/replication": { "get": { "tags": [ @@ -7100,6 +7217,24 @@ func init() { } } }, + "bucketObject": { + "type": "object", + "properties": { + "content_type": { + "type": "string" + }, + "last_modified": { + "type": "string" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer", + "format": "int64" + } + } + }, "bucketReplicationDestination": { "type": "object", "properties": { @@ -7665,6 +7800,23 @@ func init() { } } }, + "listObjectsResponse": { + "type": "object", + "properties": { + "objects": { + "type": "array", + "title": "list of resulting objects", + "items": { + "$ref": "#/definitions/bucketObject" + } + }, + "total": { + "type": "integer", + "format": "int64", + "title": "number of objects" + } + } + }, "listPoliciesResponse": { "type": "object", "properties": { diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go index 8bc00392ed..43967c93aa 100644 --- a/restapi/operations/console_api.go +++ b/restapi/operations/console_api.go @@ -162,6 +162,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { AdminAPIListGroupsHandler: admin_api.ListGroupsHandlerFunc(func(params admin_api.ListGroupsParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.ListGroups has not yet been implemented") }), + UserAPIListObjectsHandler: user_api.ListObjectsHandlerFunc(func(params user_api.ListObjectsParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation user_api.ListObjects has not yet been implemented") + }), AdminAPIListPoliciesHandler: admin_api.ListPoliciesHandlerFunc(func(params admin_api.ListPoliciesParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.ListPolicies has not yet been implemented") }), @@ -376,6 +379,8 @@ type ConsoleAPI struct { AdminAPIListConfigHandler admin_api.ListConfigHandler // AdminAPIListGroupsHandler sets the operation handler for the list groups operation AdminAPIListGroupsHandler admin_api.ListGroupsHandler + // UserAPIListObjectsHandler sets the operation handler for the list objects operation + UserAPIListObjectsHandler user_api.ListObjectsHandler // AdminAPIListPoliciesHandler sets the operation handler for the list policies operation AdminAPIListPoliciesHandler admin_api.ListPoliciesHandler // UserAPIListRemoteBucketsHandler sets the operation handler for the list remote buckets operation @@ -614,6 +619,9 @@ func (o *ConsoleAPI) Validate() error { if o.AdminAPIListGroupsHandler == nil { unregistered = append(unregistered, "admin_api.ListGroupsHandler") } + if o.UserAPIListObjectsHandler == nil { + unregistered = append(unregistered, "user_api.ListObjectsHandler") + } if o.AdminAPIListPoliciesHandler == nil { unregistered = append(unregistered, "admin_api.ListPoliciesHandler") } @@ -948,6 +956,10 @@ func (o *ConsoleAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/buckets/{bucket_name}/objects"] = user_api.NewListObjects(o.context, o.UserAPIListObjectsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/policies"] = admin_api.NewListPolicies(o.context, o.AdminAPIListPoliciesHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/restapi/operations/user_api/list_objects.go b/restapi/operations/user_api/list_objects.go new file mode 100644 index 0000000000..daf32bf12a --- /dev/null +++ b/restapi/operations/user_api/list_objects.go @@ -0,0 +1,90 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package user_api + +// 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/minio/console/models" +) + +// ListObjectsHandlerFunc turns a function with the right signature into a list objects handler +type ListObjectsHandlerFunc func(ListObjectsParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn ListObjectsHandlerFunc) Handle(params ListObjectsParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// ListObjectsHandler interface for that can handle valid list objects params +type ListObjectsHandler interface { + Handle(ListObjectsParams, *models.Principal) middleware.Responder +} + +// NewListObjects creates a new http.Handler for the list objects operation +func NewListObjects(ctx *middleware.Context, handler ListObjectsHandler) *ListObjects { + return &ListObjects{Context: ctx, Handler: handler} +} + +/*ListObjects swagger:route GET /buckets/{bucket_name}/objects UserAPI listObjects + +List Objects + +*/ +type ListObjects struct { + Context *middleware.Context + Handler ListObjectsHandler +} + +func (o *ListObjects) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + var Params = NewListObjectsParams() + + 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) + +} diff --git a/restapi/operations/user_api/list_objects_parameters.go b/restapi/operations/user_api/list_objects_parameters.go new file mode 100644 index 0000000000..da526f528a --- /dev/null +++ b/restapi/operations/user_api/list_objects_parameters.go @@ -0,0 +1,151 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewListObjectsParams creates a new ListObjectsParams object +// no default values defined in spec. +func NewListObjectsParams() ListObjectsParams { + + return ListObjectsParams{} +} + +// ListObjectsParams contains all the bound params for the list objects operation +// typically these are obtained from a http.Request +// +// swagger:parameters ListObjects +type ListObjectsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + BucketName string + /* + In: query + */ + Prefix *string + /* + In: query + */ + Recursive *bool +} + +// 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 NewListObjectsParams() beforehand. +func (o *ListObjectsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name") + if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil { + res = append(res, err) + } + + qPrefix, qhkPrefix, _ := qs.GetOK("prefix") + if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil { + res = append(res, err) + } + + qRecursive, qhkRecursive, _ := qs.GetOK("recursive") + if err := o.bindRecursive(qRecursive, qhkRecursive, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindBucketName binds and validates parameter BucketName from path. +func (o *ListObjectsParams) bindBucketName(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.BucketName = raw + + return nil +} + +// bindPrefix binds and validates parameter Prefix from query. +func (o *ListObjectsParams) bindPrefix(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 + return nil + } + + o.Prefix = &raw + + return nil +} + +// bindRecursive binds and validates parameter Recursive from query. +func (o *ListObjectsParams) bindRecursive(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 + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("recursive", "query", "bool", raw) + } + o.Recursive = &value + + return nil +} diff --git a/restapi/operations/user_api/list_objects_responses.go b/restapi/operations/user_api/list_objects_responses.go new file mode 100644 index 0000000000..f526c1a9dd --- /dev/null +++ b/restapi/operations/user_api/list_objects_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package user_api + +// 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/minio/console/models" +) + +// ListObjectsOKCode is the HTTP code returned for type ListObjectsOK +const ListObjectsOKCode int = 200 + +/*ListObjectsOK A successful response. + +swagger:response listObjectsOK +*/ +type ListObjectsOK struct { + + /* + In: Body + */ + Payload *models.ListObjectsResponse `json:"body,omitempty"` +} + +// NewListObjectsOK creates ListObjectsOK with default headers values +func NewListObjectsOK() *ListObjectsOK { + + return &ListObjectsOK{} +} + +// WithPayload adds the payload to the list objects o k response +func (o *ListObjectsOK) WithPayload(payload *models.ListObjectsResponse) *ListObjectsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the list objects o k response +func (o *ListObjectsOK) SetPayload(payload *models.ListObjectsResponse) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ListObjectsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*ListObjectsDefault Generic error response. + +swagger:response listObjectsDefault +*/ +type ListObjectsDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewListObjectsDefault creates ListObjectsDefault with default headers values +func NewListObjectsDefault(code int) *ListObjectsDefault { + if code <= 0 { + code = 500 + } + + return &ListObjectsDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the list objects default response +func (o *ListObjectsDefault) WithStatusCode(code int) *ListObjectsDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the list objects default response +func (o *ListObjectsDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the list objects default response +func (o *ListObjectsDefault) WithPayload(payload *models.Error) *ListObjectsDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the list objects default response +func (o *ListObjectsDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ListObjectsDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/user_api/list_objects_urlbuilder.go b/restapi/operations/user_api/list_objects_urlbuilder.go new file mode 100644 index 0000000000..2ed04e2754 --- /dev/null +++ b/restapi/operations/user_api/list_objects_urlbuilder.go @@ -0,0 +1,141 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +package user_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/swag" +) + +// ListObjectsURL generates an URL for the list objects operation +type ListObjectsURL struct { + BucketName string + + Prefix *string + Recursive *bool + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *ListObjectsURL) WithBasePath(bp string) *ListObjectsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *ListObjectsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *ListObjectsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/buckets/{bucket_name}/objects" + + bucketName := o.BucketName + if bucketName != "" { + _path = strings.Replace(_path, "{bucket_name}", bucketName, -1) + } else { + return nil, errors.New("bucketName is required on ListObjectsURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var prefixQ string + if o.Prefix != nil { + prefixQ = *o.Prefix + } + if prefixQ != "" { + qs.Set("prefix", prefixQ) + } + + var recursiveQ string + if o.Recursive != nil { + recursiveQ = swag.FormatBool(*o.Recursive) + } + if recursiveQ != "" { + qs.Set("recursive", recursiveQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *ListObjectsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *ListObjectsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *ListObjectsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on ListObjectsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on ListObjectsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *ListObjectsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/user_objects.go b/restapi/user_objects.go new file mode 100644 index 0000000000..6bbb911447 --- /dev/null +++ b/restapi/user_objects.go @@ -0,0 +1,93 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package restapi + +import ( + "context" + "time" + + "github.com/go-openapi/runtime/middleware" + "github.com/minio/console/models" + "github.com/minio/console/restapi/operations" + "github.com/minio/console/restapi/operations/user_api" + "github.com/minio/minio-go/v7" +) + +func registerObjectsHandlers(api *operations.ConsoleAPI) { + // list objects + api.UserAPIListObjectsHandler = user_api.ListObjectsHandlerFunc(func(params user_api.ListObjectsParams, session *models.Principal) middleware.Responder { + resp, err := getListObjectsResponse(session, params) + if err != nil { + return user_api.NewListObjectsDefault(int(err.Code)).WithPayload(err) + } + return user_api.NewListObjectsOK().WithPayload(resp) + }) +} + +// listBucketObjects gets an array of objects in a bucket +func listBucketObjects(ctx context.Context, client MinioClient, bucketName string, prefix string, recursive bool) ([]*models.BucketObject, error) { + var objects []*models.BucketObject + for lsObj := range client.listObjects(ctx, bucketName, minio.ListObjectsOptions{Prefix: prefix, Recursive: recursive}) { + if lsObj.Err != nil { + return nil, lsObj.Err + } + obj := &models.BucketObject{ + Name: lsObj.Key, + Size: lsObj.Size, + LastModified: lsObj.LastModified.String(), + ContentType: lsObj.ContentType, + } + objects = append(objects, obj) + } + return objects, nil +} + +// getListObjectsResponse returns a list of objects +func getListObjectsResponse(session *models.Principal, params user_api.ListObjectsParams) (*models.ListObjectsResponse, *models.Error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + var prefix string + var recursive bool + if params.Prefix != nil { + prefix = *params.Prefix + } + if params.Recursive != nil { + recursive = *params.Recursive + } + // bucket request needed to proceed + if params.BucketName == "" { + return nil, prepareError(errBucketNameNotInRequest) + } + mClient, err := newMinioClient(session) + if err != nil { + return nil, prepareError(err) + } + // create a minioClient interface implementation + // defining the client to be used + minioClient := minioClient{client: mClient} + + objs, err := listBucketObjects(ctx, minioClient, params.BucketName, prefix, recursive) + if err != nil { + return nil, prepareError(err) + } + + resp := &models.ListObjectsResponse{ + Objects: objs, + Total: int64(len(objs)), + } + return resp, nil +} diff --git a/restapi/user_objects_test.go b/restapi/user_objects_test.go new file mode 100644 index 0000000000..3e3c84c083 --- /dev/null +++ b/restapi/user_objects_test.go @@ -0,0 +1,162 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2020 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package restapi + +import ( + "context" + "encoding/json" + "errors" + "reflect" + "testing" + "time" + + "github.com/minio/console/models" + "github.com/minio/minio-go/v7" +) + +var minioListObjectsMock func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo + +// mock function of listObjects() needed for list objects + +func (ac minioClientMock) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo { + return minioListObjectsMock(ctx, bucket, opts) +} + +func Test_listObjects(t *testing.T) { + ctx := context.Background() + t1 := time.Now() + minClient := minioClientMock{} + type args struct { + bucketName string + prefix string + recursive bool + listFunc func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo + } + tests := []struct { + test string + args args + expectedResp []*models.BucketObject + wantError error + }{ + { + test: "Return objects", + args: args{ + bucketName: "bucket1", + prefix: "prefix", + recursive: true, + listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo { + objectStatCh := make(chan minio.ObjectInfo, 1) + go func(objectStatCh chan<- minio.ObjectInfo) { + defer close(objectStatCh) + for _, bucket := range []minio.ObjectInfo{ + minio.ObjectInfo{ + Key: "obj1", + LastModified: t1, + Size: int64(1024), + ContentType: "content", + }, + minio.ObjectInfo{ + Key: "obj2", + LastModified: t1, + Size: int64(512), + ContentType: "content", + }, + } { + objectStatCh <- bucket + } + }(objectStatCh) + return objectStatCh + }, + }, + expectedResp: []*models.BucketObject{ + &models.BucketObject{ + Name: "obj1", + LastModified: t1.String(), + Size: int64(1024), + ContentType: "content", + }, &models.BucketObject{ + Name: "obj2", + LastModified: t1.String(), + Size: int64(512), + ContentType: "content", + }, + }, + wantError: nil, + }, + { + test: "Return zero objects", + args: args{ + bucketName: "bucket1", + prefix: "prefix", + recursive: true, + listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo { + objectStatCh := make(chan minio.ObjectInfo, 1) + defer close(objectStatCh) + return objectStatCh + }, + }, + expectedResp: nil, + wantError: nil, + }, + { + test: "Handle error if present on object", + args: args{ + bucketName: "bucket1", + prefix: "prefix", + recursive: true, + listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo { + objectStatCh := make(chan minio.ObjectInfo, 1) + go func(objectStatCh chan<- minio.ObjectInfo) { + defer close(objectStatCh) + for _, bucket := range []minio.ObjectInfo{ + minio.ObjectInfo{ + Key: "obj2", + LastModified: t1, + Size: int64(512), + ContentType: "content", + }, + minio.ObjectInfo{ + Err: errors.New("error here"), + }, + } { + objectStatCh <- bucket + } + }(objectStatCh) + return objectStatCh + }, + }, + expectedResp: nil, + wantError: errors.New("error here"), + }, + } + + for _, tt := range tests { + t.Run(tt.test, func(t *testing.T) { + minioListObjectsMock = tt.args.listFunc + resp, err := listBucketObjects(ctx, minClient, tt.args.bucketName, tt.args.prefix, tt.args.recursive) + if !reflect.DeepEqual(err, tt.wantError) { + t.Errorf("listBucketObjects() error: %v, wantErr: %v", err, tt.wantError) + return + } + if !reflect.DeepEqual(resp, tt.expectedResp) { + ji, _ := json.Marshal(resp) + vi, _ := json.Marshal(tt.expectedResp) + t.Errorf("\ngot: %s \nwant: %s", ji, vi) + } + }) + } +} diff --git a/swagger.yml b/swagger.yml index baf2e12477..1766688a33 100644 --- a/swagger.yml +++ b/swagger.yml @@ -223,6 +223,35 @@ paths: tags: - UserAPI + /buckets/{bucket_name}/objects: + get: + summary: List Objects + operationId: ListObjects + parameters: + - name: bucket_name + in: path + required: true + type: string + - name: prefix + in: query + required: false + type: string + - name: recursive + in: query + required: false + type: boolean + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/listObjectsResponse" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - UserAPI + /buckets/{name}/set-policy: put: summary: Bucket Set Policy @@ -335,10 +364,10 @@ paths: summary: Bucket Replication operationId: GetBucketReplication parameters: - - name: bucket_name - in: path - required: true - type: string + - name: bucket_name + in: path + required: true + type: string responses: 200: description: A successful response. @@ -1503,6 +1532,7 @@ definitions: - PUBLIC - CUSTOM default: PRIVATE + bucket: type: object required: @@ -1518,6 +1548,7 @@ definitions: $ref: "#/definitions/bucketAccess" creation_date: type: string + listBucketsResponse: type: object properties: @@ -1530,6 +1561,33 @@ definitions: type: integer format: int64 title: number of buckets accessible to tenant user + + listObjectsResponse: + type: object + properties: + objects: + type: array + items: + $ref: "#/definitions/bucketObject" + title: list of resulting objects + total: + type: integer + format: int64 + title: number of objects + + bucketObject: + type: object + properties: + name: + type: string + size: + type: integer + format: int64 + content_type: + type: string + last_modified: + type: string + makeBucketRequest: type: object required: @@ -2298,7 +2356,7 @@ definitions: $ref: "#/definitions/encryptionConfiguration" console: type: object - $ref: '#/definitions/consoleConfiguration' + $ref: "#/definitions/consoleConfiguration" metadataFields: type: object @@ -2379,7 +2437,7 @@ definitions: consoleConfiguration: allOf: - - $ref: '#/definitions/metadataFields' + - $ref: "#/definitions/metadataFields" - type: object properties: image: @@ -2387,7 +2445,7 @@ definitions: encryptionConfiguration: allOf: - - $ref: '#/definitions/metadataFields' + - $ref: "#/definitions/metadataFields" - type: object properties: image: