`GetTimestamp()` "assumed" values it could not parse
to be a valid unix timestamp, and would use invalid
values ("hello world") as-is (even testing that
it did so).
This patch validates unix timestamp to be a valid
numeric value, and makes other values invalid.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -82,11 +82,14 @@ func GetTimestamp(value string, reference time.Time) (string, error) {
|
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 | 84 |
if err != nil {
|
| 85 |
- // if there is a `-` then it's an RFC3339 like timestamp otherwise assume unixtimestamp |
|
| 85 |
+ // if there is a `-` then it's an RFC3339 like timestamp |
|
| 86 | 86 |
if strings.Contains(value, "-") {
|
| 87 | 87 |
return "", err // was probably an RFC3339 like timestamp but the parser failed with an error |
| 88 | 88 |
} |
| 89 |
- return value, nil // unixtimestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) |
|
| 89 |
+ if _, _, err := parseTimestamp(value); err != nil {
|
|
| 90 |
+ return "", fmt.Errorf("failed to parse value as time or duration: %q", value)
|
|
| 91 |
+ } |
|
| 92 |
+ return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) |
|
| 90 | 93 |
} |
| 91 | 94 |
|
| 92 | 95 |
return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil
|
| ... | ... |
@@ -104,6 +107,10 @@ func ParseTimestamps(value string, def int64) (int64, int64, error) {
|
| 104 | 104 |
if value == "" {
|
| 105 | 105 |
return def, 0, nil |
| 106 | 106 |
} |
| 107 |
+ return parseTimestamp(value) |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func parseTimestamp(value string) (int64, int64, error) {
|
|
| 107 | 111 |
sa := strings.SplitN(value, ".", 2) |
| 108 | 112 |
s, err := strconv.ParseInt(sa[0], 10, 64) |
| 109 | 113 |
if err != nil {
|
| ... | ... |
@@ -49,8 +49,8 @@ func TestGetTimestamp(t *testing.T) {
|
| 49 | 49 |
{"1.5h", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false},
|
| 50 | 50 |
{"1h30m", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false},
|
| 51 | 51 |
|
| 52 |
- // String fallback |
|
| 53 |
- {"invalid", "invalid", false},
|
|
| 52 |
+ {"invalid", "", true},
|
|
| 53 |
+ {"", "", true},
|
|
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 | 56 |
for _, c := range cases {
|
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/docker/docker/api/types" |
| 10 | 10 |
timetypes "github.com/docker/docker/api/types/time" |
| 11 |
+ "github.com/pkg/errors" |
|
| 11 | 12 |
) |
| 12 | 13 |
|
| 13 | 14 |
// ContainerLogs returns the logs generated by a container in an io.ReadCloser. |
| ... | ... |
@@ -45,7 +46,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options |
| 45 | 45 |
if options.Since != "" {
|
| 46 | 46 |
ts, err := timetypes.GetTimestamp(options.Since, time.Now()) |
| 47 | 47 |
if err != nil {
|
| 48 |
- return nil, err |
|
| 48 |
+ return nil, errors.Wrap(err, `invalid value for "since"`) |
|
| 49 | 49 |
} |
| 50 | 50 |
query.Set("since", ts)
|
| 51 | 51 |
} |
| ... | ... |
@@ -53,7 +54,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options |
| 53 | 53 |
if options.Until != "" {
|
| 54 | 54 |
ts, err := timetypes.GetTimestamp(options.Until, time.Now()) |
| 55 | 55 |
if err != nil {
|
| 56 |
- return nil, err |
|
| 56 |
+ return nil, errors.Wrap(err, `invalid value for "until"`) |
|
| 57 | 57 |
} |
| 58 | 58 |
query.Set("until", ts)
|
| 59 | 59 |
} |
| ... | ... |
@@ -2,6 +2,7 @@ package client // import "github.com/docker/docker/client" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
+ "context" |
|
| 5 | 6 |
"fmt" |
| 6 | 7 |
"io" |
| 7 | 8 |
"io/ioutil" |
| ... | ... |
@@ -12,10 +13,9 @@ import ( |
| 12 | 12 |
"testing" |
| 13 | 13 |
"time" |
| 14 | 14 |
|
| 15 |
- "context" |
|
| 16 |
- |
|
| 17 | 15 |
"github.com/docker/docker/api/types" |
| 18 |
- "github.com/docker/docker/internal/testutil" |
|
| 16 |
+ "github.com/gotestyourself/gotestyourself/assert" |
|
| 17 |
+ is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 19 | 18 |
) |
| 20 | 19 |
|
| 21 | 20 |
func TestContainerLogsNotFoundError(t *testing.T) {
|
| ... | ... |
@@ -33,17 +33,15 @@ func TestContainerLogsError(t *testing.T) {
|
| 33 | 33 |
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
| 34 | 34 |
} |
| 35 | 35 |
_, err := client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{})
|
| 36 |
- if err == nil || err.Error() != "Error response from daemon: Server error" {
|
|
| 37 |
- t.Fatalf("expected a Server Error, got %v", err)
|
|
| 38 |
- } |
|
| 36 |
+ assert.Check(t, is.Error(err, "Error response from daemon: Server error")) |
|
| 39 | 37 |
_, err = client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{
|
| 40 | 38 |
Since: "2006-01-02TZ", |
| 41 | 39 |
}) |
| 42 |
- testutil.ErrorContains(t, err, `parsing time "2006-01-02TZ"`) |
|
| 40 |
+ assert.Check(t, is.ErrorContains(err, `parsing time "2006-01-02TZ"`)) |
|
| 43 | 41 |
_, err = client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{
|
| 44 | 42 |
Until: "2006-01-02TZ", |
| 45 | 43 |
}) |
| 46 |
- testutil.ErrorContains(t, err, `parsing time "2006-01-02TZ"`) |
|
| 44 |
+ assert.Check(t, is.ErrorContains(err, `parsing time "2006-01-02TZ"`)) |
|
| 47 | 45 |
} |
| 48 | 46 |
|
| 49 | 47 |
func TestContainerLogs(t *testing.T) {
|
| ... | ... |
@@ -51,6 +49,7 @@ func TestContainerLogs(t *testing.T) {
|
| 51 | 51 |
cases := []struct {
|
| 52 | 52 |
options types.ContainerLogsOptions |
| 53 | 53 |
expectedQueryParams map[string]string |
| 54 |
+ expectedError string |
|
| 54 | 55 |
}{
|
| 55 | 56 |
{
|
| 56 | 57 |
expectedQueryParams: map[string]string{
|
| ... | ... |
@@ -84,32 +83,44 @@ func TestContainerLogs(t *testing.T) {
|
| 84 | 84 |
}, |
| 85 | 85 |
{
|
| 86 | 86 |
options: types.ContainerLogsOptions{
|
| 87 |
- // An complete invalid date, timestamp or go duration will be |
|
| 88 |
- // passed as is |
|
| 89 |
- Since: "invalid but valid", |
|
| 87 |
+ // timestamp will be passed as is |
|
| 88 |
+ Since: "1136073600.000000001", |
|
| 90 | 89 |
}, |
| 91 | 90 |
expectedQueryParams: map[string]string{
|
| 92 | 91 |
"tail": "", |
| 93 |
- "since": "invalid but valid", |
|
| 92 |
+ "since": "1136073600.000000001", |
|
| 94 | 93 |
}, |
| 95 | 94 |
}, |
| 96 | 95 |
{
|
| 97 | 96 |
options: types.ContainerLogsOptions{
|
| 98 |
- // An complete invalid date, timestamp or go duration will be |
|
| 99 |
- // passed as is |
|
| 100 |
- Until: "invalid but valid", |
|
| 97 |
+ // timestamp will be passed as is |
|
| 98 |
+ Until: "1136073600.000000001", |
|
| 101 | 99 |
}, |
| 102 | 100 |
expectedQueryParams: map[string]string{
|
| 103 | 101 |
"tail": "", |
| 104 |
- "until": "invalid but valid", |
|
| 102 |
+ "until": "1136073600.000000001", |
|
| 103 |
+ }, |
|
| 104 |
+ }, |
|
| 105 |
+ {
|
|
| 106 |
+ options: types.ContainerLogsOptions{
|
|
| 107 |
+ // An complete invalid date will not be passed |
|
| 108 |
+ Since: "invalid value", |
|
| 105 | 109 |
}, |
| 110 |
+ expectedError: `invalid value for "since": failed to parse value as time or duration: "invalid value"`, |
|
| 111 |
+ }, |
|
| 112 |
+ {
|
|
| 113 |
+ options: types.ContainerLogsOptions{
|
|
| 114 |
+ // An complete invalid date will not be passed |
|
| 115 |
+ Until: "invalid value", |
|
| 116 |
+ }, |
|
| 117 |
+ expectedError: `invalid value for "until": failed to parse value as time or duration: "invalid value"`, |
|
| 106 | 118 |
}, |
| 107 | 119 |
} |
| 108 | 120 |
for _, logCase := range cases {
|
| 109 | 121 |
client := &Client{
|
| 110 | 122 |
client: newMockClient(func(r *http.Request) (*http.Response, error) {
|
| 111 | 123 |
if !strings.HasPrefix(r.URL.Path, expectedURL) {
|
| 112 |
- return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
|
|
| 124 |
+ return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, r.URL)
|
|
| 113 | 125 |
} |
| 114 | 126 |
// Check query parameters |
| 115 | 127 |
query := r.URL.Query() |
| ... | ... |
@@ -126,17 +137,15 @@ func TestContainerLogs(t *testing.T) {
|
| 126 | 126 |
}), |
| 127 | 127 |
} |
| 128 | 128 |
body, err := client.ContainerLogs(context.Background(), "container_id", logCase.options) |
| 129 |
- if err != nil {
|
|
| 130 |
- t.Fatal(err) |
|
| 129 |
+ if logCase.expectedError != "" {
|
|
| 130 |
+ assert.Check(t, is.Error(err, logCase.expectedError)) |
|
| 131 |
+ continue |
|
| 131 | 132 |
} |
| 133 |
+ assert.NilError(t, err) |
|
| 132 | 134 |
defer body.Close() |
| 133 | 135 |
content, err := ioutil.ReadAll(body) |
| 134 |
- if err != nil {
|
|
| 135 |
- t.Fatal(err) |
|
| 136 |
- } |
|
| 137 |
- if string(content) != "response" {
|
|
| 138 |
- t.Fatalf("expected response to contain 'response', got %s", string(content))
|
|
| 139 |
- } |
|
| 136 |
+ assert.NilError(t, err) |
|
| 137 |
+ assert.Check(t, is.Contains(string(content), "response")) |
|
| 140 | 138 |
} |
| 141 | 139 |
} |
| 142 | 140 |
|
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/docker/docker/api/types" |
| 10 | 10 |
timetypes "github.com/docker/docker/api/types/time" |
| 11 |
+ "github.com/pkg/errors" |
|
| 11 | 12 |
) |
| 12 | 13 |
|
| 13 | 14 |
// ServiceLogs returns the logs generated by a service in an io.ReadCloser. |
| ... | ... |
@@ -25,7 +26,7 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options ty |
| 25 | 25 |
if options.Since != "" {
|
| 26 | 26 |
ts, err := timetypes.GetTimestamp(options.Since, time.Now()) |
| 27 | 27 |
if err != nil {
|
| 28 |
- return nil, err |
|
| 28 |
+ return nil, errors.Wrap(err, `invalid value for "since"`) |
|
| 29 | 29 |
} |
| 30 | 30 |
query.Set("since", ts)
|
| 31 | 31 |
} |
| ... | ... |
@@ -2,6 +2,7 @@ package client // import "github.com/docker/docker/client" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 |
+ "context" |
|
| 5 | 6 |
"fmt" |
| 6 | 7 |
"io" |
| 7 | 8 |
"io/ioutil" |
| ... | ... |
@@ -12,9 +13,9 @@ import ( |
| 12 | 12 |
"testing" |
| 13 | 13 |
"time" |
| 14 | 14 |
|
| 15 |
- "context" |
|
| 16 |
- |
|
| 17 | 15 |
"github.com/docker/docker/api/types" |
| 16 |
+ "github.com/gotestyourself/gotestyourself/assert" |
|
| 17 |
+ is "github.com/gotestyourself/gotestyourself/assert/cmp" |
|
| 18 | 18 |
) |
| 19 | 19 |
|
| 20 | 20 |
func TestServiceLogsError(t *testing.T) {
|
| ... | ... |
@@ -22,15 +23,11 @@ func TestServiceLogsError(t *testing.T) {
|
| 22 | 22 |
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
| 23 | 23 |
} |
| 24 | 24 |
_, err := client.ServiceLogs(context.Background(), "service_id", types.ContainerLogsOptions{})
|
| 25 |
- if err == nil || err.Error() != "Error response from daemon: Server error" {
|
|
| 26 |
- t.Fatalf("expected a Server Error, got %v", err)
|
|
| 27 |
- } |
|
| 25 |
+ assert.Check(t, is.Error(err, "Error response from daemon: Server error")) |
|
| 28 | 26 |
_, err = client.ServiceLogs(context.Background(), "service_id", types.ContainerLogsOptions{
|
| 29 | 27 |
Since: "2006-01-02TZ", |
| 30 | 28 |
}) |
| 31 |
- if err == nil || !strings.Contains(err.Error(), `parsing time "2006-01-02TZ"`) {
|
|
| 32 |
- t.Fatalf("expected a 'parsing time' error, got %v", err)
|
|
| 33 |
- } |
|
| 29 |
+ assert.Check(t, is.ErrorContains(err, `parsing time "2006-01-02TZ"`)) |
|
| 34 | 30 |
} |
| 35 | 31 |
|
| 36 | 32 |
func TestServiceLogs(t *testing.T) {
|
| ... | ... |
@@ -38,6 +35,7 @@ func TestServiceLogs(t *testing.T) {
|
| 38 | 38 |
cases := []struct {
|
| 39 | 39 |
options types.ContainerLogsOptions |
| 40 | 40 |
expectedQueryParams map[string]string |
| 41 |
+ expectedError string |
|
| 41 | 42 |
}{
|
| 42 | 43 |
{
|
| 43 | 44 |
expectedQueryParams: map[string]string{
|
| ... | ... |
@@ -71,21 +69,27 @@ func TestServiceLogs(t *testing.T) {
|
| 71 | 71 |
}, |
| 72 | 72 |
{
|
| 73 | 73 |
options: types.ContainerLogsOptions{
|
| 74 |
- // An complete invalid date, timestamp or go duration will be |
|
| 75 |
- // passed as is |
|
| 76 |
- Since: "invalid but valid", |
|
| 74 |
+ // timestamp will be passed as is |
|
| 75 |
+ Since: "1136073600.000000001", |
|
| 77 | 76 |
}, |
| 78 | 77 |
expectedQueryParams: map[string]string{
|
| 79 | 78 |
"tail": "", |
| 80 |
- "since": "invalid but valid", |
|
| 79 |
+ "since": "1136073600.000000001", |
|
| 81 | 80 |
}, |
| 82 | 81 |
}, |
| 82 |
+ {
|
|
| 83 |
+ options: types.ContainerLogsOptions{
|
|
| 84 |
+ // An complete invalid date will not be passed |
|
| 85 |
+ Since: "invalid value", |
|
| 86 |
+ }, |
|
| 87 |
+ expectedError: `invalid value for "since": failed to parse value as time or duration: "invalid value"`, |
|
| 88 |
+ }, |
|
| 83 | 89 |
} |
| 84 | 90 |
for _, logCase := range cases {
|
| 85 | 91 |
client := &Client{
|
| 86 | 92 |
client: newMockClient(func(r *http.Request) (*http.Response, error) {
|
| 87 | 93 |
if !strings.HasPrefix(r.URL.Path, expectedURL) {
|
| 88 |
- return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
|
|
| 94 |
+ return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, r.URL)
|
|
| 89 | 95 |
} |
| 90 | 96 |
// Check query parameters |
| 91 | 97 |
query := r.URL.Query() |
| ... | ... |
@@ -102,17 +106,15 @@ func TestServiceLogs(t *testing.T) {
|
| 102 | 102 |
}), |
| 103 | 103 |
} |
| 104 | 104 |
body, err := client.ServiceLogs(context.Background(), "service_id", logCase.options) |
| 105 |
- if err != nil {
|
|
| 106 |
- t.Fatal(err) |
|
| 105 |
+ if logCase.expectedError != "" {
|
|
| 106 |
+ assert.Check(t, is.Error(err, logCase.expectedError)) |
|
| 107 |
+ continue |
|
| 107 | 108 |
} |
| 109 |
+ assert.NilError(t, err) |
|
| 108 | 110 |
defer body.Close() |
| 109 | 111 |
content, err := ioutil.ReadAll(body) |
| 110 |
- if err != nil {
|
|
| 111 |
- t.Fatal(err) |
|
| 112 |
- } |
|
| 113 |
- if string(content) != "response" {
|
|
| 114 |
- t.Fatalf("expected response to contain 'response', got %s", string(content))
|
|
| 115 |
- } |
|
| 112 |
+ assert.NilError(t, err) |
|
| 113 |
+ assert.Check(t, is.Contains(string(content), "response")) |
|
| 116 | 114 |
} |
| 117 | 115 |
} |
| 118 | 116 |
|