Fix for #13175.
This change allows user-input timestamps (e.g. to `docker events
--since/--until` or `docker logs --since` to be parsed using
standard RFC3339Nano layout in Go instead of the layout that parses
all timestamps into fixed-length strings (currently buggy).
User inputs need not to be complying to the internal format
(`RFC3339NanoFixed`) anyway.
Added test case for `events --since/--until` with all possible
timestamp input formats.
Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
| ... | ... |
@@ -13,6 +13,41 @@ import ( |
| 13 | 13 |
"github.com/go-check/check" |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
+func (s *DockerSuite) TestEventsTimestampFormats(c *check.C) {
|
|
| 17 |
+ image := "busybox" |
|
| 18 |
+ |
|
| 19 |
+ // Start stopwatch, generate an event |
|
| 20 |
+ time.Sleep(time.Second) // so that we don't grab events from previous test occured in the same second |
|
| 21 |
+ start := daemonTime(c) |
|
| 22 |
+ time.Sleep(time.Second) // remote API precision is only a second, wait a while before creating an event |
|
| 23 |
+ dockerCmd(c, "tag", image, "timestamptest:1") |
|
| 24 |
+ dockerCmd(c, "rmi", "timestamptest:1") |
|
| 25 |
+ time.Sleep(time.Second) // so that until > since |
|
| 26 |
+ end := daemonTime(c) |
|
| 27 |
+ |
|
| 28 |
+ // List of available time formats to --since |
|
| 29 |
+ unixTs := func(t time.Time) string { return fmt.Sprintf("%v", t.Unix()) }
|
|
| 30 |
+ rfc3339 := func(t time.Time) string { return t.Format(time.RFC3339) }
|
|
| 31 |
+ |
|
| 32 |
+ // --since=$start must contain only the 'untag' event |
|
| 33 |
+ for _, f := range []func(time.Time) string{unixTs, rfc3339} {
|
|
| 34 |
+ since, until := f(start), f(end) |
|
| 35 |
+ cmd := exec.Command(dockerBinary, "events", "--since="+since, "--until="+until) |
|
| 36 |
+ out, _, err := runCommandWithOutput(cmd) |
|
| 37 |
+ if err != nil {
|
|
| 38 |
+ c.Fatalf("docker events cmd failed: %v\nout=%s", err, out)
|
|
| 39 |
+ } |
|
| 40 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 41 |
+ if len(events) != 1 {
|
|
| 42 |
+ c.Fatalf("unexpected events, was expecting only 1 (since=%s, until=%s) out=%s", since, until, out)
|
|
| 43 |
+ } |
|
| 44 |
+ if !strings.Contains(out, "untag") {
|
|
| 45 |
+ c.Fatalf("expected 'untag' event not found (since=%s, until=%s) out=%s", since, until, out)
|
|
| 46 |
+ } |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 16 | 51 |
func (s *DockerSuite) TestEventsUntag(c *check.C) {
|
| 17 | 52 |
image := "busybox" |
| 18 | 53 |
dockerCmd(c, "tag", image, "utest:tag1") |
| ... | ... |
@@ -2,14 +2,21 @@ package timeutils |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"strconv" |
| 5 |
+ "strings" |
|
| 5 | 6 |
"time" |
| 6 | 7 |
) |
| 7 | 8 |
|
| 8 | 9 |
// GetTimestamp tries to parse given string as RFC3339 time |
| 9 |
-// or Unix timestamp, if successful returns a Unix timestamp |
|
| 10 |
-// as string otherwise returns value back. |
|
| 10 |
+// or Unix timestamp (with seconds precision), if successful |
|
| 11 |
+//returns a Unix timestamp as string otherwise returns value back. |
|
| 11 | 12 |
func GetTimestamp(value string) string {
|
| 12 |
- format := RFC3339NanoFixed |
|
| 13 |
+ var format string |
|
| 14 |
+ if strings.Contains(value, ".") {
|
|
| 15 |
+ format = time.RFC3339Nano |
|
| 16 |
+ } else {
|
|
| 17 |
+ format = time.RFC3339 |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 13 | 20 |
loc := time.FixedZone(time.Now().Zone()) |
| 14 | 21 |
if len(value) < len(format) {
|
| 15 | 22 |
format = format[:len(value)] |
| 16 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+package timeutils |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestGetTimestamp(t *testing.T) {
|
|
| 7 |
+ cases := []struct{ in, expected string }{
|
|
| 8 |
+ {"0", "-62167305600"}, // 0 gets parsed year 0
|
|
| 9 |
+ |
|
| 10 |
+ // Partial RFC3339 strings get parsed with second precision |
|
| 11 |
+ {"2006-01-02T15:04:05.999999999+07:00", "1136189045"},
|
|
| 12 |
+ {"2006-01-02T15:04:05.999999999Z", "1136214245"},
|
|
| 13 |
+ {"2006-01-02T15:04:05.999999999", "1136214245"},
|
|
| 14 |
+ {"2006-01-02T15:04:05", "1136214245"},
|
|
| 15 |
+ {"2006-01-02T15:04", "1136214240"},
|
|
| 16 |
+ {"2006-01-02T15", "1136214000"},
|
|
| 17 |
+ {"2006-01-02T", "1136160000"},
|
|
| 18 |
+ {"2006-01-02", "1136160000"},
|
|
| 19 |
+ {"2006", "1136073600"},
|
|
| 20 |
+ {"2015-05-13T20:39:09Z", "1431549549"},
|
|
| 21 |
+ |
|
| 22 |
+ // unix timestamps returned as is |
|
| 23 |
+ {"1136073600", "1136073600"},
|
|
| 24 |
+ |
|
| 25 |
+ // String fallback |
|
| 26 |
+ {"invalid", "invalid"},
|
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ for _, c := range cases {
|
|
| 30 |
+ o := GetTimestamp(c.in) |
|
| 31 |
+ if o != c.expected {
|
|
| 32 |
+ t.Fatalf("wrong value for '%s'. expected:'%s' got:'%s'", c.in, c.expected, o)
|
|
| 33 |
+ } |
|
| 34 |
+ } |
|
| 35 |
+} |