From b83a30642523804fe274b8206bc6a81c2f933b6f Mon Sep 17 00:00:00 2001 From: MRG FOSS Date: Mon, 17 Jul 2023 21:58:27 +0200 Subject: [PATCH] Disallow composing more than 32 objects at once The Google Cloud Storage API does not allow it either (see [documentation][1]). Trying to compose more than 32 objects at once qualifies as a 400 BadRequest. This is the body that GCS returns in such a case: ```json { "error": { "code": 400, "message": "The number of source components provided (33) exceeds the maximum (32)", "errors": [ { "message": "The number of source components provided (33) exceeds the maximum (32)", "domain": "global", "reason": "invalid" } ] } } ``` Note that GCS supports composing more than 32 objects into one. But it requires multiple `compose` calls, each one composing at most 32 objects in one go. [1]: https://cloud.google.com/storage/docs/json_api/v1/objects/compose --- fakestorage/object.go | 8 ++++++++ fakestorage/object_test.go | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/fakestorage/object.go b/fakestorage/object.go index 47994f26c2..047629dda6 100644 --- a/fakestorage/object.go +++ b/fakestorage/object.go @@ -1121,6 +1121,14 @@ func (s *Server) composeObject(r *http.Request) jsonResponse { } } + const maxComposeObjects = 32 + if len(composeRequest.SourceObjects) > maxComposeObjects { + return jsonResponse{ + status: http.StatusBadRequest, + errorMessage: fmt.Sprintf("The number of source components provided (%d) exceeds the maximum (%d)", len(composeRequest.SourceObjects), maxComposeObjects), + } + } + sourceNames := make([]string, 0, len(composeRequest.SourceObjects)) for _, n := range composeRequest.SourceObjects { sourceNames = append(sourceNames, n.Name) diff --git a/fakestorage/object_test.go b/fakestorage/object_test.go index 38c8adf3b1..26dc27c225 100644 --- a/fakestorage/object_test.go +++ b/fakestorage/object_test.go @@ -1992,6 +1992,7 @@ func TestServiceClientComposeObject(t *testing.T) { destObjectName string sourceObjectNames []string expectedContent string + expectedError string }{ { "destination file doesn't exist", @@ -1999,6 +2000,7 @@ func TestServiceClientComposeObject(t *testing.T) { "files/some-file.txt", []string{"files/source1.txt", "files/source2.txt"}, source1Content + source2Content, + "", }, { "destination file already exists", @@ -2006,6 +2008,7 @@ func TestServiceClientComposeObject(t *testing.T) { "files/destination.txt", []string{"files/source1.txt", "files/source2.txt"}, source1Content + source2Content, + "", }, { "destination is a source", @@ -2013,6 +2016,15 @@ func TestServiceClientComposeObject(t *testing.T) { "files/source3.txt", []string{"files/source2.txt", "files/source3.txt"}, source2Content + source3Content, + "", + }, + { + "too many objects at once", + "first-bucket", + "files/destination.txt", + []string{"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33"}, + "", + "googleapi: Error 400: The number of source components provided (33) exceeds the maximum (32)", }, } for _, test := range tests { @@ -2033,6 +2045,9 @@ func TestServiceClientComposeObject(t *testing.T) { composer.Metadata = map[string]string{"baz": "qux"} attrs, err := composer.Run(context.TODO()) if err != nil { + if err.Error() == test.expectedError { + return + } t.Fatal(err) }