Closes #8331
Signed-off-by: Doug Davis <dug@us.ibm.com>
| ... | ... |
@@ -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) |