diff --git a/runner/internal/shim/docker.go b/runner/internal/shim/docker.go index 49a11c7e7..604cc8354 100644 --- a/runner/internal/shim/docker.go +++ b/runner/internal/shim/docker.go @@ -56,17 +56,26 @@ const ( // https://github.com/moby/moby/blob/e77ff99ede5ee5952b3a9227863552ae6e5b6fb1/pkg/jsonmessage/jsonmessage.go#L144 // All fields are optional. type PullMessage struct { - Id string `json:"id"` // layer id - Status string `json:"status"` - ProgressDetail struct { - Current uint64 `json:"current"` // bytes - Total uint64 `json:"total"` // bytes - } `json:"progressDetail"` - ErrorDetail struct { + Id string `json:"id"` // layer id + Status string `json:"status"` + ProgressDetail ProgressDetail `json:"progressDetail"` + ErrorDetail struct { Message string `json:"message"` } `json:"errorDetail"` } +type ProgressDetail struct { + Current uint64 `json:"current"` + Total uint64 `json:"total"` + Units string `json:"units"` +} + +func (p *ProgressDetail) isUnitBytes() bool { + // > Units is the unit to print for progress. It defaults to "bytes" if empty + // https://github.com/moby/moby/blob/8151a55a776f5f83f68bcf0030c19031439ea357/api/types/jsonstream/progress.go#L9 + return p.Units == "bytes" || p.Units == "" +} + type layerProgress struct { Status string DownloadedBytes uint64 @@ -94,14 +103,18 @@ func (t *PullTracker) Update(msg PullMessage) { case "Pulling fs layer", "Waiting", "Verifying Checksum", "Already exists": // no bytes to update, just track status case "Downloading": - layer.DownloadedBytes = msg.ProgressDetail.Current - layer.TotalBytes = msg.ProgressDetail.Total + if msg.ProgressDetail.isUnitBytes() { + layer.DownloadedBytes = msg.ProgressDetail.Current + layer.TotalBytes = msg.ProgressDetail.Total + } case "Download complete": layer.DownloadedBytes = layer.TotalBytes case "Extracting": - layer.ExtractedBytes = msg.ProgressDetail.Current - layer.DownloadedBytes = msg.ProgressDetail.Total - layer.TotalBytes = msg.ProgressDetail.Total + if msg.ProgressDetail.isUnitBytes() { + layer.ExtractedBytes = msg.ProgressDetail.Current + layer.DownloadedBytes = msg.ProgressDetail.Total + layer.TotalBytes = msg.ProgressDetail.Total + } case "Pull complete": layer.ExtractedBytes = layer.TotalBytes layer.DownloadedBytes = layer.TotalBytes diff --git a/runner/internal/shim/docker_test.go b/runner/internal/shim/docker_test.go index 259c76dcd..e8dbdc350 100644 --- a/runner/internal/shim/docker_test.go +++ b/runner/internal/shim/docker_test.go @@ -268,3 +268,31 @@ func TestPullTracker_MixedLayerStatuses(t *testing.T) { assert.Equal(t, uint64(100+200), p.TotalBytes) assert.False(t, p.IsTotalBytesFinal) // layer-waiting size unknown } + +func TestPullTracker_NonBytesExtractingUnit(t *testing.T) { + tracker := newPullTracker() + tracker.Update(PullMessage{Id: "3.11", Status: "Pulling from library/python"}) + tracker.Update(PullMessage{Id: "aaa", Status: "Pulling fs layer"}) + tracker.Update(pullMsg("aaa", "Downloading", 100, 200)) + tracker.Update(pullMsg("aaa", "Downloading", 200, 200)) + tracker.Update(PullMessage{Id: "aaa", Status: "Download complete"}) + // Newer Docker daemons report extraction progress in seconds. Tested with 29.5.2 + tracker.Update(PullMessage{Id: "aaa", Status: "Extracting", ProgressDetail: ProgressDetail{Current: 1, Units: "s"}}) + tracker.Update(PullMessage{Id: "aaa", Status: "Extracting", ProgressDetail: ProgressDetail{Current: 2, Units: "s"}}) + + p := tracker.Progress() + require.NotNil(t, p) + assert.Equal(t, uint64(200), p.DownloadedBytes) + assert.Equal(t, uint64(0), p.ExtractedBytes) // reported in seconds, bytes unknown + assert.Equal(t, uint64(200), p.TotalBytes) + assert.True(t, p.IsTotalBytesFinal) + + tracker.Update(PullMessage{Id: "aaa", Status: "Pull complete"}) + + p = tracker.Progress() + require.NotNil(t, p) + assert.Equal(t, uint64(200), p.DownloadedBytes) + assert.Equal(t, uint64(200), p.ExtractedBytes) // pull complete => extracted == total + assert.Equal(t, uint64(200), p.TotalBytes) + assert.True(t, p.IsTotalBytesFinal) +}