diff --git a/go.mod b/go.mod
index 29e2c00d5f2ff..8579f77e0e4b2 100644
--- a/go.mod
+++ b/go.mod
@@ -34,7 +34,7 @@ require (
github.com/fatih/color v1.16.0
github.com/felixge/fgprof v0.9.4
github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c
- github.com/fsouza/fake-gcs-server v1.47.7
+ github.com/fsouza/fake-gcs-server v1.7.0
github.com/go-kit/log v0.2.1
github.com/go-logfmt/logfmt v0.6.0
github.com/go-redis/redis/v8 v8.11.5
@@ -166,14 +166,12 @@ require (
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/goccy/go-json v0.10.3 // indirect
- github.com/gorilla/handlers v1.5.2 // indirect
github.com/hashicorp/go-msgpack/v2 v2.1.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/ncw/swift v1.0.53 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
- github.com/pkg/xattr v0.4.10 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
diff --git a/go.sum b/go.sum
index 641b64a064530..cc26b57251f09 100644
--- a/go.sum
+++ b/go.sum
@@ -642,8 +642,8 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/fsouza/fake-gcs-server v1.47.7 h1:56/U4rKY081TaNbq0gHWi7/71UxC2KROqcnrD9BRJhs=
-github.com/fsouza/fake-gcs-server v1.47.7/go.mod h1:4vPUynN8/zZlxk5Jpy6LvvTTxItdTAObK4DYnp89Jys=
+github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8=
+github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
github.com/fullstorydev/emulators/storage v0.0.0-20240401123056-edc69752f474 h1:TufioMBjkJ6/Oqmlye/ReuxHFS35HyLmypj/BNy/8GY=
github.com/fullstorydev/emulators/storage v0.0.0-20240401123056-edc69752f474/go.mod h1:PQwxF4UU8wuL+srGxr3BOhIW5zXqgucwVlO/nPZLsxw=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@@ -1029,9 +1029,8 @@ github.com/gophercloud/gophercloud v1.13.0 h1:8iY9d1DAbzMW6Vok1AxbbK5ZaUjzMp0tdy
github.com/gophercloud/gophercloud v1.13.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
-github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
-github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
@@ -1590,8 +1589,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
-github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
-github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -2288,7 +2285,6 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -2451,6 +2447,7 @@ gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6d
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
diff --git a/pkg/storage/chunk/client/gcp/fixtures.go b/pkg/storage/chunk/client/gcp/fixtures.go
index 67f0c116b1116..3fc03fb6e0158 100644
--- a/pkg/storage/chunk/client/gcp/fixtures.go
+++ b/pkg/storage/chunk/client/gcp/fixtures.go
@@ -49,10 +49,19 @@ func (f *fixture) Clients() (
}
f.gcssrv = fakestorage.NewServer(nil)
- opts := fakestorage.CreateBucketOpts{
- Name: "chunks",
- }
- f.gcssrv.CreateBucketWithOpts(opts)
+ /*
+ // Note: fake-gcs-server upgrade does not work in the `dist` tooling builds.
+ // Leave at v1.7.0 until the issue is resolved.
+ // Example failure: https://github.com/grafana/loki/actions/runs/10744853958/job/29802951861
+ // Open issue: https://github.com/fsouza/fake-gcs-server/issues/1739
+ // Once the issue is resolved, this code block can be used to replace the
+ // `CreateBucket` call below.
+ opts := fakestorage.CreateBucketOpts{
+ Name: "chunks",
+ }
+ f.gcssrv.CreateBucketWithOpts(opts)
+ */
+ f.gcssrv.CreateBucket("chunks")
conn, err := grpc.NewClient(f.btsrv.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
diff --git a/vendor/github.com/fsouza/fake-gcs-server/LICENSE b/vendor/github.com/fsouza/fake-gcs-server/LICENSE
index a619aaecef9d1..529faa468606e 100644
--- a/vendor/github.com/fsouza/fake-gcs-server/LICENSE
+++ b/vendor/github.com/fsouza/fake-gcs-server/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) Francisco Souza
+Copyright (c) 2017-2019, Francisco Souza
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/bucket.go b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/bucket.go
index 4026f1a4a0deb..e2fa2ad3716ee 100644
--- a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/bucket.go
+++ b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/bucket.go
@@ -6,161 +6,49 @@ package fakestorage
import (
"encoding/json"
- "errors"
- "fmt"
- "io"
"net/http"
- "regexp"
- "github.com/fsouza/fake-gcs-server/internal/backend"
"github.com/gorilla/mux"
)
-var bucketRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$`)
-
// CreateBucket creates a bucket inside the server, so any API calls that
// require the bucket name will recognize this bucket.
//
// If the bucket already exists, this method does nothing.
-//
-// Deprecated: use CreateBucketWithOpts.
func (s *Server) CreateBucket(name string) {
- err := s.backend.CreateBucket(name, backend.BucketAttrs{VersioningEnabled: false, DefaultEventBasedHold: false})
- if err != nil {
- panic(err)
- }
-}
-
-func (s *Server) updateBucket(r *http.Request) jsonResponse {
- bucketName := unescapeMuxVars(mux.Vars(r))["bucketName"]
- attrsToUpdate := getBucketAttrsToUpdate(r.Body)
- err := s.backend.UpdateBucket(bucketName, attrsToUpdate)
+ s.mtx.Lock()
+ defer s.mtx.Unlock()
+ err := s.backend.CreateBucket(name)
if err != nil {
panic(err)
}
- return jsonResponse{}
-}
-
-func getBucketAttrsToUpdate(body io.ReadCloser) backend.BucketAttrs {
- var data struct {
- DefaultEventBasedHold bool `json:"defaultEventBasedHold,omitempty"`
- Versioning bucketVersioning `json:"versioning,omitempty"`
- }
- err := json.NewDecoder(body).Decode(&data)
- if err != nil {
- panic(err)
- }
- attrsToUpdate := backend.BucketAttrs{
- DefaultEventBasedHold: data.DefaultEventBasedHold,
- VersioningEnabled: data.Versioning.Enabled,
- }
- return attrsToUpdate
-}
-
-// CreateBucketOpts defines the properties of a bucket you can create with
-// CreateBucketWithOpts.
-type CreateBucketOpts struct {
- Name string
- VersioningEnabled bool
- DefaultEventBasedHold bool
-}
-
-// CreateBucketWithOpts creates a bucket inside the server, so any API calls that
-// require the bucket name will recognize this bucket. Use CreateBucketOpts to
-// customize the options for this bucket
-//
-// If the underlying backend returns an error, this method panics.
-func (s *Server) CreateBucketWithOpts(opts CreateBucketOpts) {
- err := s.backend.CreateBucket(opts.Name, backend.BucketAttrs{VersioningEnabled: opts.VersioningEnabled, DefaultEventBasedHold: opts.DefaultEventBasedHold})
- if err != nil {
- panic(err)
- }
-}
-
-func (s *Server) createBucketByPost(r *http.Request) jsonResponse {
- // Minimal version of Bucket from google.golang.org/api/storage/v1
-
- var data struct {
- Name string `json:"name,omitempty"`
- Versioning *bucketVersioning `json:"versioning,omitempty"`
- DefaultEventBasedHold bool `json:"defaultEventBasedHold,omitempty"`
- }
-
- // Read the bucket props from the request body JSON
- decoder := json.NewDecoder(r.Body)
- if err := decoder.Decode(&data); err != nil {
- return jsonResponse{errorMessage: err.Error(), status: http.StatusBadRequest}
- }
- name := data.Name
- versioning := false
- if data.Versioning != nil {
- versioning = data.Versioning.Enabled
- }
- defaultEventBasedHold := data.DefaultEventBasedHold
- if err := validateBucketName(name); err != nil {
- return jsonResponse{errorMessage: err.Error(), status: http.StatusBadRequest}
- }
-
- _, err := s.backend.GetBucket(name)
- if err == nil {
- return jsonResponse{
- errorMessage: fmt.Sprintf(
- "A Cloud Storage bucket named '%s' already exists. "+
- "Try another name. Bucket names must be globally unique "+
- "across all Google Cloud projects, including those "+
- "outside of your organization.", name),
- status: http.StatusConflict,
- }
- }
-
- // Create the named bucket
- if err := s.backend.CreateBucket(name, backend.BucketAttrs{VersioningEnabled: versioning, DefaultEventBasedHold: defaultEventBasedHold}); err != nil {
- return jsonResponse{errorMessage: err.Error()}
- }
-
- // Return the created bucket:
- bucket, err := s.backend.GetBucket(name)
- if err != nil {
- return jsonResponse{errorMessage: err.Error()}
- }
- return jsonResponse{data: newBucketResponse(bucket, s.options.BucketsLocation)}
}
-func (s *Server) listBuckets(r *http.Request) jsonResponse {
- buckets, err := s.backend.ListBuckets()
- if err != nil {
- return jsonResponse{errorMessage: err.Error()}
- }
- return jsonResponse{data: newListBucketsResponse(buckets, s.options.BucketsLocation)}
-}
+func (s *Server) listBuckets(w http.ResponseWriter, r *http.Request) {
+ s.mtx.RLock()
+ defer s.mtx.RUnlock()
-func (s *Server) getBucket(r *http.Request) jsonResponse {
- bucketName := unescapeMuxVars(mux.Vars(r))["bucketName"]
- bucket, err := s.backend.GetBucket(bucketName)
- if err != nil {
- return jsonResponse{status: http.StatusNotFound}
- }
- return jsonResponse{data: newBucketResponse(bucket, s.options.BucketsLocation)}
-}
-
-func (s *Server) deleteBucket(r *http.Request) jsonResponse {
- bucketName := unescapeMuxVars(mux.Vars(r))["bucketName"]
- err := s.backend.DeleteBucket(bucketName)
- if err == backend.BucketNotFound {
- return jsonResponse{status: http.StatusNotFound}
- }
- if err == backend.BucketNotEmpty {
- return jsonResponse{status: http.StatusPreconditionFailed, errorMessage: err.Error()}
- }
+ bucketNames, err := s.backend.ListBuckets()
if err != nil {
- return jsonResponse{status: http.StatusInternalServerError, errorMessage: err.Error()}
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
}
- return jsonResponse{}
+ resp := newListBucketsResponse(bucketNames)
+ json.NewEncoder(w).Encode(resp)
}
-func validateBucketName(bucketName string) error {
- if !bucketRegexp.MatchString(bucketName) {
- return errors.New("invalid bucket name")
+func (s *Server) getBucket(w http.ResponseWriter, r *http.Request) {
+ bucketName := mux.Vars(r)["bucketName"]
+ s.mtx.RLock()
+ defer s.mtx.RUnlock()
+ encoder := json.NewEncoder(w)
+ if err := s.backend.GetBucket(bucketName); err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ err := newErrorResponse(http.StatusNotFound, "Not found", nil)
+ encoder.Encode(err)
+ return
}
- return nil
+ resp := newBucketResponse(bucketName)
+ w.WriteHeader(http.StatusOK)
+ encoder.Encode(resp)
}
diff --git a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/config.go b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/config.go
deleted file mode 100644
index a57d154279a5e..0000000000000
--- a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/config.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package fakestorage
-
-import (
- "encoding/json"
- "net/http"
-)
-
-func (s *Server) updateServerConfig(r *http.Request) jsonResponse {
- var configOptions struct {
- ExternalUrl string `json:"externalUrl,omitempty"`
- PublicHost string `json:"publicHost,omitempty"`
- }
- err := json.NewDecoder(r.Body).Decode(&configOptions)
- if err != nil {
- return jsonResponse{
- status: http.StatusBadRequest,
- errorMessage: "Update server config payload can not be parsed.",
- }
- }
-
- if configOptions.ExternalUrl != "" {
- s.externalURL = configOptions.ExternalUrl
- }
-
- if configOptions.PublicHost != "" {
- s.publicHost = configOptions.PublicHost
- }
-
- return jsonResponse{status: http.StatusOK}
-}
diff --git a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/json_response.go b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/json_response.go
deleted file mode 100644
index f16a7c5c10180..0000000000000
--- a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/json_response.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package fakestorage
-
-import (
- "encoding/json"
- "errors"
- "net/http"
- "os"
- "syscall"
-
- "github.com/fsouza/fake-gcs-server/internal/backend"
-)
-
-type jsonResponse struct {
- status int
- header http.Header
- data any
- errorMessage string
-}
-
-type jsonHandler = func(r *http.Request) jsonResponse
-
-func jsonToHTTPHandler(h jsonHandler) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- resp := h(r)
- w.Header().Set("Content-Type", "application/json")
- for name, values := range resp.header {
- for _, value := range values {
- w.Header().Add(name, value)
- }
- }
-
- status := resp.getStatus()
- var data any
- if status > 399 {
- data = newErrorResponse(status, resp.getErrorMessage(status), nil)
- } else {
- data = resp.data
- }
-
- w.WriteHeader(status)
- json.NewEncoder(w).Encode(data)
- }
-}
-
-func (r *jsonResponse) getStatus() int {
- if r.status > 0 {
- return r.status
- }
- if r.errorMessage != "" {
- return http.StatusInternalServerError
- }
- return http.StatusOK
-}
-
-func (r *jsonResponse) getErrorMessage(status int) string {
- if r.errorMessage != "" {
- return r.errorMessage
- }
- return http.StatusText(status)
-}
-
-func errToJsonResponse(err error) jsonResponse {
- status := 0
- var pathError *os.PathError
- if errors.As(err, &pathError) && pathError.Err == syscall.ENAMETOOLONG {
- status = http.StatusBadRequest
- }
- if err == backend.PreConditionFailed {
- status = http.StatusPreconditionFailed
- }
- return jsonResponse{errorMessage: err.Error(), status: status}
-}
diff --git a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/mux_tranport.go b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/mux_tranport.go
index b228c787ae682..afaa2efeac76a 100644
--- a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/mux_tranport.go
+++ b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/mux_tranport.go
@@ -7,14 +7,16 @@ package fakestorage
import (
"net/http"
"net/http/httptest"
+
+ "github.com/gorilla/mux"
)
type muxTransport struct {
- handler http.Handler
+ router *mux.Router
}
func (t *muxTransport) RoundTrip(r *http.Request) (*http.Response, error) {
w := httptest.NewRecorder()
- t.handler.ServeHTTP(w, r)
+ t.router.ServeHTTP(w, r)
return w.Result(), nil
}
diff --git a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/object.go b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/object.go
index 6c1533ffea45a..bc1d472f36e30 100644
--- a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/object.go
+++ b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/object.go
@@ -5,365 +5,84 @@
package fakestorage
import (
- "bytes"
- "compress/gzip"
"encoding/json"
- "encoding/xml"
- "errors"
"fmt"
- "io"
"net/http"
"sort"
"strconv"
"strings"
- "time"
- "cloud.google.com/go/storage"
"github.com/fsouza/fake-gcs-server/internal/backend"
- "github.com/fsouza/fake-gcs-server/internal/notification"
"github.com/gorilla/mux"
)
-var errInvalidGeneration = errors.New("invalid generation ID")
-
-// ObjectAttrs returns only the meta-data about an object without its contents.
-type ObjectAttrs struct {
- BucketName string
- Name string
- Size int64
- ContentType string
- ContentEncoding string
+// Object represents the object that is stored within the fake server.
+type Object struct {
+ BucketName string `json:"-"`
+ Name string `json:"name"`
+ Content []byte `json:"-"`
// Crc32c checksum of Content. calculated by server when it's upload methods are used.
- Crc32c string
- Md5Hash string
- Etag string
- ACL []storage.ACLRule
- // Dates and generation can be manually injected, so you can do assertions on them,
- // or let us fill these fields for you
- Created time.Time
- Updated time.Time
- Deleted time.Time
- CustomTime time.Time
- Generation int64
- Metadata map[string]string
+ Crc32c string `json:"crc32c,omitempty"`
+ Md5Hash string `json:"md5hash,omitempty"`
}
-func (o *ObjectAttrs) id() string {
+func (o *Object) id() string {
return o.BucketName + "/" + o.Name
}
-type jsonObject struct {
- BucketName string `json:"bucket"`
- Name string `json:"name"`
- Size int64 `json:"size,string"`
- ContentType string `json:"contentType"`
- ContentEncoding string `json:"contentEncoding"`
- Crc32c string `json:"crc32c,omitempty"`
- Md5Hash string `json:"md5Hash,omitempty"`
- Etag string `json:"etag,omitempty"`
- ACL []aclRule `json:"acl,omitempty"`
- Created time.Time `json:"created,omitempty"`
- Updated time.Time `json:"updated,omitempty"`
- Deleted time.Time `json:"deleted,omitempty"`
- CustomTime time.Time `json:"customTime,omitempty"`
- Generation int64 `json:"generation,omitempty,string"`
- Metadata map[string]string `json:"metadata,omitempty"`
-}
-
-// MarshalJSON for ObjectAttrs to use ACLRule instead of storage.ACLRule
-func (o ObjectAttrs) MarshalJSON() ([]byte, error) {
- temp := jsonObject{
- BucketName: o.BucketName,
- Name: o.Name,
- ContentType: o.ContentType,
- ContentEncoding: o.ContentEncoding,
- Size: o.Size,
- Crc32c: o.Crc32c,
- Md5Hash: o.Md5Hash,
- Etag: o.Etag,
- Created: o.Created,
- Updated: o.Updated,
- Deleted: o.Deleted,
- CustomTime: o.CustomTime,
- Generation: o.Generation,
- Metadata: o.Metadata,
- }
- temp.ACL = make([]aclRule, len(o.ACL))
- for i, ACL := range o.ACL {
- temp.ACL[i] = aclRule(ACL)
- }
- return json.Marshal(temp)
-}
-
-// UnmarshalJSON for ObjectAttrs to use ACLRule instead of storage.ACLRule
-func (o *ObjectAttrs) UnmarshalJSON(data []byte) error {
- var temp jsonObject
- if err := json.Unmarshal(data, &temp); err != nil {
- return err
- }
- o.BucketName = temp.BucketName
- o.Name = temp.Name
- o.ContentType = temp.ContentType
- o.ContentEncoding = temp.ContentEncoding
- o.Size = temp.Size
- o.Crc32c = temp.Crc32c
- o.Md5Hash = temp.Md5Hash
- o.Etag = temp.Etag
- o.Created = temp.Created
- o.Updated = temp.Updated
- o.Deleted = temp.Deleted
- o.Generation = temp.Generation
- o.Metadata = temp.Metadata
- o.CustomTime = temp.CustomTime
- o.ACL = make([]storage.ACLRule, len(temp.ACL))
- for i, ACL := range temp.ACL {
- o.ACL[i] = storage.ACLRule(ACL)
- }
-
- return nil
-}
-
-// Object represents an object that is stored within the fake server. The
-// content of this type is stored is buffered, i.e. it's stored in memory.
-// Use StreamingObject to stream the content from a reader, e.g a file.
-type Object struct {
- ObjectAttrs
- Content []byte `json:"-"`
-}
-
-type noopSeekCloser struct {
- io.ReadSeeker
-}
-
-func (n noopSeekCloser) Close() error {
- return nil
-}
-
-func (o Object) StreamingObject() StreamingObject {
- return StreamingObject{
- ObjectAttrs: o.ObjectAttrs,
- Content: noopSeekCloser{bytes.NewReader(o.Content)},
- }
-}
-
-// StreamingObject is the streaming version of Object.
-type StreamingObject struct {
- ObjectAttrs
- Content io.ReadSeekCloser `json:"-"`
-}
-
-func (o *StreamingObject) Close() error {
- if o != nil && o.Content != nil {
- return o.Content.Close()
- }
- return nil
-}
-
-func (o *StreamingObject) BufferedObject() (Object, error) {
- data, err := io.ReadAll(o.Content)
- return Object{
- ObjectAttrs: o.ObjectAttrs,
- Content: data,
- }, err
-}
-
-// ACLRule is an alias of storage.ACLRule to have custom JSON marshal
-type aclRule storage.ACLRule
-
-// ProjectTeam is an alias of storage.ProjectTeam to have custom JSON marshal
-type projectTeam storage.ProjectTeam
-
-// MarshalJSON for ACLRule to customize field names
-func (acl aclRule) MarshalJSON() ([]byte, error) {
- temp := struct {
- Entity storage.ACLEntity `json:"entity"`
- EntityID string `json:"entityId"`
- Role storage.ACLRole `json:"role"`
- Domain string `json:"domain"`
- Email string `json:"email"`
- ProjectTeam *projectTeam `json:"projectTeam"`
- }{
- Entity: acl.Entity,
- EntityID: acl.EntityID,
- Role: acl.Role,
- Domain: acl.Domain,
- Email: acl.Email,
- ProjectTeam: (*projectTeam)(acl.ProjectTeam),
- }
- return json.Marshal(temp)
-}
-
-// UnmarshalJSON for ACLRule to customize field names
-func (acl *aclRule) UnmarshalJSON(data []byte) error {
- temp := struct {
- Entity storage.ACLEntity `json:"entity"`
- EntityID string `json:"entityId"`
- Role storage.ACLRole `json:"role"`
- Domain string `json:"domain"`
- Email string `json:"email"`
- ProjectTeam *projectTeam `json:"projectTeam"`
- }{}
- if err := json.Unmarshal(data, &temp); err != nil {
- return err
- }
- acl.Entity = temp.Entity
- acl.EntityID = temp.EntityID
- acl.Role = temp.Role
- acl.Domain = temp.Domain
- acl.Email = temp.Email
- acl.ProjectTeam = (*storage.ProjectTeam)(temp.ProjectTeam)
- return nil
-}
-
-// MarshalJSON for ProjectTeam to customize field names
-func (team projectTeam) MarshalJSON() ([]byte, error) {
- temp := struct {
- ProjectNumber string `json:"projectNumber"`
- Team string `json:"team"`
- }{
- ProjectNumber: team.ProjectNumber,
- Team: team.Team,
- }
- return json.Marshal(temp)
-}
-
-// UnmarshalJSON for ProjectTeam to customize field names
-func (team *projectTeam) UnmarshalJSON(data []byte) error {
- temp := struct {
- ProjectNumber string `json:"projectNumber"`
- Team string `json:"team"`
- }{}
- if err := json.Unmarshal(data, &temp); err != nil {
- return err
- }
- team.ProjectNumber = temp.ProjectNumber
- team.Team = temp.Team
- return nil
-}
+type objectList []Object
-type objectAttrsList []ObjectAttrs
-
-func (o objectAttrsList) Len() int {
+func (o objectList) Len() int {
return len(o)
}
-func (o objectAttrsList) Less(i int, j int) bool {
+func (o objectList) Less(i int, j int) bool {
return o[i].Name < o[j].Name
}
-func (o *objectAttrsList) Swap(i int, j int) {
+func (o *objectList) Swap(i int, j int) {
d := *o
d[i], d[j] = d[j], d[i]
}
-// CreateObject is the non-streaming version of CreateObjectStreaming.
+// CreateObject stores the given object internally.
//
-// In addition to streaming, CreateObjectStreaming returns an error instead of
-// panicking when an error occurs.
+// If the bucket within the object doesn't exist, it also creates it. If the
+// object already exists, it overrides the object.
func (s *Server) CreateObject(obj Object) {
- err := s.CreateObjectStreaming(obj.StreamingObject())
+ s.mtx.Lock()
+ defer s.mtx.Unlock()
+ err := s.createObject(obj)
if err != nil {
panic(err)
}
}
-// CreateObjectStreaming stores the given object internally.
-//
-// If the bucket within the object doesn't exist, it also creates it. If the
-// object already exists, it overwrites the object.
-func (s *Server) CreateObjectStreaming(obj StreamingObject) error {
- obj, err := s.createObject(obj, backend.NoConditions{})
- if err != nil {
- return err
- }
- obj.Close()
- return nil
-}
-
-func (s *Server) createObject(obj StreamingObject, conditions backend.Conditions) (StreamingObject, error) {
- oldBackendObj, err := s.backend.GetObject(obj.BucketName, obj.Name)
- // Calling Close before checking err is okay on objects, and the object
- // may need to be closed whether or not there's an error.
- defer oldBackendObj.Close() //lint:ignore SA5001 // see above
-
- prevVersionExisted := err == nil
-
- // The caller is responsible for closing the created object.
- newBackendObj, err := s.backend.CreateObject(toBackendObjects([]StreamingObject{obj})[0], conditions)
- if err != nil {
- return StreamingObject{}, err
- }
-
- var newObjEventAttr map[string]string
- if prevVersionExisted {
- newObjEventAttr = map[string]string{
- "overwroteGeneration": strconv.FormatInt(oldBackendObj.Generation, 10),
- }
-
- oldObjEventAttr := map[string]string{
- "overwrittenByGeneration": strconv.FormatInt(newBackendObj.Generation, 10),
- }
-
- bucket, _ := s.backend.GetBucket(obj.BucketName)
- if bucket.VersioningEnabled {
- s.eventManager.Trigger(&oldBackendObj, notification.EventArchive, oldObjEventAttr)
- } else {
- s.eventManager.Trigger(&oldBackendObj, notification.EventDelete, oldObjEventAttr)
- }
- }
-
- newObj := fromBackendObjects([]backend.StreamingObject{newBackendObj})[0]
- s.eventManager.Trigger(&newBackendObj, notification.EventFinalize, newObjEventAttr)
- return newObj, nil
-}
-
-type ListOptions struct {
- Prefix string
- Delimiter string
- Versions bool
- StartOffset string
- EndOffset string
- IncludeTrailingDelimiter bool
+func (s *Server) createObject(obj Object) error {
+ return s.backend.CreateObject(toBackendObjects([]Object{obj})[0])
}
// ListObjects returns a sorted list of objects that match the given criteria,
// or an error if the bucket doesn't exist.
-//
-// Deprecated: use ListObjectsWithOptions.
-func (s *Server) ListObjects(bucketName, prefix, delimiter string, versions bool) ([]ObjectAttrs, []string, error) {
- return s.ListObjectsWithOptions(bucketName, ListOptions{
- Prefix: prefix,
- Delimiter: delimiter,
- Versions: versions,
- })
-}
-
-func (s *Server) ListObjectsWithOptions(bucketName string, options ListOptions) ([]ObjectAttrs, []string, error) {
- backendObjects, err := s.backend.ListObjects(bucketName, options.Prefix, options.Versions)
+func (s *Server) ListObjects(bucketName, prefix, delimiter string) ([]Object, []string, error) {
+ s.mtx.RLock()
+ defer s.mtx.RUnlock()
+ backendObjects, err := s.backend.ListObjects(bucketName)
if err != nil {
return nil, nil, err
}
- objects := fromBackendObjectsAttrs(backendObjects)
- olist := objectAttrsList(objects)
+ objects := fromBackendObjects(backendObjects)
+ olist := objectList(objects)
sort.Sort(&olist)
- var respObjects []ObjectAttrs
+ var respObjects []Object
prefixes := make(map[string]bool)
for _, obj := range olist {
- if !strings.HasPrefix(obj.Name, options.Prefix) {
- continue
- }
- objName := strings.Replace(obj.Name, options.Prefix, "", 1)
- delimPos := strings.Index(objName, options.Delimiter)
- if options.Delimiter != "" && delimPos > -1 {
- prefix := obj.Name[:len(options.Prefix)+delimPos+1]
- if isInOffset(prefix, options.StartOffset, options.EndOffset) {
- prefixes[prefix] = true
- }
- if options.IncludeTrailingDelimiter && obj.Name == prefix {
- respObjects = append(respObjects, obj)
- }
- } else {
- if isInOffset(obj.Name, options.StartOffset, options.EndOffset) {
+ if strings.HasPrefix(obj.Name, prefix) {
+ objName := strings.Replace(obj.Name, prefix, "", 1)
+ delimPos := strings.Index(objName, delimiter)
+ if delimiter != "" && delimPos > -1 {
+ prefixes[obj.Name[:len(prefix)+delimPos+1]] = true
+ } else {
respObjects = append(respObjects, obj)
}
}
@@ -376,781 +95,143 @@ func (s *Server) ListObjectsWithOptions(bucketName string, options ListOptions)
return respObjects, respPrefixes, nil
}
-func isInOffset(name, startOffset, endOffset string) bool {
- if endOffset != "" && startOffset != "" {
- return strings.Compare(name, endOffset) < 0 && strings.Compare(name, startOffset) >= 0
- } else if endOffset != "" {
- return strings.Compare(name, endOffset) < 0
- } else if startOffset != "" {
- return strings.Compare(name, startOffset) >= 0
- } else {
- return true
- }
-}
-
-func getCurrentIfZero(date time.Time) time.Time {
- if date.IsZero() {
- return time.Now()
- }
- return date
-}
-
-func toBackendObjects(objects []StreamingObject) []backend.StreamingObject {
- backendObjects := make([]backend.StreamingObject, 0, len(objects))
+func toBackendObjects(objects []Object) []backend.Object {
+ backendObjects := []backend.Object{}
for _, o := range objects {
- backendObjects = append(backendObjects, backend.StreamingObject{
- ObjectAttrs: backend.ObjectAttrs{
- BucketName: o.BucketName,
- Name: o.Name,
- ContentType: o.ContentType,
- ContentEncoding: o.ContentEncoding,
- ACL: o.ACL,
- Created: getCurrentIfZero(o.Created).Format(timestampFormat),
- Deleted: o.Deleted.Format(timestampFormat),
- Updated: getCurrentIfZero(o.Updated).Format(timestampFormat),
- CustomTime: o.CustomTime.Format(timestampFormat),
- Generation: o.Generation,
- Metadata: o.Metadata,
- },
- Content: o.Content,
- })
- }
- return backendObjects
-}
-
-func bufferedObjectsToBackendObjects(objects []Object) []backend.StreamingObject {
- backendObjects := make([]backend.StreamingObject, 0, len(objects))
- for _, bufferedObject := range objects {
- o := bufferedObject.StreamingObject()
- backendObjects = append(backendObjects, backend.StreamingObject{
- ObjectAttrs: backend.ObjectAttrs{
- BucketName: o.BucketName,
- Name: o.Name,
- ContentType: o.ContentType,
- ContentEncoding: o.ContentEncoding,
- ACL: o.ACL,
- Created: getCurrentIfZero(o.Created).Format(timestampFormat),
- Deleted: o.Deleted.Format(timestampFormat),
- Updated: getCurrentIfZero(o.Updated).Format(timestampFormat),
- CustomTime: o.CustomTime.Format(timestampFormat),
- Generation: o.Generation,
- Metadata: o.Metadata,
- Crc32c: o.Crc32c,
- Md5Hash: o.Md5Hash,
- Size: o.Size,
- Etag: o.Etag,
- },
- Content: o.Content,
+ backendObjects = append(backendObjects, backend.Object{
+ BucketName: o.BucketName,
+ Name: o.Name,
+ Content: o.Content,
+ Crc32c: o.Crc32c,
+ Md5Hash: o.Md5Hash,
})
}
return backendObjects
}
-func fromBackendObjects(objects []backend.StreamingObject) []StreamingObject {
- backendObjects := make([]StreamingObject, 0, len(objects))
+func fromBackendObjects(objects []backend.Object) []Object {
+ backendObjects := []Object{}
for _, o := range objects {
- backendObjects = append(backendObjects, StreamingObject{
- ObjectAttrs: ObjectAttrs{
- BucketName: o.BucketName,
- Name: o.Name,
- Size: o.Size,
- ContentType: o.ContentType,
- ContentEncoding: o.ContentEncoding,
- Crc32c: o.Crc32c,
- Md5Hash: o.Md5Hash,
- Etag: o.Etag,
- ACL: o.ACL,
- Created: convertTimeWithoutError(o.Created),
- Deleted: convertTimeWithoutError(o.Deleted),
- Updated: convertTimeWithoutError(o.Updated),
- CustomTime: convertTimeWithoutError(o.CustomTime),
- Generation: o.Generation,
- Metadata: o.Metadata,
- },
- Content: o.Content,
+ backendObjects = append(backendObjects, Object{
+ BucketName: o.BucketName,
+ Name: o.Name,
+ Content: o.Content,
+ Crc32c: o.Crc32c,
+ Md5Hash: o.Md5Hash,
})
}
return backendObjects
}
-func fromBackendObjectsAttrs(objectAttrs []backend.ObjectAttrs) []ObjectAttrs {
- oattrs := make([]ObjectAttrs, 0, len(objectAttrs))
- for _, o := range objectAttrs {
- oattrs = append(oattrs, ObjectAttrs{
- BucketName: o.BucketName,
- Name: o.Name,
- Size: o.Size,
- ContentType: o.ContentType,
- ContentEncoding: o.ContentEncoding,
- Crc32c: o.Crc32c,
- Md5Hash: o.Md5Hash,
- Etag: o.Etag,
- ACL: o.ACL,
- Created: convertTimeWithoutError(o.Created),
- Deleted: convertTimeWithoutError(o.Deleted),
- Updated: convertTimeWithoutError(o.Updated),
- CustomTime: convertTimeWithoutError(o.CustomTime),
- Generation: o.Generation,
- Metadata: o.Metadata,
- })
- }
- return oattrs
-}
-
-func convertTimeWithoutError(t string) time.Time {
- r, _ := time.Parse(timestampFormat, t)
- return r
-}
-
-// GetObject is the non-streaming version of GetObjectStreaming.
+// GetObject returns the object with the given name in the given bucket, or an
+// error if the object doesn't exist.
func (s *Server) GetObject(bucketName, objectName string) (Object, error) {
- streamingObject, err := s.GetObjectStreaming(bucketName, objectName)
- if err != nil {
- return Object{}, err
- }
- return streamingObject.BufferedObject()
-}
-
-// GetObjectStreaming returns the object with the given name in the given
-// bucket, or an error if the object doesn't exist.
-func (s *Server) GetObjectStreaming(bucketName, objectName string) (StreamingObject, error) {
backendObj, err := s.backend.GetObject(bucketName, objectName)
- if err != nil {
- return StreamingObject{}, err
- }
- obj := fromBackendObjects([]backend.StreamingObject{backendObj})[0]
- return obj, nil
-}
-
-// GetObjectWithGeneration is the non-streaming version of
-// GetObjectWithGenerationStreaming.
-func (s *Server) GetObjectWithGeneration(bucketName, objectName string, generation int64) (Object, error) {
- streamingObject, err := s.GetObjectWithGenerationStreaming(bucketName, objectName, generation)
if err != nil {
return Object{}, err
}
- return streamingObject.BufferedObject()
-}
-
-// GetObjectWithGenerationStreaming returns the object with the given name and
-// given generation ID in the given bucket, or an error if the object doesn't
-// exist.
-//
-// If versioning is enabled, archived versions are considered.
-func (s *Server) GetObjectWithGenerationStreaming(bucketName, objectName string, generation int64) (StreamingObject, error) {
- backendObj, err := s.backend.GetObjectWithGeneration(bucketName, objectName, generation)
- if err != nil {
- return StreamingObject{}, err
- }
- obj := fromBackendObjects([]backend.StreamingObject{backendObj})[0]
+ obj := fromBackendObjects([]backend.Object{backendObj})[0]
return obj, nil
}
-func (s *Server) objectWithGenerationOnValidGeneration(bucketName, objectName, generationStr string) (StreamingObject, error) {
- generation, err := strconv.ParseInt(generationStr, 10, 64)
- if err != nil && generationStr != "" {
- return StreamingObject{}, errInvalidGeneration
- } else if generation > 0 {
- return s.GetObjectWithGenerationStreaming(bucketName, objectName, generation)
- }
- return s.GetObjectStreaming(bucketName, objectName)
-}
-
-func (s *Server) listObjects(r *http.Request) jsonResponse {
- bucketName := unescapeMuxVars(mux.Vars(r))["bucketName"]
- objs, prefixes, err := s.ListObjectsWithOptions(bucketName, ListOptions{
- Prefix: r.URL.Query().Get("prefix"),
- Delimiter: r.URL.Query().Get("delimiter"),
- Versions: r.URL.Query().Get("versions") == "true",
- StartOffset: r.URL.Query().Get("startOffset"),
- EndOffset: r.URL.Query().Get("endOffset"),
- IncludeTrailingDelimiter: r.URL.Query().Get("includeTrailingDelimiter") == "true",
- })
+func (s *Server) listObjects(w http.ResponseWriter, r *http.Request) {
+ bucketName := mux.Vars(r)["bucketName"]
+ prefix := r.URL.Query().Get("prefix")
+ delimiter := r.URL.Query().Get("delimiter")
+ objs, prefixes, err := s.ListObjects(bucketName, prefix, delimiter)
+ encoder := json.NewEncoder(w)
if err != nil {
- return jsonResponse{status: http.StatusNotFound}
- }
- return jsonResponse{data: newListObjectsResponse(objs, prefixes)}
-}
-
-func (s *Server) xmlListObjects(r *http.Request) xmlResponse {
- bucketName := unescapeMuxVars(mux.Vars(r))["bucketName"]
-
- opts := ListOptions{
- Prefix: r.URL.Query().Get("prefix"),
- Delimiter: r.URL.Query().Get("delimiter"),
- Versions: r.URL.Query().Get("versions") == "true",
- }
-
- objs, prefixes, err := s.ListObjectsWithOptions(bucketName, opts)
- if err != nil {
- return xmlResponse{
- status: http.StatusInternalServerError,
- errorMessage: err.Error(),
- }
- }
-
- result := ListBucketResult{
- Name: bucketName,
- Delimiter: opts.Delimiter,
- Prefix: opts.Prefix,
- KeyCount: len(objs),
- }
-
- if opts.Delimiter != "" {
- for _, prefix := range prefixes {
- result.CommonPrefixes = append(result.CommonPrefixes, CommonPrefix{Prefix: prefix})
- }
- }
-
- for _, obj := range objs {
- result.Contents = append(result.Contents, Contents{
- Key: obj.Name,
- Generation: obj.Generation,
- Size: obj.Size,
- LastModified: obj.Updated.Format(time.RFC3339),
- ETag: ETag{Value: obj.Etag},
- })
- }
-
- raw, err := xml.Marshal(result)
- if err != nil {
- return xmlResponse{
- status: http.StatusInternalServerError,
- errorMessage: err.Error(),
- }
- }
-
- return xmlResponse{
- status: http.StatusOK,
- data: []byte(xml.Header + string(raw)),
- }
-}
-
-func (s *Server) getObject(w http.ResponseWriter, r *http.Request) {
- if alt := r.URL.Query().Get("alt"); alt == "media" || r.Method == http.MethodHead {
- s.downloadObject(w, r)
+ w.WriteHeader(http.StatusNotFound)
+ errResp := newErrorResponse(http.StatusNotFound, "Not Found", nil)
+ encoder.Encode(errResp)
return
}
-
- handler := jsonToHTTPHandler(func(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
-
- obj, err := s.objectWithGenerationOnValidGeneration(vars["bucketName"], vars["objectName"], r.FormValue("generation"))
- // Calling Close before checking err is okay on objects, and the object
- // may need to be closed whether or not there's an error.
- defer obj.Close() //lint:ignore SA5001 // see above
- if err != nil {
- statusCode := http.StatusNotFound
- var errMessage string
- if errors.Is(err, errInvalidGeneration) {
- statusCode = http.StatusBadRequest
- errMessage = err.Error()
- }
- return jsonResponse{
- status: statusCode,
- errorMessage: errMessage,
- }
- }
- header := make(http.Header)
- header.Set("Accept-Ranges", "bytes")
- return jsonResponse{
- header: header,
- data: newObjectResponse(obj.ObjectAttrs),
- }
- })
-
- handler(w, r)
-}
-
-func (s *Server) deleteObject(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
- obj, err := s.GetObjectStreaming(vars["bucketName"], vars["objectName"])
- // Calling Close before checking err is okay on objects, and the object
- // may need to be closed whether or not there's an error.
- defer obj.Close() //lint:ignore SA5001 // see above
- if err == nil {
- err = s.backend.DeleteObject(vars["bucketName"], vars["objectName"])
- }
- if err != nil {
- return jsonResponse{status: http.StatusNotFound}
- }
- bucket, _ := s.backend.GetBucket(obj.BucketName)
- backendObj := toBackendObjects([]StreamingObject{obj})[0]
- if bucket.VersioningEnabled {
- s.eventManager.Trigger(&backendObj, notification.EventArchive, nil)
- } else {
- s.eventManager.Trigger(&backendObj, notification.EventDelete, nil)
- }
- return jsonResponse{}
+ encoder.Encode(newListObjectsResponse(objs, prefixes))
}
-func (s *Server) listObjectACL(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
-
- obj, err := s.GetObjectStreaming(vars["bucketName"], vars["objectName"])
+func (s *Server) getObject(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ encoder := json.NewEncoder(w)
+ obj, err := s.GetObject(vars["bucketName"], vars["objectName"])
if err != nil {
- return jsonResponse{status: http.StatusNotFound}
+ errResp := newErrorResponse(http.StatusNotFound, "Not Found", nil)
+ w.WriteHeader(http.StatusNotFound)
+ encoder.Encode(errResp)
+ return
}
- defer obj.Close()
-
- return jsonResponse{data: newACLListResponse(obj.ObjectAttrs)}
+ w.Header().Set("Accept-Ranges", "bytes")
+ encoder.Encode(newObjectResponse(obj))
}
-func (s *Server) setObjectACL(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
-
- obj, err := s.GetObjectStreaming(vars["bucketName"], vars["objectName"])
+func (s *Server) deleteObject(w http.ResponseWriter, r *http.Request) {
+ s.mtx.Lock()
+ defer s.mtx.Unlock()
+ vars := mux.Vars(r)
+ err := s.backend.DeleteObject(vars["bucketName"], vars["objectName"])
if err != nil {
- return jsonResponse{status: http.StatusNotFound}
- }
- defer obj.Close()
-
- var data struct {
- Entity string
- Role string
- }
-
- decoder := json.NewDecoder(r.Body)
- if err := decoder.Decode(&data); err != nil {
- return jsonResponse{
- status: http.StatusBadRequest,
- errorMessage: err.Error(),
- }
- }
-
- entity := storage.ACLEntity(data.Entity)
- role := storage.ACLRole(data.Role)
- obj.ACL = []storage.ACLRule{{
- Entity: entity,
- Role: role,
- }}
-
- obj, err = s.createObject(obj, backend.NoConditions{})
- if err != nil {
- return errToJsonResponse(err)
+ errResp := newErrorResponse(http.StatusNotFound, "Not Found", nil)
+ w.WriteHeader(http.StatusNotFound)
+ json.NewEncoder(w).Encode(errResp)
+ return
}
- defer obj.Close()
-
- return jsonResponse{data: newACLListResponse(obj.ObjectAttrs)}
+ w.WriteHeader(http.StatusOK)
}
-func (s *Server) rewriteObject(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
- obj, err := s.objectWithGenerationOnValidGeneration(vars["sourceBucket"], vars["sourceObject"], r.FormValue("sourceGeneration"))
- // Calling Close before checking err is okay on objects, and the object
- // may need to be closed whether or not there's an error.
- defer obj.Close() //lint:ignore SA5001 // see above
+func (s *Server) rewriteObject(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ obj, err := s.GetObject(vars["sourceBucket"], vars["sourceObject"])
if err != nil {
- statusCode := http.StatusNotFound
- var errMessage string
- if errors.Is(err, errInvalidGeneration) {
- statusCode = http.StatusBadRequest
- errMessage = err.Error()
- }
- return jsonResponse{errorMessage: errMessage, status: statusCode}
- }
-
- var metadata multipartMetadata
- err = json.NewDecoder(r.Body).Decode(&metadata)
- if err != nil && err != io.EOF { // The body is optional
- return jsonResponse{errorMessage: "Invalid metadata", status: http.StatusBadRequest}
- }
-
- // Only supplied metadata overwrites the new object's metdata
- if len(metadata.Metadata) == 0 {
- metadata.Metadata = obj.Metadata
- }
- if metadata.ContentType == "" {
- metadata.ContentType = obj.ContentType
- }
- if metadata.ContentEncoding == "" {
- metadata.ContentEncoding = obj.ContentEncoding
+ http.Error(w, "not found", http.StatusNotFound)
+ return
}
-
dstBucket := vars["destinationBucket"]
- newObject := StreamingObject{
- ObjectAttrs: ObjectAttrs{
- BucketName: dstBucket,
- Name: vars["destinationObject"],
- ACL: obj.ACL,
- ContentType: metadata.ContentType,
- ContentEncoding: metadata.ContentEncoding,
- Metadata: metadata.Metadata,
- },
- Content: obj.Content,
+ newObject := Object{
+ BucketName: dstBucket,
+ Name: vars["destinationObject"],
+ Content: append([]byte(nil), obj.Content...),
+ Crc32c: obj.Crc32c,
+ Md5Hash: obj.Md5Hash,
}
-
- created, err := s.createObject(newObject, backend.NoConditions{})
- if err != nil {
- return errToJsonResponse(err)
- }
- defer created.Close()
-
- if vars["copyType"] == "copyTo" {
- return jsonResponse{data: newObjectResponse(created.ObjectAttrs)}
- }
- return jsonResponse{data: newObjectRewriteResponse(created.ObjectAttrs)}
+ s.CreateObject(newObject)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(newObjectRewriteResponse(newObject))
}
func (s *Server) downloadObject(w http.ResponseWriter, r *http.Request) {
- vars := unescapeMuxVars(mux.Vars(r))
- obj, err := s.objectWithGenerationOnValidGeneration(vars["bucketName"], vars["objectName"], r.FormValue("generation"))
- // Calling Close before checking err is okay on objects, and the object
- // may need to be closed whether or not there's an error.
- defer obj.Close() //lint:ignore SA5001 // see above
+ vars := mux.Vars(r)
+ obj, err := s.GetObject(vars["bucketName"], vars["objectName"])
if err != nil {
- statusCode := http.StatusNotFound
- message := http.StatusText(statusCode)
- if errors.Is(err, errInvalidGeneration) {
- statusCode = http.StatusBadRequest
- message = err.Error()
- }
- http.Error(w, message, statusCode)
+ http.Error(w, "not found", http.StatusNotFound)
return
}
-
- var content io.Reader
- content = obj.Content
status := http.StatusOK
-
- transcoded := false
- ranged := false
- start := int64(0)
- lastByte := int64(0)
- satisfiable := true
- contentLength := int64(0)
-
- handledTranscoding := func() bool {
- // This should also be false if the Cache-Control metadata field == "no-transform",
- // but we don't currently support that field.
- // See https://cloud.google.com/storage/docs/transcoding
-
- if obj.ContentEncoding == "gzip" && !strings.Contains(r.Header.Get("accept-encoding"), "gzip") {
- // GCS will transparently decompress gzipped content, see
- // https://cloud.google.com/storage/docs/transcoding
- // In this case, any Range header is ignored and the full content is returned.
-
- // If the content is not a valid gzip file, ignore errors and continue
- // without transcoding. Otherwise, return decompressed content.
- gzipReader, err := gzip.NewReader(content)
- if err == nil {
- rawContent, err := io.ReadAll(gzipReader)
- if err == nil {
- transcoded = true
- content = bytes.NewReader(rawContent)
- contentLength = int64(len(rawContent))
- obj.Size = contentLength
- return true
- }
- }
- }
- return false
- }
-
- if !handledTranscoding() {
- ranged, start, lastByte, satisfiable = s.handleRange(obj, r)
- contentLength = lastByte - start + 1
- }
-
- if ranged && satisfiable {
- _, err = obj.Content.Seek(start, io.SeekStart)
- if err != nil {
- http.Error(w, "could not seek", http.StatusInternalServerError)
- return
- }
- content = io.LimitReader(obj.Content, contentLength)
+ start, end, content := s.handleRange(obj, r)
+ if len(content) != len(obj.Content) {
status = http.StatusPartialContent
- w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, lastByte, obj.Size))
+ w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, len(obj.Content)))
}
w.Header().Set("Accept-Ranges", "bytes")
- w.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10))
- w.Header().Set("X-Goog-Generation", strconv.FormatInt(obj.Generation, 10))
- w.Header().Set("X-Goog-Hash", fmt.Sprintf("crc32c=%s,md5=%s", obj.Crc32c, obj.Md5Hash))
- w.Header().Set("Last-Modified", obj.Updated.Format(http.TimeFormat))
- w.Header().Set("ETag", obj.Etag)
- for name, value := range obj.Metadata {
- w.Header().Set("X-Goog-Meta-"+name, value)
- }
- w.Header().Set("Access-Control-Allow-Origin", "*")
-
- if ranged && !satisfiable {
- status = http.StatusRequestedRangeNotSatisfiable
- content = bytes.NewReader([]byte(fmt.Sprintf(``+
- `InvalidRange
`+
- `The requested range cannot be satisfied.`+
- `%s `, r.Header.Get("Range"))))
- w.Header().Set(contentTypeHeader, "application/xml; charset=UTF-8")
- } else {
- if obj.ContentType != "" {
- w.Header().Set(contentTypeHeader, obj.ContentType)
- }
- // If content was transcoded, the underlying encoding was removed so we shouldn't report it.
- if obj.ContentEncoding != "" && !transcoded {
- w.Header().Set("Content-Encoding", obj.ContentEncoding)
- }
- // X-Goog-Stored-Content-Encoding must be set to the original encoding,
- // defaulting to "identity" if no encoding was set.
- storedContentEncoding := "identity"
- if obj.ContentEncoding != "" {
- storedContentEncoding = obj.ContentEncoding
- }
- w.Header().Set("X-Goog-Stored-Content-Encoding", storedContentEncoding)
- }
-
+ w.Header().Set("Content-Length", strconv.Itoa(len(content)))
w.WriteHeader(status)
if r.Method == http.MethodGet {
- io.Copy(w, content)
+ w.Write(content)
}
}
-func (s *Server) handleRange(obj StreamingObject, r *http.Request) (ranged bool, start int64, lastByte int64, satisfiable bool) {
- start, end, err := parseRange(r.Header.Get("Range"), obj.Size)
- if err != nil {
- // If the range isn't valid, GCS returns all content.
- return false, 0, obj.Size - 1, false
- }
- // GCS is pretty flexible when it comes to invalid ranges. A 416 http
- // response is only returned when the range start is beyond the length of
- // the content. Otherwise, the range is ignored.
- switch {
- // Invalid start. Return 416 and NO content.
- // Examples:
- // Length: 40, Range: bytes=50-60
- // Length: 40, Range: bytes=50-
- case start >= obj.Size:
- // This IS a ranged request, but it ISN'T satisfiable.
- return true, 0, 0, false
- // Negative range, ignore range and return all content.
- // Examples:
- // Length: 40, Range: bytes=30-20
- case end < start:
- return false, 0, obj.Size - 1, false
- // Return range. Clamp start and end.
- // Examples:
- // Length: 40, Range: bytes=-100
- // Length: 40, Range: bytes=0-100
- default:
- if start < 0 {
- start = 0
- }
- if end >= obj.Size {
- end = obj.Size - 1
- }
- return true, start, end, true
- }
-}
-
-// parseRange parses the range header and returns the corresponding start and
-// end indices in the content. The end index is inclusive. This function
-// doesn't validate that the start and end indices fall within the content
-// bounds. The content length is only used to handle "suffix length" and
-// range-to-end ranges.
-func parseRange(rangeHeaderValue string, contentLength int64) (start int64, end int64, err error) {
- // For information about the range header, see:
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
- // https://httpwg.org/specs/rfc7233.html#header.range
- // https://httpwg.org/specs/rfc7233.html#byte.ranges
- // https://httpwg.org/specs/rfc7233.html#status.416
- //
- // =
- //
- // The following ranges are parsed:
- // "bytes=40-50" (range with given start and end)
- // "bytes=40-" (range to end of content)
- // "bytes=-40" (suffix length, offset from end of string)
- //
- // The unit MUST be "bytes".
- parts := strings.SplitN(rangeHeaderValue, "=", 2)
- if len(parts) != 2 {
- return 0, 0, fmt.Errorf("expecting `=` in range header, got: %s", rangeHeaderValue)
- }
- if parts[0] != "bytes" {
- return 0, 0, fmt.Errorf("invalid range unit, expecting `bytes`, got: %s", parts[0])
- }
- rangeSpec := parts[1]
- if len(rangeSpec) == 0 {
- return 0, 0, errors.New("empty range")
- }
- if rangeSpec[0] == '-' {
- offsetFromEnd, err := strconv.ParseInt(rangeSpec, 10, 64)
- if err != nil {
- return 0, 0, fmt.Errorf("invalid suffix length, got: %s", rangeSpec)
- }
- start = contentLength + offsetFromEnd
- end = contentLength - 1
- } else {
- rangeParts := strings.SplitN(rangeSpec, "-", 2)
- if len(rangeParts) != 2 {
- return 0, 0, fmt.Errorf("only one range supported, got: %s", rangeSpec)
- }
- start, err = strconv.ParseInt(rangeParts[0], 10, 64)
- if err != nil {
- return 0, 0, fmt.Errorf("invalid range start, got: %s", rangeParts[0])
- }
- if rangeParts[1] == "" {
- end = contentLength - 1
- } else {
- end, err = strconv.ParseInt(rangeParts[1], 10, 64)
- if err != nil {
- return 0, 0, fmt.Errorf("invalid range end, got: %s", rangeParts[1])
+func (s *Server) handleRange(obj Object, r *http.Request) (start, end int, content []byte) {
+ if reqRange := r.Header.Get("Range"); reqRange != "" {
+ parts := strings.SplitN(reqRange, "=", 2)
+ if len(parts) == 2 && parts[0] == "bytes" {
+ rangeParts := strings.SplitN(parts[1], "-", 2)
+ if len(rangeParts) == 2 {
+ start, _ = strconv.Atoi(rangeParts[0])
+ end, _ = strconv.Atoi(rangeParts[1])
+ if end < 1 {
+ end = len(obj.Content)
+ }
+ return start, end, obj.Content[start:end]
}
}
}
- return start, end, nil
-}
-
-func (s *Server) patchObject(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
- bucketName := vars["bucketName"]
- objectName := vars["objectName"]
-
- type acls struct {
- Entity string
- Role string
- }
-
- var payload struct {
- ContentType string
- ContentEncoding string
- Metadata map[string]string `json:"metadata"`
- CustomTime string
- Acl []acls
- }
- err := json.NewDecoder(r.Body).Decode(&payload)
- if err != nil {
- return jsonResponse{
- status: http.StatusBadRequest,
- errorMessage: "Metadata in the request couldn't decode",
- }
- }
-
- var attrsToUpdate backend.ObjectAttrs
-
- attrsToUpdate.ContentType = payload.ContentType
- attrsToUpdate.ContentEncoding = payload.ContentEncoding
- attrsToUpdate.Metadata = payload.Metadata
- attrsToUpdate.CustomTime = payload.CustomTime
-
- if len(payload.Acl) > 0 {
- attrsToUpdate.ACL = []storage.ACLRule{}
- for _, aclData := range payload.Acl {
- newAcl := storage.ACLRule{Entity: storage.ACLEntity(aclData.Entity), Role: storage.ACLRole(aclData.Role)}
- attrsToUpdate.ACL = append(attrsToUpdate.ACL, newAcl)
- }
- }
-
- backendObj, err := s.backend.PatchObject(bucketName, objectName, attrsToUpdate)
- if err != nil {
- return jsonResponse{
- status: http.StatusNotFound,
- errorMessage: "Object not found to be PATCHed",
- }
- }
- defer backendObj.Close()
-
- s.eventManager.Trigger(&backendObj, notification.EventMetadata, nil)
- return jsonResponse{data: fromBackendObjects([]backend.StreamingObject{backendObj})[0]}
-}
-
-func (s *Server) updateObject(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
- bucketName := vars["bucketName"]
- objectName := vars["objectName"]
-
- type acls struct {
- Entity string
- Role string
- }
-
- var payload struct {
- Metadata map[string]string `json:"metadata"`
- ContentType string `json:"contentType"`
- CustomTime string
- Acl []acls
- }
- err := json.NewDecoder(r.Body).Decode(&payload)
- if err != nil {
- return jsonResponse{
- status: http.StatusBadRequest,
- errorMessage: "Metadata in the request couldn't decode",
- }
- }
-
- var attrsToUpdate backend.ObjectAttrs
-
- attrsToUpdate.Metadata = payload.Metadata
- attrsToUpdate.CustomTime = payload.CustomTime
- attrsToUpdate.ContentType = payload.ContentType
- if len(payload.Acl) > 0 {
- attrsToUpdate.ACL = []storage.ACLRule{}
- for _, aclData := range payload.Acl {
- newAcl := storage.ACLRule{Entity: storage.ACLEntity(aclData.Entity), Role: storage.ACLRole(aclData.Role)}
- attrsToUpdate.ACL = append(attrsToUpdate.ACL, newAcl)
- }
- }
- backendObj, err := s.backend.UpdateObject(bucketName, objectName, attrsToUpdate)
- if err != nil {
- return jsonResponse{
- status: http.StatusNotFound,
- errorMessage: "Object not found to be updated",
- }
- }
- defer backendObj.Close()
-
- s.eventManager.Trigger(&backendObj, notification.EventMetadata, nil)
- return jsonResponse{data: fromBackendObjects([]backend.StreamingObject{backendObj})[0]}
-}
-
-func (s *Server) composeObject(r *http.Request) jsonResponse {
- vars := unescapeMuxVars(mux.Vars(r))
- bucketName := vars["bucketName"]
- destinationObject := vars["destinationObject"]
-
- var composeRequest struct {
- SourceObjects []struct {
- Name string
- }
- Destination struct {
- Bucket string
- ContentType string
- Metadata map[string]string
- }
- }
-
- decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&composeRequest)
- if err != nil {
- return jsonResponse{
- status: http.StatusBadRequest,
- errorMessage: "Error parsing request body",
- }
- }
-
- 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)
- }
-
- backendObj, err := s.backend.ComposeObject(bucketName, sourceNames, destinationObject, composeRequest.Destination.Metadata, composeRequest.Destination.ContentType)
- if err != nil {
- return jsonResponse{
- status: http.StatusInternalServerError,
- errorMessage: "Error running compose",
- }
- }
- defer backendObj.Close()
-
- obj := fromBackendObjects([]backend.StreamingObject{backendObj})[0]
-
- s.eventManager.Trigger(&backendObj, notification.EventFinalize, nil)
-
- return jsonResponse{data: newObjectResponse(obj.ObjectAttrs)}
+ return 0, 0, obj.Content
}
diff --git a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/response.go b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/response.go
index e28b84eeb73af..92164cafb1057 100644
--- a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/response.go
+++ b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/response.go
@@ -4,72 +4,44 @@
package fakestorage
-import (
- "time"
-
- "github.com/fsouza/fake-gcs-server/internal/backend"
-)
-
-const timestampFormat = "2006-01-02T15:04:05.999999Z07:00"
-
-func formatTime(t time.Time) string {
- if t.IsZero() {
- return ""
- }
- return t.Format(timestampFormat)
-}
+import "sort"
type listResponse struct {
- Kind string `json:"kind"`
- Items []any `json:"items,omitempty"`
- Prefixes []string `json:"prefixes,omitempty"`
+ Kind string `json:"kind"`
+ Items []interface{} `json:"items"`
+ Prefixes []string `json:"prefixes"`
}
-func newListBucketsResponse(buckets []backend.Bucket, location string) listResponse {
+func newListBucketsResponse(bucketNames []string) listResponse {
resp := listResponse{
Kind: "storage#buckets",
- Items: make([]any, len(buckets)),
+ Items: make([]interface{}, len(bucketNames)),
}
- for i, bucket := range buckets {
- resp.Items[i] = newBucketResponse(bucket, location)
+ sort.Strings(bucketNames)
+ for i, name := range bucketNames {
+ resp.Items[i] = newBucketResponse(name)
}
return resp
}
type bucketResponse struct {
- Kind string `json:"kind"`
- ID string `json:"id"`
- DefaultEventBasedHold bool `json:"defaultEventBasedHold"`
- Name string `json:"name"`
- Versioning *bucketVersioning `json:"versioning,omitempty"`
- TimeCreated string `json:"timeCreated,omitempty"`
- Updated string `json:"updated,omitempty"`
- Location string `json:"location,omitempty"`
- StorageClass string `json:"storageClass,omitempty"`
+ Kind string `json:"kind"`
+ ID string `json:"id"`
+ Name string `json:"name"`
}
-type bucketVersioning struct {
- Enabled bool `json:"enabled,omitempty"`
-}
-
-func newBucketResponse(bucket backend.Bucket, location string) bucketResponse {
+func newBucketResponse(bucketName string) bucketResponse {
return bucketResponse{
- Kind: "storage#bucket",
- ID: bucket.Name,
- Name: bucket.Name,
- DefaultEventBasedHold: bucket.DefaultEventBasedHold,
- Versioning: &bucketVersioning{bucket.VersioningEnabled},
- TimeCreated: formatTime(bucket.TimeCreated),
- Updated: formatTime(bucket.TimeCreated), // not tracking update times yet, reporting `updated` = `timeCreated`
- Location: location,
- StorageClass: "STANDARD",
+ Kind: "storage#bucket",
+ ID: bucketName,
+ Name: bucketName,
}
}
-func newListObjectsResponse(objs []ObjectAttrs, prefixes []string) listResponse {
+func newListObjectsResponse(objs []Object, prefixes []string) listResponse {
resp := listResponse{
Kind: "storage#objects",
- Items: make([]any, len(objs)),
+ Items: make([]interface{}, len(objs)),
Prefixes: prefixes,
}
for i, obj := range objs {
@@ -78,93 +50,27 @@ func newListObjectsResponse(objs []ObjectAttrs, prefixes []string) listResponse
return resp
}
-// objectAccessControl is copied from the Google SDK to avoid direct
-// dependency.
-type objectAccessControl struct {
- Bucket string `json:"bucket,omitempty"`
- Domain string `json:"domain,omitempty"`
- Email string `json:"email,omitempty"`
- Entity string `json:"entity,omitempty"`
- EntityID string `json:"entityId,omitempty"`
- Etag string `json:"etag,omitempty"`
- Generation int64 `json:"generation,omitempty,string"`
- ID string `json:"id,omitempty"`
- Kind string `json:"kind,omitempty"`
- Object string `json:"object,omitempty"`
- ProjectTeam struct {
- ProjectNumber string `json:"projectNumber,omitempty"`
- Team string `json:"team,omitempty"`
- } `json:"projectTeam,omitempty"`
- Role string `json:"role,omitempty"`
- SelfLink string `json:"selfLink,omitempty"`
-}
-
type objectResponse struct {
- Kind string `json:"kind"`
- Name string `json:"name"`
- ID string `json:"id"`
- Bucket string `json:"bucket"`
- Size int64 `json:"size,string"`
- ContentType string `json:"contentType,omitempty"`
- ContentEncoding string `json:"contentEncoding,omitempty"`
- Crc32c string `json:"crc32c,omitempty"`
- ACL []*objectAccessControl `json:"acl,omitempty"`
- Md5Hash string `json:"md5Hash,omitempty"`
- Etag string `json:"etag,omitempty"`
- TimeCreated string `json:"timeCreated,omitempty"`
- TimeDeleted string `json:"timeDeleted,omitempty"`
- Updated string `json:"updated,omitempty"`
- Generation int64 `json:"generation,string"`
- CustomTime string `json:"customTime,omitempty"`
- Metadata map[string]string `json:"metadata,omitempty"`
+ Kind string `json:"kind"`
+ Name string `json:"name"`
+ ID string `json:"id"`
+ Bucket string `json:"bucket"`
+ Size int64 `json:"size,string"`
+ // Crc32c: CRC32c checksum, same as in google storage client code
+ Crc32c string `json:"crc32c,omitempty"`
+ Md5Hash string `json:"md5hash,omitempty"`
}
-func newObjectResponse(obj ObjectAttrs) objectResponse {
- acl := getAccessControlsListFromObject(obj)
-
+func newObjectResponse(obj Object) objectResponse {
return objectResponse{
- Kind: "storage#object",
- ID: obj.id(),
- Bucket: obj.BucketName,
- Name: obj.Name,
- Size: obj.Size,
- ContentType: obj.ContentType,
- ContentEncoding: obj.ContentEncoding,
- Crc32c: obj.Crc32c,
- Md5Hash: obj.Md5Hash,
- Etag: obj.Etag,
- ACL: acl,
- Metadata: obj.Metadata,
- TimeCreated: formatTime(obj.Created),
- TimeDeleted: formatTime(obj.Deleted),
- Updated: formatTime(obj.Updated),
- CustomTime: formatTime(obj.CustomTime),
- Generation: obj.Generation,
- }
-}
-
-type aclListResponse struct {
- Items []*objectAccessControl `json:"items"`
-}
-
-func newACLListResponse(obj ObjectAttrs) aclListResponse {
- if len(obj.ACL) == 0 {
- return aclListResponse{}
- }
- return aclListResponse{Items: getAccessControlsListFromObject(obj)}
-}
-
-func getAccessControlsListFromObject(obj ObjectAttrs) []*objectAccessControl {
- aclItems := make([]*objectAccessControl, len(obj.ACL))
- for idx, aclRule := range obj.ACL {
- aclItems[idx] = &objectAccessControl{
- Bucket: obj.BucketName,
- Entity: string(aclRule.Entity),
- Object: obj.Name,
- Role: string(aclRule.Role),
- }
+ Kind: "storage#object",
+ ID: obj.id(),
+ Bucket: obj.BucketName,
+ Name: obj.Name,
+ Size: int64(len(obj.Content)),
+ Crc32c: obj.Crc32c,
+ Md5Hash: obj.Md5Hash,
}
- return aclItems
}
type rewriteResponse struct {
@@ -176,11 +82,11 @@ type rewriteResponse struct {
Resource objectResponse `json:"resource"`
}
-func newObjectRewriteResponse(obj ObjectAttrs) rewriteResponse {
+func newObjectRewriteResponse(obj Object) rewriteResponse {
return rewriteResponse{
Kind: "storage#rewriteResponse",
- TotalBytesRewritten: obj.Size,
- ObjectSize: obj.Size,
+ TotalBytesRewritten: int64(len(obj.Content)),
+ ObjectSize: int64(len(obj.Content)),
Done: true,
RewriteToken: "",
Resource: newObjectResponse(obj),
diff --git a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/server.go b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/server.go
index 7d5f1da33aa2d..165d9d7ec2ed4 100644
--- a/vendor/github.com/fsouza/fake-gcs-server/fakestorage/server.go
+++ b/vendor/github.com/fsouza/fake-gcs-server/fakestorage/server.go
@@ -5,52 +5,30 @@
package fakestorage
import (
- "bufio"
- "bytes"
- "compress/gzip"
"context"
"crypto/tls"
- "errors"
"fmt"
- "io"
- "mime"
- "mime/multipart"
"net"
"net/http"
"net/http/httptest"
- "net/http/httputil"
- "net/textproto"
- "net/url"
- "os"
- "path/filepath"
- "strings"
"sync"
"cloud.google.com/go/storage"
"github.com/fsouza/fake-gcs-server/internal/backend"
- "github.com/fsouza/fake-gcs-server/internal/checksum"
- "github.com/fsouza/fake-gcs-server/internal/notification"
- "github.com/gorilla/handlers"
"github.com/gorilla/mux"
- "golang.org/x/oauth2/google"
"google.golang.org/api/option"
)
-const defaultPublicHost = "storage.googleapis.com"
-
// Server is the fake server.
//
// It provides a fake implementation of the Google Cloud Storage API.
type Server struct {
- backend backend.Storage
- uploads sync.Map
- transport http.RoundTripper
- ts *httptest.Server
- handler http.Handler
- options Options
- externalURL string
- publicHost string
- eventManager notification.EventManager
+ backend backend.Storage
+ uploads map[string]Object
+ transport http.RoundTripper
+ ts *httptest.Server
+ mux *mux.Router
+ mtx sync.RWMutex
}
// NewServer creates a new instance of the server, pre-loaded with the given
@@ -63,8 +41,6 @@ func NewServer(objects []Object) *Server {
}
// NewServerWithHostPort creates a new server that listens on a custom host and port
-//
-// Deprecated: use NewServerWithOptions.
func NewServerWithHostPort(objects []Object, host string, port uint16) (*Server, error) {
return NewServerWithOptions(Options{
InitialObjects: objects,
@@ -73,102 +49,30 @@ func NewServerWithHostPort(objects []Object, host string, port uint16) (*Server,
})
}
-// Options are used to configure the server on creation.
+// Options are used to configure the server on creation
type Options struct {
InitialObjects []Object
StorageRoot string
- Seed string
- Scheme string
Host string
Port uint16
// when set to true, the server will not actually start a TCP listener,
// client requests will get processed by an internal mocked transport.
NoListener bool
-
- // Optional external URL, such as https://gcs.127.0.0.1.nip.io:4443
- // Returned in the Location header for resumable uploads
- // The "real" value is https://www.googleapis.com, the JSON API
- // The default is whatever the server is bound to, such as https://0.0.0.0:4443
- ExternalURL string
-
- // Optional URL for public access
- // An example is "storage.gcs.127.0.0.1.nip.io:4443", which will configure
- // the server to serve objects at:
- // https://storage.gcs.127.0.0.1.nip.io:4443//