Skip to content

feat(s3): add lsh s3 object storage commands - #97

Open
LanusseMorais wants to merge 4 commits into
mainfrom
e-4227-cli-object-storage-list-objects-inside-a-bucket-without-the
Open

LanusseMorais wants to merge 4 commits into
mainfrom
e-4227-cli-object-storage-list-objects-inside-a-bucket-without-the

Conversation

@LanusseMorais

@LanusseMorais LanusseMorais commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • New lsh s3 command group covering buckets, objects, access keys and lifecycle rules, addressing them as s3://<bucket>/<key>: list get copy move delete create-bucket delete-bucket sync presign configure access-keys lifecycle metrics usage (short aliases ls stat cp mv rm mb rb). lsh help object-storage explains addressing and access keys.
  • Bucket administration goes through the Latitude API; object operations talk to the bucket's S3 endpoint directly, so endpoint, signing region, path addressing and checksum behaviour are resolved by the CLI instead of being configured by hand.
  • Scoped S3 access keys are persisted per profile (0600) and auto-selected per bucket; the secret is shown once and never reaches stdout unless explicitly requested.
  • Documented exit codes (0-7, 130) for the group, see lsh help exit-codes; the older command groups keep their current behaviour.
  • Adds the text output format and extends --query to it.
  • Bumps latitudesh-go-sdk v1.16.5 -> v1.19.20 (access keys, lifecycle rules, metrics, usage) and adapts the existing command groups to the new request/response models.
  • Uses minio-go/v7 as the S3 client: +1.54 MB on the binary against +7.96 MB for aws-sdk-go-v2 (measured). Release builds now use -s -w -trimpath.
  • Replaces the generated storage_objects group; storage-objects remains as an alias.

Known limitations

  • The public API has no object-level endpoints, so object commands need an S3 access key separate from the API token.
  • POST /storage/access_keys returns the secret exactly once. A key that cannot be persisted is deleted again rather than left behind as a credential nobody can use.
  • SDK gaps worked around here: region.site.slug and an access key's buckets[]/access are dropped by the generated models (read from the raw JSON:API document), and retention_period comes back as a string while the model is *int64 (normalised in the HTTP client).
  • A server-side copy carries a single identity, so two buckets that resolve different access keys are streamed through the client instead.
  • The CLI's own verbs are canonical (list, get, copy, move, delete, create-bucket, delete-bucket); the short spellings (ls, stat, cp, mv, rm, mb, rb) remain as aliases. delete removes objects: lsh s3 delete s3://<bucket> (and the legacy storage-objects delete <bucket-id>) fails with a message naming delete-bucket instead of removing the bucket.
  • storage-objects is kept as a hidden, deprecated command that prints a notice on stderr and re-dispatches to lsh s3. The buckets alias was dropped.

Closes E-4227
Closes E-4233
Closes E-4047

Testing

Clone & checkout

git clone https://github.com/latitudesh/cli.git && cd cli
git checkout e-4227-cli-object-storage-list-objects-inside-a-bucket-without-the

Build & unit tests

go build -o lsh .
go test ./...

Try the commands

./lsh login

# buckets
./lsh s3 create-bucket s3://<BUCKET> --region DAL --project <PROJECT>   # offers to create and save an access key
./lsh s3 list                                                  # project picker, or --all-projects
./lsh s3 get s3://<BUCKET>

# objects
./lsh s3 copy ./file s3://<BUCKET>/dir/
./lsh s3 list s3://<BUCKET>/dir/ --human-readable --summarize
./lsh s3 copy s3://<BUCKET>/dir/file ./restored
./lsh s3 sync ./dir s3://<BUCKET>/dir/ --delete --dry-run
./lsh s3 presign s3://<BUCKET>/dir/file --expires-in 1h
./lsh s3 delete s3://<BUCKET>/dir/ --recursive --yes

# access keys
./lsh s3 access-keys create --bucket <BUCKET>=rw --save
./lsh s3 access-keys list
./lsh s3 configure export s3://<BUCKET> --format env

# lifecycle, metrics, usage
./lsh s3 lifecycle create s3://<BUCKET> --prefix tmp/ --expiration-days 7
./lsh s3 metrics s3://<BUCKET>
./lsh s3 usage --project <PROJECT>

# cleanup
./lsh s3 delete-bucket s3://<BUCKET> --force --yes

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR is not yet safe to merge because the advertised legacy storage-objects shim breaks existing get and delete scripts.

Summary

  • Introduces the lsh s3 command hierarchy and object data-plane operations.
  • Adds profile-scoped S3 credentials with interprocess-safe configuration updates.
  • Preserves storage-objects as a deprecated shim, although two legacy bucket operations are not currently translated correctly.
  • Updates release flags, output formats, documentation, tests, and existing commands for the newer Latitude SDK.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  CLI[lsh s3] --> API[Latitude API]
  CLI --> S3[S3 bucket endpoint]
  API --> B[Buckets]
  API --> K[Access keys]
  API --> L[Lifecycle, metrics, usage]
  S3 --> O[Object list, copy, move, delete, get, sync]
  K --> P[Profile credential store]
Loading

Reviews (3) · Last reviewed commit: "refactor(s3): lsh verbs as canonical nam..."

@LanusseMorais

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

Comment thread internal/objectstorage/creds.go
Comment thread cmd/s3/s3.go Outdated
Comment thread internal/objectstorage/creds.go Outdated
Comment thread cmd/s3/s3.go
Comment on lines +531 to +532
root.SetArgs(append([]string{"s3"}, args...))
err := root.Execute()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Legacy commands break

The compatibility shim forwards legacy arguments unchanged. However, storage-objects get <id> accepted a raw bucket ID while s3 get requires an s3:// URI, and storage-objects delete <id> deleted a bucket while s3 delete now deletes objects. Existing scripts using either documented legacy form therefore fail instead of performing the original bucket operation. Translate these legacy verbs and arguments to the corresponding bucket commands before redispatching.

Prompt To Fix With AI
This is a comment left during a code review.
Path: cmd/s3/s3.go
Line: 531-532

Comment:
**Legacy commands break**

The compatibility shim forwards legacy arguments unchanged. However, `storage-objects get <id>` accepted a raw bucket ID while `s3 get` requires an `s3://` URI, and `storage-objects delete <id>` deleted a bucket while `s3 delete` now deletes objects. Existing scripts using either documented legacy form therefore fail instead of performing the original bucket operation. Translate these legacy verbs and arguments to the corresponding bucket commands before redispatching.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant