Skip to content

feat(storage): managed folders - #102

Merged
Michael McQuade (giraffesyo) merged 3 commits into
canaryfrom
feat/gcs-managed-folders
Nov 6, 2026
Merged

Michael McQuade (giraffesyo) merged 3 commits into
canaryfrom
feat/gcs-managed-folders

Conversation

@giraffesyo

@giraffesyo Michael McQuade (giraffesyo) commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds Cloud Storage managed folders to the gcp/storage stand-in, plus the uniform bucket-level access setting they depend on.

Managed folders (storage.managedFolders.*):

  • insert, get, list (prefix, pageToken, and a page size of at most 1,000 as pageSize, which the discovery doc and Go client use, or maxResults, which the reference page names; both, if set and not 0, must agree; in name order) and delete (allowNonEmpty, ifMetagenerationMatch/ifMetagenerationNotMatch, which get takes too).
  • Responses carry kind, id, selfLink, name, bucket, metageneration, createTime and updateTime. Names end with / and follow the documented rules: 1 to 1,024 bytes, at most 15 /s, no CR or LF, not . or .., and not under .well-known/acme-challenge/.
  • Inserting a folder that already exists returns 409 conflict. A missing folder returns 404 notFound.
  • Deleting a folder that holds objects or nested managed folders fails with 409 conflict unless allowNonEmpty=true. Noncurrent generations count; soft-deleted ones don't. With allowNonEmpty=true the folder goes and the objects stay, as the reference's "a simulated folder with the same name is retained" implies.
  • A bucket counts as empty only with no objects and no managed folders (the deleting-buckets page warns against "accidentally deleting objects or managed folders", and the Go client's integration cleanup deletes managed folders before the bucket), so buckets.delete with folders left fails with the existing 409 conflict "The bucket you tried to delete is not empty."
  • Under WithPolicy the methods need storage.managedFolders.create/get/list/delete. allowNonEmpty=true also needs storage.managedFolders.setIamPolicy, per the delete reference.
  • PutManagedFolder and ManagedFolders set up and inspect folders directly.

Uniform bucket-level access (iamConfiguration): managed folders work only in buckets that have it, so buckets.insert and buckets.patch now accept it.

  • It is accepted as uniformBucketLevelAccess and its old name bucketPolicyOnly. When a body carries both, uniformBucketLevelAccess wins, as the discovery doc and the Go client say, so a raw read-modify-write can turn it off. It is reported under both names with a lockedTime 90 days after it is turned on. Without this field the stand-in rejected the Go client's UniformBucketLevelAccess bucket create with 400.
  • It can't be turned off while managed folders remain (the docs say to delete them first) or after lockedTime.
  • While it is on, predefinedAcl on uploads, objects.patch and objects.update fails with what Cloud Storage answers: 400 invalid "Cannot insert legacy ACL for an object when uniform bucket-level access is enabled. Read more at https://cloud.google.com/storage/docs/uniform-bucket-level-access". The status codes page lists this case under badRequest, and the package doc notes the mismatch. objects.restore with copySourceAcl=true fails the same way (inferred). On bucket insert and patch, predefinedAcl and predefinedDefaultObjectAcl fail with 400 badRequest, per the status codes reference (message inferred).
  • Under WithPolicy, copySourceAcl=true on a bucket without uniform access needs storage.objects.setIamPolicy, per the restore reference. acl and defaultObjectAcl bodies were already rejected with 400, and copy, rewrite and compose are not implemented.
  • publicAccessPrevention (inherited by default, or enforced, also checked in PutBucket) is recorded and reported, not enforced, like cors and rpo.
  • Every bucket now reports iamConfiguration, as Cloud Storage does.

Left out: managedFolders.update, the per-folder IAM policy methods and rapidCacheConfig return 404 or 400, because the stand-in has no IAM policies on buckets or objects to build on. The Storage Control client is also not covered: its managed folder calls are gRPC-only, and the stand-in serves the JSON API.

Inferred (marked in the package doc)

The docs and client libraries don't give these, so they are inferred:

  • Managed folder calls on a bucket without uniform bucket-level access return 409 conflict. The Go client library's tests expect FAILED_PRECONDITION over gRPC. The 409 mirrors how the JSON API answers a non-empty bucket delete; the status and message are inferred.
  • Deleting a non-empty managed folder returns 409 conflict (status, reason and message). Noncurrent generations count toward non-empty.
  • The messages for 409 on an existing folder, 404 on a missing one, the 400 invalid for a bad name, and the 400 badRequest for bucket ACLs under uniform access.
  • objects.restore with copySourceAcl=true on a uniform bucket is refused.
  • buckets.delete with only managed folders left uses the same message as with objects left.
  • A name sent without the trailing / gets one added, as the reference's examples name folders without it.
  • id (bucket/name/) and selfLink (name escaped).
  • allowNonEmpty also leaves nested managed folders in place.
  • Turning uniform access off after lockedTime returns 400 invalid, and while managed folders remain it returns 409 conflict (statuses and messages).

Soft-deleted buckets hold no managed folders to restore, which fits the docs' "You can't recover managed folders."

Testing

  • HTTP tests in gcp/storage/folders_test.go cover the response shape, error statuses and reasons, paging with either size parameter, the emptiness rules, the 90-day lock, ACL refusal under uniform access, reported iamConfiguration, and policy checks.
  • Tests in sdktest/gcp/storage/folders_test.go go through google.golang.org/api/storage/v1 (ManagedFolders.*) and cloud.google.com/go/storage (creating, updating and deleting buckets with uniform bucket-level access, reading back public access prevention, and predefined ACLs refused).
  • go vet, go test -race ./... in both modules, make lint and make vuln pass.
  • make bench for gcp/storage (3 runs each), canary vs this branch: about 185-199 µs/op vs 192-199 µs/op, with 219 allocs/op in both. This is within noise.

Checklist

  • make check passes
  • New methods are tested through the official client library
  • Package documentation says where the stand-in differs from the real API

managedFolders insert, get, list and delete, with allowNonEmpty and metageneration preconditions, and uniform bucket-level access on buckets, which managed folders need. A bucket with managed folders left is not empty.
Accept maxResults beside pageSize, refuse predefined ACLs under uniform bucket-level access, record and report publicAccessPrevention, and cite sources for the managed folder rules.
uniformBucketLevelAccess wins over bucketPolicyOnly, object ACLs under uniform access answer Cloud Storage's 400 invalid, objects.restore refuses copySourceAcl under uniform access and needs storage.objects.setIamPolicy without it, PutBucket checks publicAccessPrevention, and a page size of 0 counts as unset.
@giraffesyo
Michael McQuade (giraffesyo) merged commit bbc8e86 into canary Nov 6, 2026
13 checks passed
@giraffesyo
Michael McQuade (giraffesyo) deleted the feat/gcs-managed-folders branch November 6, 2026 00:43
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