diff --git a/backend/oracleobjectstorage/command.go b/backend/oracleobjectstorage/command.go index 4be61e3e5..e6b079231 100644 --- a/backend/oracleobjectstorage/command.go +++ b/backend/oracleobjectstorage/command.go @@ -7,12 +7,15 @@ import ( "context" "fmt" "sort" + "strconv" "strings" + "sync" "time" "github.com/oracle/oci-go-sdk/v65/common" "github.com/oracle/oci-go-sdk/v65/objectstorage" "github.com/rclone/rclone/fs" + "github.com/rclone/rclone/fs/operations" ) // ------------------------------------------------------------ @@ -23,6 +26,7 @@ const ( operationRename = "rename" operationListMultiPart = "list-multipart-uploads" operationCleanup = "cleanup" + operationRestore = "restore" ) var commandHelp = []fs.CommandHelp{{ @@ -77,6 +81,42 @@ Durations are parsed as per the rest of rclone, 2h, 7d, 7w etc. Opts: map[string]string{ "max-age": "Max age of upload to delete", }, +}, { + Name: operationRestore, + Short: "Restore objects from Archive to Standard storage", + Long: `This command can be used to restore one or more objects from Archive to Standard storage. + + Usage Examples: + + rclone backend restore oos:bucket/path/to/directory -o hours=HOURS + rclone backend restore oos:bucket -o hours=HOURS + +This flag also obeys the filters. Test first with --interactive/-i or --dry-run flags + + rclone --interactive backend restore --include "*.txt" oos:bucket/path -o hours=72 + +All the objects shown will be marked for restore, then + + rclone backend restore --include "*.txt" oos:bucket/path -o hours=72 + + It returns a list of status dictionaries with Object Name and Status + keys. The Status will be "RESTORED"" if it was successful or an error message + if not. + + [ + { + "Object": "test.txt" + "Status": "RESTORED", + }, + { + "Object": "test/file4.txt" + "Status": "RESTORED", + } + ] +`, + Opts: map[string]string{ + "hours": "The number of hours for which this object will be restored. Default is 24 hrs.", + }, }, } @@ -113,6 +153,8 @@ func (f *Fs) Command(ctx context.Context, commandName string, args []string, } } return nil, f.cleanUp(ctx, maxAge) + case operationRestore: + return f.restore(ctx, opt) default: return nil, fs.ErrorCommandNotFound } @@ -290,3 +332,63 @@ func (f *Fs) listMultipartUploadParts(ctx context.Context, bucketName, bucketPat } return uploadedParts, nil } + +func (f *Fs) restore(ctx context.Context, opt map[string]string) (interface{}, error) { + req := objectstorage.RestoreObjectsRequest{ + NamespaceName: common.String(f.opt.Namespace), + RestoreObjectsDetails: objectstorage.RestoreObjectsDetails{}, + } + if hours := opt["hours"]; hours != "" { + ihours, err := strconv.Atoi(hours) + if err != nil { + return nil, fmt.Errorf("bad value for hours: %w", err) + } + req.RestoreObjectsDetails.Hours = &ihours + } + type status struct { + Object string + Status string + } + var ( + outMu sync.Mutex + out = []status{} + err error + ) + err = operations.ListFn(ctx, f, func(obj fs.Object) { + // Remember this is run --checkers times concurrently + o, ok := obj.(*Object) + st := status{Object: obj.Remote(), Status: "RESTORED"} + defer func() { + outMu.Lock() + out = append(out, st) + outMu.Unlock() + }() + if !ok { + st.Status = "Not an OCI Object Storage object" + return + } + if o.storageTier == nil || (*o.storageTier != "archive") { + st.Status = "Object not in Archive storage tier" + return + } + if operations.SkipDestructive(ctx, obj, "restore") { + return + } + bucket, bucketPath := o.split() + reqCopy := req + reqCopy.BucketName = &bucket + reqCopy.ObjectName = &bucketPath + var response objectstorage.RestoreObjectsResponse + err = f.pacer.Call(func() (bool, error) { + response, err = f.srv.RestoreObjects(ctx, reqCopy) + return shouldRetry(ctx, response.HTTPResponse(), err) + }) + if err != nil { + st.Status = err.Error() + } + }) + if err != nil { + return out, err + } + return out, nil +} diff --git a/docs/content/oracleobjectstorage.md b/docs/content/oracleobjectstorage.md index a418ad8b3..8ac76dc27 100644 --- a/docs/content/oracleobjectstorage.md +++ b/docs/content/oracleobjectstorage.md @@ -771,6 +771,47 @@ Options: - "max-age": Max age of upload to delete +### restore + +Restore objects from Archive to Standard storage + + rclone backend restore remote: [options] [+] + +This command can be used to restore one or more objects from Archive to Standard storage. + + Usage Examples: + + rclone backend restore oos:bucket/path/to/directory -o hours=HOURS + rclone backend restore oos:bucket -o hours=HOURS + +This flag also obeys the filters. Test first with --interactive/-i or --dry-run flags + + rclone --interactive backend restore --include "*.txt" oos:bucket/path -o hours=72 + +All the objects shown will be marked for restore, then + + rclone backend restore --include "*.txt" oos:bucket/path -o hours=72 + + It returns a list of status dictionaries with Object Name and Status + keys. The Status will be "RESTORED"" if it was successful or an error message + if not. + + [ + { + "Object": "test.txt" + "Status": "RESTORED", + }, + { + "Object": "test/file4.txt" + "Status": "RESTORED", + } + ] + + +Options: + +- "hours": The number of hours for which this object will be restored. Default is 24 hrs. + {{< rem autogenerated options stop >}} ## Tutorials