-
Notifications
You must be signed in to change notification settings - Fork 1
Implement missing storage-cli operations for AliCloud #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
565a6eb to
1699400
Compare
Missing Operations: copy, delete-recursive, list, properties, ensure-bucket-exists.
f6f5c44 to
2bfbdec
Compare
| ) error | ||
|
|
||
| DeleteRecursive( | ||
| dest string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's meant with "dest" here? A bucket or a folder?
| } | ||
|
|
||
| List( | ||
| prefix string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above: Is this a bucket name or can it also be a folder name?
| meta, err := dsc.bucket.GetObjectDetailedMeta(dest) | ||
| if err != nil { | ||
| var ossErr oss.ServiceError | ||
| if errors.As(err, &ossErr) && ossErr.StatusCode == 404 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean the blob exists, but it has no properties?
| objects = append(objects, obj.Key) | ||
| } | ||
|
|
||
| if !resp.IsTruncated { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for pagination? Do we have a test for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is for pagination, it keeps going until there are no more pages. IsTruncated = true if there are more objects to fetch. We will have a test for it ;)
|
|
||
| case "copy": | ||
| if len(nonFlagArgs) != 3 { | ||
| log.Fatalf("Get method expected 3 arguments got %d\n", len(nonFlagArgs)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Copy method expected ..." ?
| case "delete-recursive": | ||
| var prefix string | ||
| if len(nonFlagArgs) > 2 { | ||
| log.Fatalf("delete-recursive takes at most one argument (prefix) got %d\n", len(nonFlagArgs)-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"one" or "two" arguments here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both is possible, "first" argument is the function name delete-recursive, if you don't specify a prefix it deletes all stuff in the current bucket (specified in config) and if you pass a "second" argument, that is the prefix and only objects starting with that prefix will be deleted in the current bucket. So it is "one" at most if you don't count the function name. Can add sth to the log line to make it clearer eg
delete-recursive takes at most one argument (prefix) got 2: [aa bb]
| }) | ||
| }) | ||
|
|
||
| Describe("Invoking `ensure-bucket-exists`", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't there be a test that actually creates a bucket with this command and verifies that it exists?
| Expect(err).ToNot(HaveOccurred()) | ||
| Expect(cliSession.ExitCode()).To(BeZero()) | ||
|
|
||
| cliSession, err = integration.RunCli(cliPath, configPath, "delete", destBlob) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the srcBlob could not be deleted, the test panics and does not try to attempt to clean up the destBlob. Maybe just remove the Expects as this is not part of the expected test results?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements five missing storage-cli operations for AliCloud (Alibaba Cloud OSS): copy, delete-recursive, list, properties, and ensure-bucket-exists. These operations bring AliCloud feature parity with other storage providers in the storage-cli tool.
Key Changes
- Added CLI command handlers for the five new operations with argument validation
- Implemented the underlying storage client methods with pagination support for list and delete-recursive operations
- Added comprehensive integration tests for all new operations
- Modified the storage client initialization to cache OSS client and bucket instances
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| alioss/main.go | Added command handlers for copy, delete-recursive, list, properties, and ensure-bucket-exists with argument validation |
| alioss/client/storage_client.go | Implemented five new storage operations, modified struct to cache client/bucket instances, added BlobProperties type |
| alioss/client/client.go | Added wrapper methods in AliBlobstore to delegate to storage client implementations |
| alioss/integration/general_ali_test.go | Added integration tests for delete-recursive, copy, list, and ensure-bucket-exists operations |
| alioss/client/clientfakes/fake_storage_client.go | Auto-generated fake client code for new interface methods |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| case "ensure-bucket-exists": | ||
| if len(nonFlagArgs) != 1 { | ||
| log.Fatalf("EnsureBucketExists method expected 1 arguments got %d\n", len(nonFlagArgs)) |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammatical error: "expected 1 arguments" should be "expected 1 argument" (singular form).
| log.Fatalf("EnsureBucketExists method expected 1 arguments got %d\n", len(nonFlagArgs)) | |
| log.Fatalf("EnsureBucketExists method expected 1 argument got %d\n", len(nonFlagArgs)) |
| } else if len(nonFlagArgs) == 2 { | ||
| prefix = nonFlagArgs[1] | ||
| } else { | ||
| log.Fatalf("List method expected 1 or 2 arguments, got %d\n", len(nonFlagArgs)-1) |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is misleading. It says "expected 1 or 2 arguments, got %d" and passes len(nonFlagArgs)-1, but this would exclude the command itself. For instance, if the user passes list prefix1 prefix2, len(nonFlagArgs) would be 3, and the message would say "got 2" which is incorrect - it should say "got 3" (command + 2 arguments). The format string should either say "expected 0 or 1 arguments" (excluding the command) or pass len(nonFlagArgs) instead of len(nonFlagArgs)-1.
| log.Fatalf("List method expected 1 or 2 arguments, got %d\n", len(nonFlagArgs)-1) | |
| log.Fatalf("List method expected 1 or 2 arguments, got %d\n", len(nonFlagArgs)) |
| func (client *AliBlobstore) DeleteRecursive(prefix string) error { | ||
|
|
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Unnecessary blank line after function signature. This is inconsistent with other methods in the file (e.g., Delete at line 40, Get at line 36) which don't have blank lines before their return statements.
| } | ||
|
|
||
| func (client *AliBlobstore) Copy(srcBlob string, dstBlob string) error { | ||
|
|
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Unnecessary blank line after function signature. This is inconsistent with other methods in the file (e.g., Delete at line 40, Get at line 36) which don't have blank lines before their return statements.
| storageConfig config.AliStorageConfig | ||
| client *oss.Client | ||
| bucket *oss.Bucket | ||
| bucketURL string |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bucketURL field is set during initialization (line 90, 96) but is never used anywhere in the codebase. Consider removing it unless it's intended for future use.
| ) error | ||
|
|
||
| DeleteRecursive( | ||
| dest string, |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent parameter naming: The DeleteRecursive method uses dest as the parameter name (line 39), but this represents a prefix for filtering objects, not a destination. Consider renaming to prefix for clarity and consistency with the actual usage in the implementation (line 171) and the List method (line 57).
| dest string, | |
| prefix string, |
| case "delete-recursive": | ||
| var prefix string | ||
| if len(nonFlagArgs) > 2 { | ||
| log.Fatalf("delete-recursive takes at most one argument (prefix) got %d\n", len(nonFlagArgs)-1) |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is misleading. It says "takes at most one argument (prefix) got %d" and passes len(nonFlagArgs)-1, but this logic is inconsistent. For instance, if the user passes delete-recursive prefix1 prefix2, len(nonFlagArgs) would be 3, and the message would say "got 2" arguments, which doesn't match the phrasing "takes at most one argument". The format string should either say "takes at most 2 total arguments" or pass len(nonFlagArgs)-1 with text like "got %d extra arguments".
| log.Fatalf("delete-recursive takes at most one argument (prefix) got %d\n", len(nonFlagArgs)-1) | |
| log.Fatalf("delete-recursive expected at most 2 arguments (command and optional prefix), got %d\n", len(nonFlagArgs)) |
| ) ([]string, error) | ||
|
|
||
| Properties( | ||
| dest string, |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent parameter naming: The Properties method uses dest as the parameter name, but this represents an object key to retrieve properties for, not a destination. Consider renaming to object for clarity and consistency with other methods like Delete (line 35) and Exists (line 43).
| dest string, | |
| object string, |
| func (dsc DefaultStorageClient) Properties( | ||
| dest string, | ||
| ) error { | ||
| log.Printf("Getting properties for object %s/%s\n", | ||
| dsc.storageConfig.BucketName, dest) | ||
|
|
||
| meta, err := dsc.bucket.GetObjectDetailedMeta(dest) | ||
| if err != nil { | ||
| var ossErr oss.ServiceError | ||
| if errors.As(err, &ossErr) && ossErr.StatusCode == 404 { | ||
| fmt.Println(`{}`) | ||
| return nil | ||
| } | ||
|
|
||
| return fmt.Errorf("failed to get properties for object %s: %w", dest, err) | ||
| } | ||
|
|
||
| eTag := meta.Get("ETag") | ||
| lastModifiedStr := meta.Get("Last-Modified") | ||
| contentLengthStr := meta.Get("Content-Length") | ||
|
|
||
| var ( | ||
| lastModified time.Time | ||
| contentLength int64 | ||
| ) | ||
|
|
||
| if lastModifiedStr != "" { | ||
| t, parseErr := time.Parse(time.RFC1123, lastModifiedStr) | ||
| if parseErr == nil { | ||
| lastModified = t | ||
| } | ||
| } | ||
|
|
||
| if contentLengthStr != "" { | ||
| cl, convErr := strconv.ParseInt(contentLengthStr, 10, 64) | ||
| if convErr == nil { | ||
| contentLength = cl | ||
| } | ||
| } | ||
|
|
||
| props := BlobProperties{ | ||
| ETag: strings.Trim(eTag, `"`), | ||
| LastModified: lastModified, | ||
| ContentLength: contentLength, | ||
| } | ||
|
|
||
| output, err := json.MarshalIndent(props, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("failed to marshal object properties: %w", err) | ||
| } | ||
|
|
||
| fmt.Println(string(output)) | ||
| return nil | ||
| } |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistency: The Properties method uses dsc.bucket (the cached bucket from the struct), while existing methods like Upload (line 107-115) and Delete (line 157-165) create new client and bucket instances for each operation. This inconsistency could lead to unexpected behavior. Either use the cached bucket consistently across all methods, or create a new bucket instance in this method for consistency with existing code.
| } | ||
|
|
||
| func (client *AliBlobstore) Properties(dest string) error { | ||
|
|
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Unnecessary blank line after function signature. This is inconsistent with other methods in the file (e.g., Delete at line 40, Get at line 36) which don't have blank lines before their return statements.
Add missing operations: copy, delete-recursive, list, properties, ensure-bucket-exists.