Browse code

Use the HTTP Last-Modified http header as the mtime value for ADD cmd when present

Closes #8331

Signed-off-by: Doug Davis <dug@us.ibm.com>

Doug Davis authored on 2014/10/23 03:16:42
Showing 3 changed files
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"fmt"
10 10
 	"io"
11 11
 	"io/ioutil"
12
+	"net/http"
12 13
 	"net/url"
13 14
 	"os"
14 15
 	"path"
... ...
@@ -254,8 +255,21 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
254 254
 		fmt.Fprintf(b.OutStream, "\n")
255 255
 		tmpFile.Close()
256 256
 
257
-		// Remove the mtime of the newly created tmp file
258
-		if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil {
257
+		// Set the mtime to the Last-Modified header value if present
258
+		// Otherwise just remove atime and mtime
259
+		times := make([]syscall.Timespec, 2)
260
+
261
+		lastMod := resp.Header.Get("Last-Modified")
262
+		if lastMod != "" {
263
+			mTime, err := http.ParseTime(lastMod)
264
+			// If we can't parse it then just let it default to 'zero'
265
+			// otherwise use the parsed time value
266
+			if err == nil {
267
+				times[1] = syscall.NsecToTimespec(mTime.UnixNano())
268
+			}
269
+		}
270
+
271
+		if err := system.UtimesNano(tmpFileName, times); err != nil {
259 272
 			return err
260 273
 		}
261 274
 
... ...
@@ -376,7 +376,11 @@ destination container.
376 376
 All new files and directories are created with a UID and GID of 0.
377 377
 
378 378
 In the case where `<src>` is a remote file URL, the destination will
379
-have permissions of 600.
379
+have permissions of 600. If the remote file being retrieved has an HTTP
380
+`Last-Modified` header, the timestamp from that header will be used
381
+to set the `mtime` on the destination file. Then, like any other file
382
+processed during an `ADD`, `mtime` will be included in the determination
383
+of whether or not the file has changed and the cache should be updated.
380 384
 
381 385
 > **Note**:
382 386
 > If you build by passing a `Dockerfile` through STDIN (`docker
... ...
@@ -7,9 +7,11 @@ import (
7 7
 	"io/ioutil"
8 8
 	"os"
9 9
 	"os/exec"
10
+	"path"
10 11
 	"path/filepath"
11 12
 	"regexp"
12 13
 	"strings"
14
+	"syscall"
13 15
 	"testing"
14 16
 	"time"
15 17
 
... ...
@@ -2214,6 +2216,64 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) {
2214 2214
 	logDone("build - add remote file without cache")
2215 2215
 }
2216 2216
 
2217
+func TestBuildADDRemoteFileMTime(t *testing.T) {
2218
+	name := "testbuildaddremotefilemtime"
2219
+	defer deleteImages(name)
2220
+
2221
+	server, err := fakeStorage(map[string]string{"baz": "hello"})
2222
+	if err != nil {
2223
+		t.Fatal(err)
2224
+	}
2225
+	defer server.Close()
2226
+
2227
+	ctx, err := fakeContext(fmt.Sprintf(`FROM scratch
2228
+        MAINTAINER dockerio
2229
+        ADD %s/baz /usr/lib/baz/quux`, server.URL), nil)
2230
+	if err != nil {
2231
+		t.Fatal(err)
2232
+	}
2233
+	defer ctx.Close()
2234
+
2235
+	id1, err := buildImageFromContext(name, ctx, true)
2236
+	if err != nil {
2237
+		t.Fatal(err)
2238
+	}
2239
+
2240
+	id2, err := buildImageFromContext(name, ctx, true)
2241
+	if err != nil {
2242
+		t.Fatal(err)
2243
+	}
2244
+	if id1 != id2 {
2245
+		t.Fatal("The cache should have been used but wasn't - #1")
2246
+	}
2247
+
2248
+	// Now set baz's times to anything else and redo the build
2249
+	// This time the cache should not be used
2250
+	bazPath := path.Join(server.FakeContext.Dir, "baz")
2251
+	err = syscall.UtimesNano(bazPath, make([]syscall.Timespec, 2))
2252
+	if err != nil {
2253
+		t.Fatalf("Error setting mtime on %q: %v", bazPath, err)
2254
+	}
2255
+
2256
+	id3, err := buildImageFromContext(name, ctx, true)
2257
+	if err != nil {
2258
+		t.Fatal(err)
2259
+	}
2260
+	if id1 == id3 {
2261
+		t.Fatal("The cache should not have been used but was")
2262
+	}
2263
+
2264
+	// And for good measure do it again and make sure cache is used this time
2265
+	id4, err := buildImageFromContext(name, ctx, true)
2266
+	if err != nil {
2267
+		t.Fatal(err)
2268
+	}
2269
+	if id3 != id4 {
2270
+		t.Fatal("The cache should have been used but wasn't - #2")
2271
+	}
2272
+	logDone("build - add remote file testing mtime")
2273
+}
2274
+
2217 2275
 func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) {
2218 2276
 	name := "testbuildaddlocalandremotefilewithcache"
2219 2277
 	defer deleteImages(name)