[GH-ISSUE #201] Object retrieval fails with query path constructed by storage.NewReader() #33

Closed
opened 2026-03-03 12:07:36 +03:00 by kerem · 11 comments
Owner

Originally created by @lukevalenta on GitHub (Mar 23, 2020).
Original GitHub issue: https://github.com/fsouza/fake-gcs-server/issues/201

storage.NewReader() fails to read newly-created object. The reason is that the GET query path is constructed as "/bucket/object" in storage.NewRangeReader, and fake-gcs-server appears to only support paths like "/storage/v1/b/bucket/o/object".

Steps to recreate the issue are below:

  1. Start a fake-gcs-server with docker-compose.
>$ cat docker-compose.yaml
version: "2.2"

services:
  gcs: 
    hostname: gcs
    image: fsouza/fake-gcs-server:latest
    ports:
      - "4443:4443"
>$ docker-compose up -d gcs
Creating network "fake-gcs-server-bug_default" with the default driver
Creating fake-gcs-server-bug_gcs_1 ... done
  1. Run go client to create bucket, write object, and then attempt to read object (which fails).
>$ cat main.go
package main

import (
	"context"
	"crypto/tls"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"cloud.google.com/go/storage"
	"google.golang.org/api/option"
)

var (
	projectName = flag.String("prj.name", "test-project", "")
	bucketName  = flag.String("bkt.name", "test-bucket", "")
	fileName    = flag.String("obj.name", "test-file", "")
)

func main() {
	flag.Parse()
	transCfg := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // ignore expired SSL certificates
	}
	httpClient := &http.Client{Transport: transCfg}
	ctx := context.Background()
	client, err := storage.NewClient(ctx, option.WithEndpoint("https://0.0.0.0:4443/storage/v1/"), option.WithHTTPClient(httpClient))
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	bkt := client.Bucket(*bucketName)
	if _, err := bkt.Attrs(ctx); err != nil {
		log.Printf("Creating bucket %v", *bucketName)
		if err = bkt.Create(ctx, *projectName, nil); err != nil {
			log.Fatalf("Failed to create bucket: %v", err)
		}
	}
	log.Printf("Writing file %v", *fileName)
	err = writeFile(ctx, client, *bucketName, *fileName)
	if err != nil {
		log.Fatalf("Failed to write file: %v", err)
	}

	data, err := downloadFile(ctx, client, *bucketName, *fileName)
	if err != nil {
		log.Fatalf("Failed to read object %v:%v/%v: %v", *projectName, *bucketName, *fileName, err)
	}
	log.Printf("read %v bytes from object %v:%v/%v", len(data), *projectName, *bucketName, *fileName)
}

func writeFile(ctx context.Context, client *storage.Client, bucketName, fileKey string) error {
	writer := client.Bucket(bucketName).Object(fileKey).NewWriter(ctx)
	defer writer.Close()
	_, err := fmt.Fprintf(writer, "This object contains text.\n")
	return err
}

func downloadFile(ctx context.Context, client *storage.Client, bucketName, fileKey string) ([]byte, error) {
	reader, err := client.Bucket(bucketName).Object(fileKey).NewReader(ctx)
	if err != nil {
		return nil, err
	}
	defer reader.Close()
	return ioutil.ReadAll(reader)
}

>$ go mod init test-module
>$ go mod vendor
>$ go run -mod vendor main.go
2020/03/23 10:02:13 Creating bucket test-bucket
2020/03/23 10:02:13 Writing file test-file
2020/03/23 10:02:13 Failed to read object test-project:test-bucket/test-file: storage: object doesn't exist
exit status 1
  1. Manually retrieve object via cURL.
>$ curl --insecure https://0.0.0.0:4443/storage/v1/b/test-bucket/o/test-file
{"kind":"storage#object","name":"test-file","id":"test-bucket/test-file","bucket":"test-bucket","size":"27","contentType":"text/plain; charset=utf-8","crc32c":"vJADtw==","acl":[{"bucket":"test-bucket","entity":"projectOwner","object":"test-file","role":"OWNER"}],"md5Hash":"PRQTwE6LQBuo7v10WdA/LQ==","timeCreated":"2020-03-23T14:55:32Z","timeDeleted":"0001-01-01T00:00:00Z","updated":"2020-03-23T14:55:32Z","generation":"0"}
  1. Check server logs
>$ docker-compose logs
Attaching to fake-gcs-server-bug_gcs_1
gcs_1  | time="2020-03-23T15:01:59Z" level=info msg="couldn't load any objects or buckets from \"/data\", starting empty"
gcs_1  | time="2020-03-23T15:01:59Z" level=info msg="server started at https://[::]:4443"
gcs_1  | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"GET /storage/v1/b/test-bucket?alt=json&prettyPrint=false&projection=full HTTP/1.1\" 404 59"
gcs_1  | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"POST /storage/v1/b?alt=json&prettyPrint=false&project=test-project HTTP/1.1\" 200 119"
gcs_1  | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"POST /upload/storage/v1/b/test-bucket/o?alt=json&prettyPrint=false&projection=full&uploadType=multipart HTTP/1.1\" 200 345"
gcs_1  | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"GET /test-bucket/test-file HTTP/1.1\" 404 19"
gcs_1  | time="2020-03-23T15:02:24Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:24 +0000] \"GET /storage/v1/b/test-bucket/o/test-file HTTP/1.1\" 200 425"

From the server logs we can see that the cURL object retrieval works with the path "/storage/v1/b/test-bucket/o/test-file", but the Go client's object retrieval with path "/test-bucket/test-file" fails.

The Go client works with a real GCS server. Changing the URL path to "/storage/v1/b/%s/o/%s" in the storage.NewRangeReader function also works.

Originally created by @lukevalenta on GitHub (Mar 23, 2020). Original GitHub issue: https://github.com/fsouza/fake-gcs-server/issues/201 storage.NewReader() fails to read newly-created object. The reason is that the GET query path is constructed as "/bucket/object" in [storage.NewRangeReader](https://github.com/googleapis/google-cloud-go/blob/3e8951f5e3d821b4b0f8bcd2ab6b768a25b6f8b1/storage/reader.go#L107), and fake-gcs-server appears to only support paths like "/storage/v1/b/bucket/o/object". Steps to recreate the issue are below: 1. Start a fake-gcs-server with docker-compose. ``` >$ cat docker-compose.yaml version: "2.2" services: gcs: hostname: gcs image: fsouza/fake-gcs-server:latest ports: - "4443:4443" >$ docker-compose up -d gcs Creating network "fake-gcs-server-bug_default" with the default driver Creating fake-gcs-server-bug_gcs_1 ... done ``` 2. Run go client to create bucket, write object, and then attempt to read object (which fails). ``` >$ cat main.go package main import ( "context" "crypto/tls" "flag" "fmt" "io/ioutil" "log" "net/http" "cloud.google.com/go/storage" "google.golang.org/api/option" ) var ( projectName = flag.String("prj.name", "test-project", "") bucketName = flag.String("bkt.name", "test-bucket", "") fileName = flag.String("obj.name", "test-file", "") ) func main() { flag.Parse() transCfg := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // ignore expired SSL certificates } httpClient := &http.Client{Transport: transCfg} ctx := context.Background() client, err := storage.NewClient(ctx, option.WithEndpoint("https://0.0.0.0:4443/storage/v1/"), option.WithHTTPClient(httpClient)) if err != nil { log.Fatalf("Failed to create client: %v", err) } bkt := client.Bucket(*bucketName) if _, err := bkt.Attrs(ctx); err != nil { log.Printf("Creating bucket %v", *bucketName) if err = bkt.Create(ctx, *projectName, nil); err != nil { log.Fatalf("Failed to create bucket: %v", err) } } log.Printf("Writing file %v", *fileName) err = writeFile(ctx, client, *bucketName, *fileName) if err != nil { log.Fatalf("Failed to write file: %v", err) } data, err := downloadFile(ctx, client, *bucketName, *fileName) if err != nil { log.Fatalf("Failed to read object %v:%v/%v: %v", *projectName, *bucketName, *fileName, err) } log.Printf("read %v bytes from object %v:%v/%v", len(data), *projectName, *bucketName, *fileName) } func writeFile(ctx context.Context, client *storage.Client, bucketName, fileKey string) error { writer := client.Bucket(bucketName).Object(fileKey).NewWriter(ctx) defer writer.Close() _, err := fmt.Fprintf(writer, "This object contains text.\n") return err } func downloadFile(ctx context.Context, client *storage.Client, bucketName, fileKey string) ([]byte, error) { reader, err := client.Bucket(bucketName).Object(fileKey).NewReader(ctx) if err != nil { return nil, err } defer reader.Close() return ioutil.ReadAll(reader) } >$ go mod init test-module >$ go mod vendor >$ go run -mod vendor main.go 2020/03/23 10:02:13 Creating bucket test-bucket 2020/03/23 10:02:13 Writing file test-file 2020/03/23 10:02:13 Failed to read object test-project:test-bucket/test-file: storage: object doesn't exist exit status 1 ``` 3. Manually retrieve object via cURL. ``` >$ curl --insecure https://0.0.0.0:4443/storage/v1/b/test-bucket/o/test-file {"kind":"storage#object","name":"test-file","id":"test-bucket/test-file","bucket":"test-bucket","size":"27","contentType":"text/plain; charset=utf-8","crc32c":"vJADtw==","acl":[{"bucket":"test-bucket","entity":"projectOwner","object":"test-file","role":"OWNER"}],"md5Hash":"PRQTwE6LQBuo7v10WdA/LQ==","timeCreated":"2020-03-23T14:55:32Z","timeDeleted":"0001-01-01T00:00:00Z","updated":"2020-03-23T14:55:32Z","generation":"0"} ``` 4. Check server logs ``` >$ docker-compose logs Attaching to fake-gcs-server-bug_gcs_1 gcs_1 | time="2020-03-23T15:01:59Z" level=info msg="couldn't load any objects or buckets from \"/data\", starting empty" gcs_1 | time="2020-03-23T15:01:59Z" level=info msg="server started at https://[::]:4443" gcs_1 | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"GET /storage/v1/b/test-bucket?alt=json&prettyPrint=false&projection=full HTTP/1.1\" 404 59" gcs_1 | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"POST /storage/v1/b?alt=json&prettyPrint=false&project=test-project HTTP/1.1\" 200 119" gcs_1 | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"POST /upload/storage/v1/b/test-bucket/o?alt=json&prettyPrint=false&projection=full&uploadType=multipart HTTP/1.1\" 200 345" gcs_1 | time="2020-03-23T15:02:13Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:13 +0000] \"GET /test-bucket/test-file HTTP/1.1\" 404 19" gcs_1 | time="2020-03-23T15:02:24Z" level=info msg="192.168.224.1 - - [23/Mar/2020:15:02:24 +0000] \"GET /storage/v1/b/test-bucket/o/test-file HTTP/1.1\" 200 425" ``` From the server logs we can see that the cURL object retrieval works with the path "/storage/v1/b/test-bucket/o/test-file", but the Go client's object retrieval with path "/test-bucket/test-file" fails. The Go client works with a real GCS server. Changing the URL path to "/storage/v1/b/%s/o/%s" in the storage.NewRangeReader function also works.
kerem closed this issue 2026-03-03 12:07:36 +03:00
Author
Owner

@Terminator637 commented on GitHub (Apr 10, 2020):

I got the same problem.

<!-- gh-comment-id:612019874 --> @Terminator637 commented on GitHub (Apr 10, 2020): I got the same problem.
Author
Owner

@someone1 commented on GitHub (Apr 16, 2020):

I fixed this by running with public-host=localhost as part of the command line options, and my code connects to https://localhost:4443

<!-- gh-comment-id:614878941 --> @someone1 commented on GitHub (Apr 16, 2020): I fixed this by running with `public-host=localhost` as part of the command line options, and my code connects to `https://localhost:4443`
Author
Owner

@Terminator637 commented on GitHub (Apr 17, 2020):

I fixed this by running with public-host=localhost as part of the command line options, and my code connects to https://localhost:4443

Thank you! I found another closed issue with this problem https://github.com/fsouza/fake-gcs-server/issues/196

<!-- gh-comment-id:615031204 --> @Terminator637 commented on GitHub (Apr 17, 2020): > I fixed this by running with `public-host=localhost` as part of the command line options, and my code connects to `https://localhost:4443` Thank you! I found another closed issue with this problem https://github.com/fsouza/fake-gcs-server/issues/196
Author
Owner

@lukevalenta commented on GitHub (Apr 29, 2020):

Excellent! I'll close this ticket then. Changing to the below docker-compose file fixes this example:

version: "2.2"

services:
  gcs: 
    hostname: gcs
    image: fsouza/fake-gcs-server:latest
    entrypoint: ["/bin/fake-gcs-server", "-data", "/data", "-public-host", "0.0.0.0"]
    ports:
      - "4443:4443"
<!-- gh-comment-id:621418888 --> @lukevalenta commented on GitHub (Apr 29, 2020): Excellent! I'll close this ticket then. Changing to the below docker-compose file fixes this example: ``` version: "2.2" services: gcs: hostname: gcs image: fsouza/fake-gcs-server:latest entrypoint: ["/bin/fake-gcs-server", "-data", "/data", "-public-host", "0.0.0.0"] ports: - "4443:4443" ```
Author
Owner

@anz-rfc commented on GitHub (May 25, 2020):

For anyone trying else to get this to work locally, be aware that you need to configure fake-gcs-server's -public-host to exactly match the the host used to configure storage.NewClient endpoint that is used to connect to the fake storage.

i.e. the following will NOT integrate:

# starting the fake server
fake-gcs-server -data /data -public-host 0.0.0.0
// in your go application code
client, err := storage.NewClient(ctx, option.WithEndpoint("https://localhost:4443/storage/v1/"), option.WithHTTPClient(httpClient))

as the server's mux config will not match "localhost" to the "0.0.0.0" PublicHost value endpoint here:

github.com/fsouza/fake-gcs-server@d2b72b590a/fakestorage/server.go (L187)

instead, you must use localhost when configuring both client and server, or use 0.0.0.0 when configuring both client and server.

<!-- gh-comment-id:633402720 --> @anz-rfc commented on GitHub (May 25, 2020): For anyone trying else to get this to work locally, be aware that you need to configure `fake-gcs-server`'s `-public-host` to exactly match the the host used to configure `storage.NewClient` endpoint that is used to connect to the fake storage. i.e. the following will NOT integrate: ``` # starting the fake server fake-gcs-server -data /data -public-host 0.0.0.0 ``` ``` // in your go application code client, err := storage.NewClient(ctx, option.WithEndpoint("https://localhost:4443/storage/v1/"), option.WithHTTPClient(httpClient)) ``` as the server's mux config will not match "localhost" to the "0.0.0.0" PublicHost value endpoint here: https://github.com/fsouza/fake-gcs-server/blob/d2b72b590aac28d57a49d9269a81cdf3f1629a80/fakestorage/server.go#L187 instead, you must use `localhost` when configuring both client and server, or use `0.0.0.0` when configuring both client and server.
Author
Owner

@anz-rfc commented on GitHub (May 25, 2020):

Arguably, it might make configuration less error prone if -public-host was made a mandatory argument with no default, and the server mux routing was rewritten to use public-host as host for all endpoints, not just some of them, so if you didnt explicitly configure fake-gcs-server properly from the start then no api calls worked, instead of some api calls working but other ones not working. But perhaps that departs from how GCS API actually works (?)

<!-- gh-comment-id:633405610 --> @anz-rfc commented on GitHub (May 25, 2020): Arguably, it might make configuration less error prone if `-public-host` was made a mandatory argument with no default, and the server `mux` routing was rewritten to use `public-host` as host for all endpoints, not just some of them, so if you didnt explicitly configure `fake-gcs-server` properly from the start then no api calls worked, instead of some api calls working but other ones not working. But perhaps that departs from how GCS API actually works (?)
Author
Owner

@byF commented on GitHub (Jun 3, 2020):

This definitely needs to be in the main readme, I spent so much time trying to figure this out.

<!-- gh-comment-id:638158966 --> @byF commented on GitHub (Jun 3, 2020): This definitely needs to be in the main readme, I spent so much time trying to figure this out.
Author
Owner

@rogierlommers commented on GitHub (Oct 13, 2020):

How does this work with the docker run-example? Can we somehow add this -public-host flag to the docker run command?

<!-- gh-comment-id:707975147 --> @rogierlommers commented on GitHub (Oct 13, 2020): How does this work with the `docker run`-example? Can we somehow add this `-public-host` flag to the docker run command?
Author
Owner

@lukevalenta commented on GitHub (Oct 13, 2020):

You should be able to do this by overriding the entrypoint (https://docs.docker.com/engine/reference/run/#entrypoint-default-command-to-execute-at-runtime). Something like this (although I haven't tested it):

docker run -d --entrypoint "/bin/fake-gcs-server -data /data -public-host 0.0.0.0" --name fake-gcs-server -p 4443:4443 fsouza/fake-gcs-server
<!-- gh-comment-id:707980831 --> @lukevalenta commented on GitHub (Oct 13, 2020): You should be able to do this by overriding the entrypoint (https://docs.docker.com/engine/reference/run/#entrypoint-default-command-to-execute-at-runtime). Something like this (although I haven't tested it): ``` docker run -d --entrypoint "/bin/fake-gcs-server -data /data -public-host 0.0.0.0" --name fake-gcs-server -p 4443:4443 fsouza/fake-gcs-server ```
Author
Owner

@rogierlommers commented on GitHub (Oct 14, 2020):

You should be able to do this by overriding the entrypoint (https://docs.docker.com/engine/reference/run/#entrypoint-default-command-to-execute-at-runtime). Something like this (although I haven't tested it):

docker run -d --entrypoint "/bin/fake-gcs-server -data /data -public-host 0.0.0.0" --name fake-gcs-server -p 4443:4443 fsouza/fake-gcs-server

Thanks for your suggestion, unfortunately I get an error:

docker run --rm=true --entrypoint "/bin/fake-gcs-server -data /data -public-host 0.0.0.0" -v ${PWD}/mocked_assets:/data fsouza/fake-gcs-server --name fake-gcs-server -p 4443:4443 fsouza/fake-gcs-server

docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"/bin/fake-gcs-server -data /data -public-host 0.0.0.0\": stat /bin/fake-gcs-server -data /data -public-host 0.0.0.0: no such file or directory": unknown.

Which is strange, because indeed the file exists within the container. For some reason it looks like the additional cli flags are messing this up. Any other suggestions?

/ # stat /bin/fake-gcs-server
  File: /bin/fake-gcs-server
  Size: 17337445  	Blocks: 33864      IO Block: 4096   regular file
Device: 6dh/109d	Inode: 1442298     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-10-12 13:39:48.000000000
Modify: 2020-10-12 13:39:48.000000000
Change: 2020-10-13 20:01:35.000000000
<!-- gh-comment-id:708180528 --> @rogierlommers commented on GitHub (Oct 14, 2020): > You should be able to do this by overriding the entrypoint (https://docs.docker.com/engine/reference/run/#entrypoint-default-command-to-execute-at-runtime). Something like this (although I haven't tested it): > > ``` > docker run -d --entrypoint "/bin/fake-gcs-server -data /data -public-host 0.0.0.0" --name fake-gcs-server -p 4443:4443 fsouza/fake-gcs-server > ``` Thanks for your suggestion, unfortunately I get an error: ``` docker run --rm=true --entrypoint "/bin/fake-gcs-server -data /data -public-host 0.0.0.0" -v ${PWD}/mocked_assets:/data fsouza/fake-gcs-server --name fake-gcs-server -p 4443:4443 fsouza/fake-gcs-server docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"/bin/fake-gcs-server -data /data -public-host 0.0.0.0\": stat /bin/fake-gcs-server -data /data -public-host 0.0.0.0: no such file or directory": unknown. ``` Which is strange, because indeed the file exists within the container. For some reason it looks like the additional cli flags are messing this up. Any other suggestions? ``` / # stat /bin/fake-gcs-server File: /bin/fake-gcs-server Size: 17337445 Blocks: 33864 IO Block: 4096 regular file Device: 6dh/109d Inode: 1442298 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-10-12 13:39:48.000000000 Modify: 2020-10-12 13:39:48.000000000 Change: 2020-10-13 20:01:35.000000000 ```
Author
Owner

@rogierlommers commented on GitHub (Oct 14, 2020):

Never mind, turns out that entrypoint and (optional) additional command line flags need to be separated. Not very user-friendly of the docker-cli in my opinion. But hey... it works 👍 .

For future reference:

If you run the container with this:

docker run --rm=true -v ${PWD}/mocked_assets:/data --name fake-gcs-server -p 4443:4443 --entrypoint /bin/fake-gcs-server fsouza/fake-gcs-server -data /data -public-host storage.gcs.127.0.0.1.nip.io:4443
                                                                                       --------------------------------- ---------------------- ----------------------------------------------------------
                                                                                                     |                             |                                     |
                                                                                                     |                             |                                     |
                                                                                               overwriting entrypoint          image we use                      all additional flags

Then you can initiate the Go client with this:

		transCfg := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}

		httpClient := &http.Client{Transport: transCfg}
		client, err = storage.NewClient(
			context.TODO(),
			option.WithEndpoint("https://storage.gcs.127.0.0.1.nip.io:4443/storage/v1/"),
			option.WithHTTPClient(httpClient))

And you can download a (mocked) file with this:

func download(client *storage.Client) {
	fileKey := "example_javascript.js"
	data, err := downloadFile(client, bucketName, fileKey)
	if err != nil {
		logrus.Fatal(err)
	}
	fmt.Printf("contents of %s/%s: %s\n", bucketName, fileKey, data)

}

Thanks, @ltv511 for pointing me in the right direction.

<!-- gh-comment-id:708186301 --> @rogierlommers commented on GitHub (Oct 14, 2020): Never mind, turns out that entrypoint and (optional) additional command line flags need to be separated. Not very user-friendly of the docker-cli in my opinion. But hey... it works 👍 . For future reference: If you run the container with this: ``` docker run --rm=true -v ${PWD}/mocked_assets:/data --name fake-gcs-server -p 4443:4443 --entrypoint /bin/fake-gcs-server fsouza/fake-gcs-server -data /data -public-host storage.gcs.127.0.0.1.nip.io:4443 --------------------------------- ---------------------- ---------------------------------------------------------- | | | | | | overwriting entrypoint image we use all additional flags ``` Then you can initiate the Go client with this: ``` transCfg := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } httpClient := &http.Client{Transport: transCfg} client, err = storage.NewClient( context.TODO(), option.WithEndpoint("https://storage.gcs.127.0.0.1.nip.io:4443/storage/v1/"), option.WithHTTPClient(httpClient)) ``` And you can download a (mocked) file with this: ``` func download(client *storage.Client) { fileKey := "example_javascript.js" data, err := downloadFile(client, bucketName, fileKey) if err != nil { logrus.Fatal(err) } fmt.Printf("contents of %s/%s: %s\n", bucketName, fileKey, data) } ``` Thanks, @ltv511 for pointing me in the right direction.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/fake-gcs-server#33
No description provided.