Repository: fsouza/fake-gcs-server
Pull request #1038 (2023-01-09 18:01:08)

build(deps): bump actions/cache from 3.2.2 to 3.2.3
Bumps [actions/cache](https://github.com/actions/cache) from 3.2.2 to 3.2.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/cache/releases">actions/cache's releases</a>.</em></p>
<blockquote>
<h2>v3.2.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Add Mint example by <a href="https://github.com/uhooi"><code>@​uhooi</code></a> in <a href="https://github-redirect.dependabot.com/actions/cache/pull/1051">actions/cache#1051</a></li>
<li>Fixed broken link by <a href="https://github.com/kotewar"><code>@​kotewar</code></a> in <a href="https://github-redirect.dependabot.com/actions/cache/pull/1057">actions/cache#1057</a></li>
<li>Add support to opt-in enable cross-os caching on windows by <a href="https://github.com/Phantsure"><code>@​Phantsure</code></a> in <a href="https://github-redirect.dependabot.com/actions/cache/pull/1056">actions/cache#1056</a></li>
<li>Release support for cross-os caching as opt-in feature by <a href="https://github.com/Phantsure"><code>@​Phantsure</code></a> in <a href="https://github-redirect.dependabot.com/actions/cache/pull/1060">actions/cache#1060</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/uhooi"><code>@​uhooi</code></a> made their first contribution in <a href="https://github-redirect.dependabot.com/actions/cache/pull/1051">actions/cache#1051</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v3...v3.2.3">https://github.com/actions/cache/compare/v3...v3.2.3</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's changelog</a>.</em></p>
<blockquote>
<h3>3.2.2</h3>
<ul>
<li>Reverted the changes made in 3.2.1 to use gnu tar and zstd by default on windows.</li>
</ul>
<h3>3.2.3</h3>
<ul>
<li>Support cross os caching on Windows as an opt-in feature.</li>
<li>Fix issue with symlink restoration on Windows for cross-os caches.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/cache/commit/58c146cc91c5b9e778e71775dfe9bf1442ad9a12"><code>58c146c</code></a> Release support for cross-os caching as opt-in feature (<a href="https://github-redirect.dependabot.com/actions/cache/issues/1060">#1060</a>)</li>
<li><a href="https://github.com/actions/cache/commit/6fd2d4538ca777f67fccddb233cf1a8ff1339012"><code>6fd2d45</code></a> Add support to opt-in enable cross-os caching on windows (<a href="https://github-redirect.dependabot.com/actions/cache/issues/1056">#1056</a>)</li>
<li><a href="https://github.com/actions/cache/commit/1f414295fe1e1bab9fb9b7ef11ce3f9fe556343c"><code>1f41429</code></a> Fixed broken link (<a href="https://github-redirect.dependabot.com/actions/cache/issues/1057">#1057</a>)</li>
<li><a href="https://github.com/actions/cache/commit/365406cb701e1cc758077b05049298a49a8b71c2"><code>365406c</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/actions/cache/issues/1051">#1051</a> from uhooi/feature/add_mint_example</li>
<li><a href="https://github.com/actions/cache/commit/d6217569d58fbcf42f4bdf873fd06820f76724c2"><code>d621756</code></a> Update Mint example</li>
<li><a href="https://github.com/actions/cache/commit/84e54000da2dc6a631a786445872e84be33086ea"><code>84e5400</code></a> Merge remote-tracking branch 'origin/main' into feature/add_mint_example</li>
<li>See full diff in <a href="https://github.com/actions/cache/compare/v3.2.2...v3.2.3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=3.2.2&new-version=3.2.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7338095..3f904d8 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -75,7 +75,7 @@ jobs:
 
       - name: staticcheck cache
         id: staticcheck-cache
-        uses: actions/cache@v3.2.2
+        uses: actions/cache@v3.2.3
         with:
           path: ~/.cache/staticcheck
           key: "${{ steps.get-staticcheck-version.outputs.version }}"
diff --git a/Dockerfile b/Dockerfile
index ca1efe0..2ee8616 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,7 +10,7 @@ ADD . ./
 ENV CGO_ENABLED=0
 RUN go build -o fake-gcs-server
 
-FROM alpine:3.17.0
+FROM alpine:3.17.1
 RUN apk add --no-cache mailcap
 COPY --from=builder /code/fake-gcs-server /bin/fake-gcs-server
 RUN /bin/fake-gcs-server -h
diff --git a/fakestorage/object.go b/fakestorage/object.go
index 4b704ec..156e5ea 100644
--- a/fakestorage/object.go
+++ b/fakestorage/object.go
@@ -42,6 +42,7 @@ type ObjectAttrs struct {
 	Created    time.Time
 	Updated    time.Time
 	Deleted    time.Time
+	CustomTime time.Time
 	Generation int64
 	Metadata   map[string]string
 }
@@ -50,24 +51,27 @@ func (o *ObjectAttrs) 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 := 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"`
-		Generation      int64             `json:"generation,omitempty,string"`
-		Metadata        map[string]string `json:"metadata,omitempty"`
-	}{
+	temp := jsonObject{
 		BucketName:      o.BucketName,
 		Name:            o.Name,
 		ContentType:     o.ContentType,
@@ -79,6 +83,7 @@ func (o ObjectAttrs) MarshalJSON() ([]byte, error) {
 		Created:         o.Created,
 		Updated:         o.Updated,
 		Deleted:         o.Deleted,
+		CustomTime:      o.CustomTime,
 		Generation:      o.Generation,
 		Metadata:        o.Metadata,
 	}
@@ -91,22 +96,7 @@ func (o ObjectAttrs) MarshalJSON() ([]byte, error) {
 
 // UnmarshalJSON for ObjectAttrs to use ACLRule instead of storage.ACLRule
 func (o *ObjectAttrs) UnmarshalJSON(data []byte) error {
-	temp := 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"`
-		Generation      int64             `json:"generation,omitempty,string"`
-		Metadata        map[string]string `json:"metadata,omitempty"`
-	}{}
+	var temp jsonObject
 	if err := json.Unmarshal(data, &temp); err != nil {
 		return err
 	}
@@ -123,6 +113,7 @@ func (o *ObjectAttrs) UnmarshalJSON(data []byte) error {
 	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)
@@ -416,6 +407,7 @@ func toBackendObjects(objects []StreamingObject) []backend.StreamingObject {
 				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,
 			},
@@ -439,6 +431,7 @@ func bufferedObjectsToBackendObjects(objects []Object) []backend.StreamingObject
 				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,
 			},
@@ -465,6 +458,7 @@ func fromBackendObjects(objects []backend.StreamingObject) []StreamingObject {
 				Created:         convertTimeWithoutError(o.Created),
 				Deleted:         convertTimeWithoutError(o.Deleted),
 				Updated:         convertTimeWithoutError(o.Updated),
+				CustomTime:      convertTimeWithoutError(o.CustomTime),
 				Generation:      o.Generation,
 				Metadata:        o.Metadata,
 			},
@@ -490,6 +484,7 @@ func fromBackendObjectsAttrs(objectAttrs []backend.ObjectAttrs) []ObjectAttrs {
 			Created:         convertTimeWithoutError(o.Created),
 			Deleted:         convertTimeWithoutError(o.Deleted),
 			Updated:         convertTimeWithoutError(o.Updated),
+			CustomTime:      convertTimeWithoutError(o.CustomTime),
 			Generation:      o.Generation,
 			Metadata:        o.Metadata,
 		})
@@ -945,8 +940,9 @@ func (s *Server) patchObject(r *http.Request) jsonResponse {
 	}
 
 	var payload struct {
-		Metadata map[string]string `json:"metadata"`
-		Acl      []acls
+		Metadata   map[string]string `json:"metadata"`
+		CustomTime string
+		Acl        []acls
 	}
 	err := json.NewDecoder(r.Body).Decode(&payload)
 	if err != nil {
@@ -959,6 +955,7 @@ func (s *Server) patchObject(r *http.Request) jsonResponse {
 	var attrsToUpdate backend.ObjectAttrs
 
 	attrsToUpdate.Metadata = payload.Metadata
+	attrsToUpdate.CustomTime = payload.CustomTime
 
 	if len(payload.Acl) > 0 {
 		attrsToUpdate.ACL = []storage.ACLRule{}
@@ -978,7 +975,8 @@ func (s *Server) patchObject(r *http.Request) jsonResponse {
 	defer backendObj.Close()
 
 	s.eventManager.Trigger(&backendObj, notification.EventMetadata, nil)
-	return jsonResponse{data: fromBackendObjects([]backend.StreamingObject{backendObj})[0]}
+	resp := jsonResponse{data: fromBackendObjects([]backend.StreamingObject{backendObj})[0]}
+	return resp
 }
 
 func (s *Server) updateObject(r *http.Request) jsonResponse {
diff --git a/fakestorage/response.go b/fakestorage/response.go
index 779dd58..56299f8 100644
--- a/fakestorage/response.go
+++ b/fakestorage/response.go
@@ -109,6 +109,7 @@ type objectResponse struct {
 	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"`
 }
 
@@ -131,6 +132,7 @@ func newObjectResponse(obj ObjectAttrs) objectResponse {
 		TimeCreated:     formatTime(obj.Created),
 		TimeDeleted:     formatTime(obj.Deleted),
 		Updated:         formatTime(obj.Updated),
+		CustomTime:      formatTime(obj.CustomTime),
 		Generation:      obj.Generation,
 	}
 }
diff --git a/fakestorage/upload.go b/fakestorage/upload.go
index 42b8c3c..0499864 100644
--- a/fakestorage/upload.go
+++ b/fakestorage/upload.go
@@ -17,6 +17,7 @@ import (
 	"net/url"
 	"strconv"
 	"strings"
+	"time"
 
 	"cloud.google.com/go/storage"
 	"github.com/fsouza/fake-gcs-server/internal/backend"
@@ -35,6 +36,7 @@ const (
 type multipartMetadata struct {
 	ContentType     string            `json:"contentType"`
 	ContentEncoding string            `json:"contentEncoding"`
+	CustomTime      time.Time         `json:"customTime,omitempty"`
 	Name            string            `json:"name"`
 	Metadata        map[string]string `json:"metadata"`
 }
@@ -193,6 +195,7 @@ func (s *Server) simpleUpload(bucketName string, r *http.Request) jsonResponse {
 	name := r.URL.Query().Get("name")
 	predefinedACL := r.URL.Query().Get("predefinedAcl")
 	contentEncoding := r.URL.Query().Get("contentEncoding")
+	customTime := r.URL.Query().Get("customTime")
 	if name == "" {
 		return jsonResponse{
 			status:       http.StatusBadRequest,
@@ -205,6 +208,7 @@ func (s *Server) simpleUpload(bucketName string, r *http.Request) jsonResponse {
 			Name:            name,
 			ContentType:     r.Header.Get(contentTypeHeader),
 			ContentEncoding: contentEncoding,
+			CustomTime:      convertTimeWithoutError(customTime),
 			ACL:             getObjectACL(predefinedACL),
 		},
 		Content: notImplementedSeeker{r.Body},
@@ -230,6 +234,7 @@ func (s *Server) signedUpload(bucketName string, r *http.Request) jsonResponse {
 	name := unescapeMuxVars(mux.Vars(r))["objectName"]
 	predefinedACL := r.URL.Query().Get("predefinedAcl")
 	contentEncoding := r.URL.Query().Get("contentEncoding")
+	customTime := r.URL.Query().Get("customTime")
 
 	// Load data from HTTP Headers
 	if contentEncoding == "" {
@@ -250,6 +255,7 @@ func (s *Server) signedUpload(bucketName string, r *http.Request) jsonResponse {
 			Name:            name,
 			ContentType:     r.Header.Get(contentTypeHeader),
 			ContentEncoding: contentEncoding,
+			CustomTime:      convertTimeWithoutError(customTime),
 			ACL:             getObjectACL(predefinedACL),
 			Metadata:        metaData,
 		},
@@ -337,6 +343,7 @@ func (s *Server) multipartUpload(bucketName string, r *http.Request) jsonRespons
 			Name:            objName,
 			ContentType:     contentType,
 			ContentEncoding: metadata.ContentEncoding,
+			CustomTime:      metadata.CustomTime,
 			ACL:             getObjectACL(predefinedACL),
 			Metadata:        metadata.Metadata,
 		},
@@ -378,6 +385,7 @@ func (s *Server) resumableUpload(bucketName string, r *http.Request) jsonRespons
 			Name:            objName,
 			ContentType:     metadata.ContentType,
 			ContentEncoding: contentEncoding,
+			CustomTime:      metadata.CustomTime,
 			ACL:             getObjectACL(predefinedACL),
 			Metadata:        metadata.Metadata,
 		},
diff --git a/internal/backend/object.go b/internal/backend/object.go
index e33894b..7dc742e 100644
--- a/internal/backend/object.go
+++ b/internal/backend/object.go
@@ -28,6 +28,7 @@ type ObjectAttrs struct {
 	Created         string
 	Deleted         string
 	Updated         string
+	CustomTime      string
 	Generation      int64
 }
 


Test patch:
diff --git a/fakestorage/object_test.go b/fakestorage/object_test.go
index 19f270d..b04a4a8 100644
--- a/fakestorage/object_test.go
+++ b/fakestorage/object_test.go
@@ -1615,6 +1615,47 @@ func checkObjectMetadata(actual, expected map[string]string, t *testing.T) {
 	}
 }
 
+func TestServerClientObjectPatchCustomTime(t *testing.T) {
+	const (
+		bucketName  = "some-bucket"
+		objectName  = "items/data.txt"
+		content     = "some nice content"
+		contentType = "text/plain; charset=utf-8"
+	)
+	startTime := time.Now().Truncate(time.Second)
+	objs := []Object{
+		{
+			ObjectAttrs: ObjectAttrs{
+				BucketName:  bucketName,
+				Name:        objectName,
+				ContentType: contentType,
+				CustomTime:  startTime.Add(-5 * time.Hour),
+			},
+			Content: []byte(content),
+		},
+	}
+
+	runServersTest(t, runServersOptions{objs: objs}, func(t *testing.T, server *Server) {
+		client := server.Client()
+		objHandle := client.Bucket(bucketName).Object(objectName)
+
+		ctx := context.TODO()
+		_, err := objHandle.Update(ctx, storage.ObjectAttrsToUpdate{CustomTime: startTime})
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		attrs, err := objHandle.Attrs(ctx)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !attrs.CustomTime.Equal(startTime) {
+			t.Errorf("unexpected custom time\nwant %q\ngot  %q", startTime.String(), attrs.CustomTime.String())
+		}
+	})
+}
+
 func TestParseRangeRequest(t *testing.T) {
 	ctx := context.TODO()
 
diff --git a/fakestorage/upload_test.go b/fakestorage/upload_test.go
index 4672e81..38e0636 100644
--- a/fakestorage/upload_test.go
+++ b/fakestorage/upload_test.go
@@ -18,6 +18,7 @@ import (
 	"reflect"
 	"strings"
 	"testing"
+	"time"
 
 	"cloud.google.com/go/storage"
 	"github.com/fsouza/fake-gcs-server/internal/checksum"
@@ -70,10 +71,12 @@ func TestServerClientObjectWriter(t *testing.T) {
 				server.CreateBucketWithOpts(CreateBucketOpts{Name: test.bucketName})
 				client := server.Client()
 
+				customTime := time.Now().Truncate(time.Second).Add(-time.Hour)
 				objHandle := client.Bucket(test.bucketName).Object(test.objectName)
 				w := objHandle.NewWriter(context.Background())
 				w.ChunkSize = test.chunkSize
 				w.ContentType = contentType
+				w.CustomTime = customTime
 				w.Metadata = map[string]string{
 					"foo": "bar",
 				}
@@ -115,6 +118,9 @@ func TestServerClientObjectWriter(t *testing.T) {
 				if !reflect.DeepEqual(obj.Metadata, w.Metadata) {
 					t.Errorf("wrong meta data\nwant %+v\ngot  %+v", w.Metadata, obj.Metadata)
 				}
+				if !customTime.Equal(obj.CustomTime) {
+					t.Errorf("wrong custom time\nwant %q\ngot  %q", customTime.String(), obj.CustomTime.String())
+				}
 
 				reader, err := client.Bucket(test.bucketName).Object(test.objectName).NewReader(context.Background())
 				if err != nil {