diff --git a/changelog/unreleased/enchancement-cback-http-service.md b/changelog/unreleased/enchancement-cback-http-service.md new file mode 100644 index 0000000000..6e71bb1c85 --- /dev/null +++ b/changelog/unreleased/enchancement-cback-http-service.md @@ -0,0 +1,5 @@ +Enhancement: Implementation of HTTP Service for cback + +This implementation allows for creation of new restore jobs, as well as the ability to GET information regarding particular jobs + +https://github.com/cs3org/reva/pull/3153 \ No newline at end of file diff --git a/changelog/unreleased/enhancement-cback-storage-driver.md b/changelog/unreleased/enhancement-cback-storage-driver.md deleted file mode 100644 index 2e36b97651..0000000000 --- a/changelog/unreleased/enhancement-cback-storage-driver.md +++ /dev/null @@ -1,5 +0,0 @@ -Enhancement: Implementation of cback storage driver for REVA - -This is a read only fs interface. - -https://github.com/cs3org/reva/pull/3116 \ No newline at end of file diff --git a/internal/http/services/cback/cback.go b/internal/http/services/cback/cback.go new file mode 100644 index 0000000000..635067de4f --- /dev/null +++ b/internal/http/services/cback/cback.go @@ -0,0 +1,320 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package cback + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rhttp" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/go-chi/chi/v5" + "github.com/mitchellh/mapstructure" + "github.com/rs/zerolog" +) + +type requestTemp struct { + BackupID int `json:"backup_id"` + Pattern string `json:"pattern"` + Snapshot string `json:"snapshot"` + // destination string + // enabled bool + // date string +} + +func init() { + global.Register("cback", New) +} + +// New returns a new helloworld service +func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) { + conf := &config{} + if err := mapstructure.Decode(m, conf); err != nil { + return nil, err + } + + conf.init() + r := chi.NewRouter() + s := &svc{ + conf: conf, + router: r, + client: rhttp.GetHTTPClient(), + } + + if err := s.routerInit(); err != nil { + return nil, err + } + + return s, nil + +} + +// Close performs cleanup. +func (s *svc) Close() error { + return nil +} + +type config struct { + Prefix string `mapstructure:"prefix"` + ImpersonatorToken string `mapstructure:"token"` + APIURL string `mapstructure:"api_url"` +} + +func (c *config) init() { + + if c.Prefix == "" { + c.Prefix = "cback" + } +} + +type svc struct { + conf *config + router *chi.Mux + client *http.Client +} + +func (s *svc) Prefix() string { + return s.conf.Prefix +} + +func (s *svc) Unprotected() []string { + return nil +} + +func (s *svc) routerInit() error { + + s.router.Get("/restore", s.handleListJobs) + s.router.Post("/restore", s.handleRestoreID) + s.router.Get("/restore/{restore_id}", s.handleRestoreStatus) + return nil +} + +func (s *svc) handleRestoreID(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + user, inContext := ctxpkg.ContextGetUser(ctx) + + if !inContext { + http.Error(w, errtypes.UserRequired("no user found in context").Error(), http.StatusInternalServerError) + return + } + + url := s.conf.APIURL + "/restores/" + var ssID, searchPath string + + path := r.URL.Query().Get("path") + + if path == "" { + http.Error(w, "The id query parameter is missing", http.StatusBadRequest) + return + } + + resp, err := s.matchBackups(user.Username, path) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if resp == nil { + http.Error(w, errtypes.NotFound("cback: not found").Error(), http.StatusInternalServerError) + return + } + + snapshotList, err := s.listSnapshots(user.Username, resp.ID) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if resp.Substring != "" { + ssID, searchPath = s.pathTrimmer(snapshotList, resp) + + if ssID == "" { + http.Error(w, errtypes.NotFound("cback: snapshot not found").Error(), http.StatusNotFound) + return + } + + err = s.checkFileType(resp.ID, ssID, user.Username, searchPath, resp.Source) + + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + structbody := &requestTemp{ + BackupID: resp.ID, + Snapshot: ssID, + Pattern: searchPath, + } + + jbody, err := json.Marshal(structbody) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resp, err := s.getRequest(user.Username, url, http.MethodPost, bytes.NewBuffer(jbody)) + + if err != nil { + switch err { + case errtypes.NotFound("cback: resource not found"): + http.Error(w, err.Error(), http.StatusNotFound) + return + case errtypes.PermissionDenied("cback: user has no permissions to get the backup"): + http.Error(w, err.Error(), http.StatusForbidden) + return + case errtypes.BadRequest("cback"): + http.Error(w, err.Error(), http.StatusBadRequest) + return + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + defer resp.Close() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(resp) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if _, err := io.Copy(w, resp); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + } else { + + err = errtypes.NotFound("cback: resource not found") + http.Error(w, err.Error(), http.StatusNotFound) + return + } +} + +func (s *svc) handleListJobs(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + user, inContext := ctxpkg.ContextGetUser(ctx) + + if !inContext { + http.Error(w, errtypes.UserRequired("no user found in context").Error(), http.StatusInternalServerError) + return + } + + url := s.conf.APIURL + "/restores/" + + resp, err := s.getRequest(user.Username, url, http.MethodGet, nil) + + if err != nil { + switch err { + case errtypes.NotFound("cback: resource not found"): + http.Error(w, err.Error(), http.StatusNotFound) + return + case errtypes.PermissionDenied("cback: user has no permissions to get the backup"): + http.Error(w, err.Error(), http.StatusForbidden) + return + case errtypes.BadRequest("cback"): + http.Error(w, err.Error(), http.StatusBadRequest) + return + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + defer resp.Close() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(resp) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if _, err := io.Copy(w, resp); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + +} + +func (s *svc) handleRestoreStatus(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + user, inContext := ctxpkg.ContextGetUser(ctx) + + if !inContext { + http.Error(w, errtypes.UserRequired("no user found in context").Error(), http.StatusInternalServerError) + return + } + + restoreID := chi.URLParam(r, "restore_id") + + url := s.conf.APIURL + "/restores/" + restoreID + resp, err := s.getRequest(user.Username, url, http.MethodGet, nil) + + if err != nil { + switch err { + case errtypes.NotFound("cback: resource not found"): + http.Error(w, err.Error(), http.StatusNotFound) + return + case errtypes.PermissionDenied("cback: user has no permissions to get the backup"): + http.Error(w, err.Error(), http.StatusForbidden) + return + case errtypes.BadRequest("cback"): + http.Error(w, err.Error(), http.StatusBadRequest) + return + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + defer resp.Close() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(resp) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + if _, err := io.Copy(w, resp); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func (s *svc) Handler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s.router.ServeHTTP(w, r) + }) +} diff --git a/internal/http/services/cback/utilities.go b/internal/http/services/cback/utilities.go new file mode 100644 index 0000000000..0288bf1ca7 --- /dev/null +++ b/internal/http/services/cback/utilities.go @@ -0,0 +1,193 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package cback + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "strconv" + "strings" + + "github.com/cs3org/reva/pkg/errtypes" +) + +type backUpResponse struct { + Detail string `json:"detail"` + ID int `json:"id"` + Name string `json:"name"` + Source string `json:"source"` + Substring string +} + +type snapshotResponse struct { + Detail string `json:"detail"` + ID string `json:"id"` + Time string `json:"time"` + Paths []string `json:"paths"` +} + +func (s *svc) pathTrimmer(snapshotList []snapshotResponse, resp *backUpResponse) (string, string) { + + var ssID, searchPath string + for _, snapshot := range snapshotList { + + if snapshot.ID == resp.Substring { + ssID = resp.Substring + searchPath = resp.Source + break + + } else if strings.HasPrefix(resp.Substring, snapshot.ID) { + searchPath = strings.TrimPrefix(resp.Substring, snapshot.ID) + searchPath = resp.Source + searchPath + ssID = snapshot.ID + break + } + } + + return ssID, searchPath +} + +func (s *svc) listSnapshots(userName string, backupID int) ([]snapshotResponse, error) { + + url := s.conf.APIURL + "/backups/" + strconv.Itoa(backupID) + "/snapshots" + responseData, err := s.getRequest(userName, url, http.MethodGet, nil) + + if err != nil { + return nil, err + } + + defer responseData.Close() + + /*Unmarshalling the JSON response into the Response struct*/ + responseObject := []snapshotResponse{} + err = json.NewDecoder(responseData).Decode(&responseObject) + + if err != nil { + return nil, err + } + + return responseObject, nil +} + +func (s *svc) matchBackups(userName, pathInput string) (*backUpResponse, error) { + + url := s.conf.APIURL + "/backups/" + responseData, err := s.getRequest(userName, url, http.MethodGet, nil) + + if err != nil { + return nil, err + } + + defer responseData.Close() + + /*Unmarshalling the JSON response into the Response struct*/ + responseObject := []backUpResponse{} + err = json.NewDecoder(responseData).Decode(&responseObject) + + if err != nil { + return nil, err + } + + for _, response := range responseObject { + if response.Detail != "" { + err = errors.New(response.Detail) + return nil, err + } + + if strings.Compare(pathInput, response.Source) == 0 { + return &response, nil + } + } + + for _, response := range responseObject { + if response.Detail != "" { + err = errors.New(response.Detail) + return nil, err + } + + if strings.HasPrefix(pathInput, response.Source) { + substr := strings.TrimPrefix(pathInput, response.Source) + substr = strings.TrimLeft(substr, "/") + response.Substring = substr + return &response, nil + } + } + + /*If there is no error, but also no match found in the backup path the response is nil. + This means that the LSFolder function will know that no match has been found using the exact path, + and will therefore start checking if there is a substring of the backup job included in the inputted path*/ + return nil, nil +} + +func (s *svc) checkFileType(backupID int, snapID, userName, path, source string) error { + + url := s.conf.APIURL + "/backups/" + strconv.Itoa(backupID) + "/snapshots/" + snapID + "/" + path + "?content=false" + + responseData, err := s.getRequest(userName, url, http.MethodOptions, nil) + + if err != nil { + return err + } + + defer responseData.Close() + + return nil +} + +func (s *svc) getRequest(userName, url string, reqType string, body io.Reader) (io.ReadCloser, error) { + + req, err := http.NewRequest(reqType, url, body) + + if err != nil { + return nil, err + } + + req.SetBasicAuth(userName, s.conf.ImpersonatorToken) + + if body != nil { + req.Header.Add("Content-Type", "application/json") + } + + req.Header.Add("accept", `application/json`) + + resp, err := s.client.Do(req) + + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + + switch resp.StatusCode { + case http.StatusNotFound: + return nil, errtypes.NotFound("cback: resource not found") + case http.StatusForbidden: + return nil, errtypes.PermissionDenied("cback: user has no permissions to get the backup") + case http.StatusBadRequest: + return nil, errtypes.BadRequest("cback") + default: + return nil, errtypes.InternalError("cback: internal server error") + } + + } + + return resp.Body, nil +} diff --git a/internal/http/services/loader/loader.go b/internal/http/services/loader/loader.go index 6de231b478..34ffb8b1ff 100644 --- a/internal/http/services/loader/loader.go +++ b/internal/http/services/loader/loader.go @@ -22,6 +22,7 @@ import ( // Load core HTTP services _ "github.com/cs3org/reva/internal/http/services/appprovider" _ "github.com/cs3org/reva/internal/http/services/archiver" + _ "github.com/cs3org/reva/internal/http/services/cback" _ "github.com/cs3org/reva/internal/http/services/datagateway" _ "github.com/cs3org/reva/internal/http/services/dataprovider" _ "github.com/cs3org/reva/internal/http/services/helloworld" diff --git a/pkg/storage/fs/cback/cback.go b/pkg/storage/fs/cback/cback.go deleted file mode 100644 index 22a60e106f..0000000000 --- a/pkg/storage/fs/cback/cback.go +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package cback - -import ( - "context" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "time" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - v1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - ctxpkg "github.com/cs3org/reva/pkg/ctx" - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/mime" - "github.com/cs3org/reva/pkg/rhttp" - "github.com/cs3org/reva/pkg/storage" - "github.com/cs3org/reva/pkg/storage/fs/registry" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" -) - -type cback struct { - conf *Options - client *http.Client -} - -func init() { - registry.Register("cback", New) -} - -// New returns an implementation to the storage.FS interface that talks to -// cback -func New(m map[string]interface{}) (fs storage.FS, err error) { - - c := &Options{} - if err = mapstructure.Decode(m, c); err != nil { - return nil, errors.Wrap(err, "Error Decoding Configuration") - } - - httpClient := rhttp.GetHTTPClient() - - // Returns the storage.FS interface - return &cback{conf: c, client: httpClient}, nil - -} - -func (fs *cback) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (*provider.ResourceInfo, error) { - var ssID, searchPath string - - user, inContext := ctxpkg.ContextGetUser(ctx) - - if !inContext { - return nil, errtypes.UserRequired("user not found in context") - } - - resp, err := fs.matchBackups(user.Username, ref.Path) - - if err != nil { - return nil, err - } - - snapshotList, err := fs.listSnapshots(user.Username, resp.ID) - - if err != nil { - return nil, err - } - - ssID, searchPath = fs.pathTrimmer(snapshotList, resp) - - if resp.Source == ref.Path { - setTime := v1beta1.Timestamp{ - Seconds: uint64(time.Now().Unix()), - Nanos: 0, - } - - ident := provider.ResourceId{ - OpaqueId: ref.Path, - StorageId: "cback", - } - - ri := &provider.ResourceInfo{ - Etag: "", - PermissionSet: &permID, - Checksum: &checkSum, - Mtime: &setTime, - Id: &ident, - Owner: user.Id, - Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, - Size: 0, - Path: ref.Path, - MimeType: mime.Detect(true, ref.Path), - } - - return ri, nil - - } - - ret, err := fs.statResource(resp.ID, ssID, user.Username, searchPath, resp.Source) - - if err != nil { - return nil, err - } - - setTime := v1beta1.Timestamp{ - Seconds: ret.Mtime, - Nanos: 0, - } - - ident := provider.ResourceId{ - OpaqueId: ret.Path, - StorageId: "cback", - } - - ri := &provider.ResourceInfo{ - Etag: "", - PermissionSet: &permID, - Checksum: &checkSum, - Mtime: &setTime, - Id: &ident, - Owner: user.Id, - Type: ret.Type, - Size: ret.Size, - Path: ret.Path, - } - - if ret.Type == 2 { - ri.MimeType = mime.Detect(true, ret.Path) - } else { - ri.MimeType = mime.Detect(false, ret.Path) - } - - return ri, nil -} - -func (fs *cback) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) { - - var path string = ref.GetPath() - var ssID, searchPath string - - user, inContext := ctxpkg.ContextGetUser(ctx) - - if !inContext { - return nil, errtypes.UserRequired("no user found in context") - } - - resp, err := fs.matchBackups(user.Username, path) - - if err != nil { - return nil, err - } - - // Code executed before snapshot being inputted - if resp == nil { - pathList, err := fs.pathFinder(user.Username, ref.Path) - - if err != nil { - return nil, err - } - - files := make([]*provider.ResourceInfo, 0, len(pathList)) - - for _, paths := range pathList { - - setTime := v1beta1.Timestamp{ - Seconds: 0, - Nanos: 0, - } - - ident := provider.ResourceId{ - OpaqueId: paths, - StorageId: "cback", - } - - f := provider.ResourceInfo{ - Mtime: &setTime, - Id: &ident, - Checksum: &checkSum, - Path: paths, - Owner: user.Id, - PermissionSet: &permID, - Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, - Size: 0, - Etag: "", - MimeType: mime.Detect(true, paths), - } - files = append(files, &f) - } - - return files, nil - } - - snapshotList, err := fs.listSnapshots(user.Username, resp.ID) - - if err != nil { - fmt.Print(err) - return nil, err - } - - if resp.Substring != "" { - ssID, searchPath = fs.pathTrimmer(snapshotList, resp) - - if ssID == "" { - return nil, errtypes.NotFound("cback: snapshotID invalid") - } - - // If no match in path, therefore prints the files - fmt.Printf("The ssID is: %v\nThe Path is %v\n", ssID, searchPath) - ret, err := fs.fileSystem(resp.ID, ssID, user.Username, searchPath, resp.Source) - - if err != nil { - fmt.Print(err) - return nil, err - } - - files := make([]*provider.ResourceInfo, 0, len(ret)) - - for _, j := range ret { - - setTime := v1beta1.Timestamp{ - Seconds: j.Mtime, - Nanos: 0, - } - - ident := provider.ResourceId{ - OpaqueId: j.Path, - StorageId: "cback", - } - - f := provider.ResourceInfo{ - Mtime: &setTime, - Id: &ident, - Checksum: &checkSum, - Path: j.Path, - Owner: user.Id, - PermissionSet: &permID, - Type: j.Type, - Size: j.Size, - Etag: "", - } - - if j.Type == 2 { - f.MimeType = mime.Detect(true, j.Path) - } else { - f.MimeType = mime.Detect(false, j.Path) - } - - files = append(files, &f) - } - - return files, nil - - } - - // If match in path, therefore print the Snapshot IDs - files := make([]*provider.ResourceInfo, 0, len(snapshotList)) - - for _, snapshot := range snapshotList { - - epochTime, err := fs.timeConv(snapshot.Time) - - if err != nil { - return nil, err - } - - ident := provider.ResourceId{ - OpaqueId: ref.Path + "/" + snapshot.ID, - StorageId: "cback", - } - - setTime := v1beta1.Timestamp{ - Seconds: uint64(epochTime), - Nanos: 0, - } - - f := provider.ResourceInfo{ - Path: ref.Path + "/" + snapshot.ID, - Checksum: &checkSum, - Etag: "", - Owner: user.Id, - PermissionSet: &permID, - Id: &ident, - MimeType: mime.Detect(true, ref.Path+"/"+snapshot.ID), - Size: 0, - Mtime: &setTime, - Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER, - } - - files = append(files, &f) - - } - - return files, nil - -} - -func (fs *cback) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { - var path string = ref.GetPath() - var ssID, searchPath string - user, _ := ctxpkg.ContextGetUser(ctx) - - resp, err := fs.matchBackups(user.Username, path) - - if err != nil { - fmt.Print(err) - return nil, err - } - - snapshotList, err := fs.listSnapshots(user.Username, resp.ID) - - if err != nil { - fmt.Print(err) - return nil, err - } - - if resp.Substring != "" { - - ssID, searchPath = fs.pathTrimmer(snapshotList, resp) - - url := fs.conf.APIURL + "/backups/" + strconv.Itoa(resp.ID) + "/snapshots/" + ssID + "/" + searchPath - requestType := "GET" - md, err := fs.GetMD(ctx, ref, nil) - - if err != nil { - return nil, err - } - - if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { - - responseData, err := fs.getRequest(user.Username, url, requestType, nil) - - if err != nil { - return nil, err - } - - return responseData, nil - } - - return nil, errtypes.BadRequest("can only download files") - - } - - return nil, errtypes.NotFound("cback: resource not found") -} - -func (fs *cback) GetHome(ctx context.Context) (string, error) { - return "", errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) CreateHome(ctx context.Context) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) CreateDir(ctx context.Context, ref *provider.Reference) error { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) TouchFile(ctx context.Context, ref *provider.Reference) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) Delete(ctx context.Context, ref *provider.Reference) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) Move(ctx context.Context, oldRef, newRef *provider.Reference) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) ListRevisions(ctx context.Context, ref *provider.Reference) (fvs []*provider.FileVersion, err error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (file io.ReadCloser, err error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) GetPathByID(ctx context.Context, id *provider.ResourceId) (str string, err error) { - return "", errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) ListGrants(ctx context.Context, ref *provider.Reference) (glist []*provider.Grant, err error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) GetQuota(ctx context.Context, ref *provider.Reference) (total uint64, used uint64, err error) { - return 0, 0, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) CreateReference(ctx context.Context, path string, targetURI *url.URL) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) Shutdown(ctx context.Context) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) EmptyRecycle(ctx context.Context) error { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (r *provider.CreateStorageSpaceResponse, err error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) ListRecycle(ctx context.Context, basePath, key, relativePath string) ([]*provider.RecycleItem, error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) RestoreRecycleItem(ctx context.Context, basePath, key, relativePath string, restoreRef *provider.Reference) error { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) PurgeRecycleItem(ctx context.Context, basePath, key, relativePath string) error { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { - return nil, errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - return errtypes.NotSupported("Operation Not Permitted") -} - -func (fs *cback) Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error { - return errtypes.NotSupported("Operation Not Permitted") - -} - -func (fs *cback) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { - return nil, errtypes.NotSupported("Operation Not Permitted") - -} diff --git a/pkg/storage/fs/cback/options.go b/pkg/storage/fs/cback/options.go deleted file mode 100644 index 098905fa7b..0000000000 --- a/pkg/storage/fs/cback/options.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package cback - -// Options for the CBACK module -type Options struct { - ImpersonatorToken string `mapstructure:"token"` - APIURL string `mapstructure:"api_url"` -} diff --git a/pkg/storage/fs/cback/utilities.go b/pkg/storage/fs/cback/utilities.go deleted file mode 100644 index 4be7bfe579..0000000000 --- a/pkg/storage/fs/cback/utilities.go +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package cback - -import ( - "encoding/json" - "errors" - "io" - "net/http" - "strconv" - "strings" - "time" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/pkg/errtypes" -) - -type backUpResponse struct { - Detail string `json:"detail"` - ID int `json:"id"` - Name string `json:"name"` - Source string `json:"source"` - Substring string // Used in function -} - -type snapshotResponse struct { - Detail string `json:"detail"` - ID string `json:"id"` - Time string `json:"time"` - Paths []string `json:"paths"` -} - -type contents struct { - Name string `json:"name"` - Type string `json:"type"` - Mode uint64 `json:"mode"` - Mtime float64 `json:"mtime"` - Atime float64 `json:"atime"` - Ctime float64 `json:"ctime"` - Inode uint64 `json:"inode"` - Size uint64 `json:"size"` - Detail string `json:"detail"` -} - -type fsReturn struct { - Type provider.ResourceType - Mtime uint64 - Size uint64 - Path string - Detail string -} - -var permID = provider.ResourcePermissions{ - AddGrant: false, - CreateContainer: false, - Delete: false, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: false, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: false, - Move: false, - RemoveGrant: false, - PurgeRecycle: false, - RestoreFileVersion: true, - RestoreRecycleItem: false, - Stat: true, - UpdateGrant: false, - DenyGrant: false} - -var checkSum = provider.ResourceChecksum{ - Sum: "", - Type: provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_UNSET, -} - -func mapReturn(fileType string) (provider.ResourceType, error) { - /* This function can be changed accordingly, depending on the file type - being return by the APIs */ - - switch fileType { - case "file": - return provider.ResourceType_RESOURCE_TYPE_FILE, nil - - case "dir": - return provider.ResourceType_RESOURCE_TYPE_CONTAINER, nil - - default: - return provider.ResourceType_RESOURCE_TYPE_INVALID, errtypes.NotFound("Resource type unrecognized") - } -} - -func (fs *cback) getRequest(userName, url string, reqType string, body io.Reader) (io.ReadCloser, error) { - - req, err := http.NewRequest(reqType, url, body) - req.SetBasicAuth(userName, fs.conf.ImpersonatorToken) - - if err != nil { - return nil, err - } - - if body != nil { - req.Header.Add("Content-Type", "application/json") - } - - req.Header.Add("accept", `application/json`) - - resp, err := fs.client.Do(req) - - if err != nil { - return nil, err - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - - switch resp.StatusCode { - - case http.StatusNotFound: - return nil, errtypes.NotFound("cback: resource not found") - case http.StatusForbidden: - return nil, errtypes.PermissionDenied("cback: user has no permissions to get the backup") - case http.StatusBadRequest: - return nil, errtypes.BadRequest("cback") - default: - return nil, errtypes.InternalError("cback: internal server error") - } - } - - return resp.Body, nil - -} - -func (fs *cback) listSnapshots(userName string, backupID int) ([]snapshotResponse, error) { - - url := fs.conf.APIURL + "/backups/" + strconv.Itoa(backupID) + "/snapshots" - responseData, err := fs.getRequest(userName, url, http.MethodGet, nil) - - if err != nil { - return nil, err - } - - defer responseData.Close() - - /*Unmarshalling the JSON response into the Response struct*/ - responseObject := []snapshotResponse{} - - err = json.NewDecoder(responseData).Decode(&responseObject) - - if err != nil { - return nil, err - } - - return responseObject, nil -} - -func (fs *cback) matchBackups(userName, pathInput string) (*backUpResponse, error) { - - url := fs.conf.APIURL + "/backups/" - responseData, err := fs.getRequest(userName, url, http.MethodGet, nil) - - if err != nil { - return nil, err - } - - defer responseData.Close() - - /*Unmarshalling the JSON response into the Response struct*/ - responseObject := []backUpResponse{} - - err = json.NewDecoder(responseData).Decode(&responseObject) - - if err != nil { - return nil, err - } - - if len(responseObject) == 0 { - err = errors.New("no match found") - return nil, err - } - - for _, response := range responseObject { - if response.Detail != "" { - err = errors.New(response.Detail) - return nil, err - } - - if strings.Compare(pathInput, response.Source) == 0 { - return &response, nil - } - } - - for _, response := range responseObject { - if response.Detail != "" { - err = errors.New(response.Detail) - return nil, err - } - - if strings.HasPrefix(pathInput, response.Source) { - substr := strings.TrimPrefix(pathInput, response.Source) - substr = strings.TrimLeft(substr, "/") - response.Substring = substr - return &response, nil - } - } - - /*If there is no error, but also no match found in the backup path the response is nil. - This means that the LSFolder function will know that no match has been found using the exact path, - and will therefore start checking if there is a substring of the backup job included in the inputted path*/ - return nil, nil -} - -func (fs *cback) statResource(backupID int, snapID, userName, path, source string) (*fsReturn, error) { - - url := fs.conf.APIURL + "/backups/" + strconv.Itoa(backupID) + "/snapshots/" + snapID + "/" + path + "?content=false" - responseData, err := fs.getRequest(userName, url, http.MethodOptions, nil) - - if err != nil { - return nil, err - } - - defer responseData.Close() - - responseObject := contents{} - - err = json.NewDecoder(responseData).Decode(&responseObject) - - if err != nil { - return nil, err - } - - m, err := mapReturn(responseObject.Type) - - if err != nil { - return nil, err - } - - retObject := fsReturn{ - Path: source + "/" + snapID + strings.TrimPrefix(responseObject.Name, source), - Type: m, - Mtime: uint64(responseObject.Mtime), - Size: responseObject.Size, - Detail: responseObject.Detail, - } - - return &retObject, nil -} - -func (fs *cback) fileSystem(backupID int, snapID, userName, path, source string) ([]*fsReturn, error) { - - url := fs.conf.APIURL + "/backups/" + strconv.Itoa(backupID) + "/snapshots/" + snapID + "/" + path + "?content=true" - responseData, err := fs.getRequest(userName, url, http.MethodOptions, nil) - - if err != nil { - return nil, err - } - - defer responseData.Close() - - /*Unmarshalling the JSON response into the Response struct*/ - responseObject := []contents{} - - err = json.NewDecoder(responseData).Decode(&responseObject) - - if err != nil { - return nil, err - } - - resp := make([]*fsReturn, 0, len(responseObject)) - - for _, response := range responseObject { - - m, err := mapReturn(response.Type) - - if err != nil { - return nil, err - } - - temp := fsReturn{ - Size: response.Size, - Type: m, - Mtime: uint64(response.Mtime), - Path: source + "/" + snapID + strings.TrimPrefix(response.Name, source), - } - - resp = append(resp, &temp) - } - - return resp, nil -} - -func (fs *cback) timeConv(timeInput string) (int64, error) { - tm, err := time.Parse(time.RFC3339, timeInput) - - if err != nil { - return 0, err - } - - epoch := tm.Unix() - return epoch, nil -} - -func (fs *cback) pathFinder(userName, path string) ([]string, error) { - url := fs.conf.APIURL + "/backups/" - responseData, err := fs.getRequest(userName, url, http.MethodGet, nil) - matchFound := false - - if err != nil { - return nil, err - } - - defer responseData.Close() - - /*Unmarshalling the JSON response into the Response struct*/ - responseObject := []backUpResponse{} - - err = json.NewDecoder(responseData).Decode(&responseObject) - - if err != nil { - return nil, err - } - - returnString := make([]string, 0, len(responseObject)) - - for _, response := range responseObject { - if response.Detail != "" { - err = errors.New(response.Detail) - return nil, err - } - - if strings.HasPrefix(response.Source, path) { - substr := strings.TrimPrefix(response.Source, path) - substr = strings.TrimLeft(substr, "/") - temp := strings.Split(substr, "/") - returnString = append(returnString, temp[0]) - matchFound = true - } - } - - if matchFound { - return duplicateRemoval(returnString), nil - } - - return nil, errtypes.NotFound("cback: resource not found") - -} - -func (fs *cback) pathTrimmer(snapshotList []snapshotResponse, resp *backUpResponse) (string, string) { - - var ssID, searchPath string - - for _, snapshot := range snapshotList { - - if snapshot.ID == resp.Substring { - ssID = resp.Substring - searchPath = resp.Source - break - - } else if strings.HasPrefix(resp.Substring, snapshot.ID) { - searchPath = strings.TrimPrefix(resp.Substring, snapshot.ID) - searchPath = resp.Source + searchPath - ssID = snapshot.ID - break - } - } - - return ssID, searchPath - -} - -func duplicateRemoval(strSlice []string) []string { - inList := make(map[string]bool) - var list []string - for _, str := range strSlice { - if _, value := inList[str]; !value { - inList[str] = true - list = append(list, str) - } - } - return list -} diff --git a/pkg/storage/fs/loader/loader.go b/pkg/storage/fs/loader/loader.go index e0b9f53575..cd88c5ddc7 100644 --- a/pkg/storage/fs/loader/loader.go +++ b/pkg/storage/fs/loader/loader.go @@ -20,7 +20,6 @@ package loader import ( // Load core storage filesystem backends. - _ "github.com/cs3org/reva/pkg/storage/fs/cback" _ "github.com/cs3org/reva/pkg/storage/fs/cephfs" _ "github.com/cs3org/reva/pkg/storage/fs/eos" _ "github.com/cs3org/reva/pkg/storage/fs/eosgrpc"