diff --git a/cli/command/formatter/container.go b/cli/command/formatter/container.go index 2badc9a398..7e10091a8d 100644 --- a/cli/command/formatter/container.go +++ b/cli/command/formatter/container.go @@ -254,6 +254,7 @@ func (c *ContainerContext) Labels() string { for k, v := range c.c.Labels { joinLabels = append(joinLabels, k+"="+v) } + sort.Strings(joinLabels) return strings.Join(joinLabels, ",") } diff --git a/cli/command/formatter/container_test.go b/cli/command/formatter/container_test.go index fe595484b0..584e29def8 100644 --- a/cli/command/formatter/container_test.go +++ b/cli/command/formatter/container_test.go @@ -497,28 +497,59 @@ func TestContainerContextWriteJSONField(t *testing.T) { } func TestContainerBackCompat(t *testing.T) { - containers := []container.Summary{{ID: "brewhaha"}} - cases := []string{ - "ID", - "Names", - "Image", - "Command", - "CreatedAt", - "RunningFor", - "Ports", - "Status", - "Size", - "Labels", - "Mounts", + createdAtTime := time.Now().AddDate(-1, 0, 0) // 1 year ago + + ctrContext := container.Summary{ + ID: "aabbccddeeff", + Names: []string{"/foobar_baz"}, + Image: "docker.io/library/ubuntu", // should this have canonical format or not? + ImageID: "sha256:a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5", // should this have algo-prefix or not? + ImageManifestDescriptor: nil, + Command: "/bin/sh", + Created: createdAtTime.UTC().Unix(), + Ports: []container.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}, + SizeRw: 123, + SizeRootFs: 12345, + Labels: map[string]string{"label1": "value1", "label2": "value2"}, + State: "running", + Status: "running", + HostConfig: struct { + NetworkMode string `json:",omitempty"` + Annotations map[string]string `json:",omitempty"` + }{ + NetworkMode: "bridge", + Annotations: map[string]string{ + "com.example.annotation": "hello", + }, + }, + NetworkSettings: nil, + Mounts: nil, } - buf := bytes.NewBuffer(nil) - for _, c := range cases { - ctx := Context{Format: Format(fmt.Sprintf("{{ .%s }}", c)), Output: buf} - if err := ContainerWrite(ctx, containers); err != nil { - t.Logf("could not render template for field '%s': %v", c, err) - t.Fail() - } - buf.Reset() + + tests := []struct { + field string + expected string + }{ + {field: "ID", expected: "aabbccddeeff"}, + {field: "Names", expected: "foobar_baz"}, + {field: "Image", expected: "docker.io/library/ubuntu"}, + {field: "Command", expected: `"/bin/sh"`}, + {field: "CreatedAt", expected: time.Unix(createdAtTime.Unix(), 0).String()}, + {field: "RunningFor", expected: "12 months ago"}, + {field: "Ports", expected: "8080/tcp"}, + {field: "Status", expected: "running"}, + {field: "Size", expected: "123B (virtual 12.3kB)"}, + {field: "Labels", expected: "label1=value1,label2=value2"}, + {field: "Mounts", expected: ""}, + } + + for _, tc := range tests { + t.Run(tc.field, func(t *testing.T) { + buf := new(bytes.Buffer) + ctx := Context{Format: Format(fmt.Sprintf("{{ .%s }}", tc.field)), Output: buf} + assert.NilError(t, ContainerWrite(ctx, []container.Summary{ctrContext})) + assert.Check(t, is.Equal(strings.TrimSpace(buf.String()), tc.expected)) + }) } }