Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit eb7732f

Browse files
authored
Merge pull request #1505 from aiordache/image_cmd
Add `compose images` cmd
2 parents d073c93 + 08055d7 commit eb7732f

File tree

10 files changed

+279
-0
lines changed

10 files changed

+279
-0
lines changed

aci/compose.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,7 @@ func (cs *aciComposeService) Events(ctx context.Context, project string, options
241241
func (cs *aciComposeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
242242
return "", 0, errdefs.ErrNotImplemented
243243
}
244+
245+
func (cs *aciComposeService) Images(ctx context.Context, projectName string, options compose.ImagesOptions) ([]compose.ImageSummary, error) {
246+
return nil, errdefs.ErrNotImplemented
247+
}

api/client/compose.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,7 @@ func (c *composeService) Events(ctx context.Context, project string, options com
115115
func (c *composeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
116116
return "", 0, errdefs.ErrNotImplemented
117117
}
118+
119+
func (c *composeService) Images(ctx context.Context, projectName string, options compose.ImagesOptions) ([]compose.ImageSummary, error) {
120+
return nil, errdefs.ErrNotImplemented
121+
}

api/compose/api.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ type Service interface {
7272
Events(ctx context.Context, project string, options EventsOptions) error
7373
// Port executes the equivalent to a `compose port`
7474
Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error)
75+
// Images executes the equivalent of a `compose images`
76+
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
7577
}
7678

7779
// BuildOptions group options of the Build API
@@ -164,6 +166,11 @@ type PullOptions struct {
164166
IgnoreFailures bool
165167
}
166168

169+
// ImagesOptions group options of the Images API
170+
type ImagesOptions struct {
171+
Services []string
172+
}
173+
167174
// KillOptions group options of the Kill API
168175
type KillOptions struct {
169176
// Signal to send to containers
@@ -285,6 +292,15 @@ type ContainerProcSummary struct {
285292
Titles []string
286293
}
287294

295+
// ImageSummary holds container image description
296+
type ImageSummary struct {
297+
ID string
298+
ContainerName string
299+
Repository string
300+
Tag string
301+
Size int64
302+
}
303+
288304
// ServiceStatus hold status about a service
289305
type ServiceStatus struct {
290306
ID string

cli/cmd/compose/compose.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ func Command(contextType string) *cobra.Command {
157157
topCommand(&opts),
158158
eventsCommand(&opts),
159159
portCommand(&opts),
160+
imagesCommand(&opts),
160161
)
161162

162163
if contextType == store.LocalContextType || contextType == store.DefaultContextType {

cli/cmd/compose/images.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package compose
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"io"
23+
"os"
24+
"sort"
25+
"strings"
26+
27+
"github.com/spf13/cobra"
28+
29+
"github.com/docker/compose-cli/api/client"
30+
"github.com/docker/compose-cli/api/compose"
31+
"github.com/docker/compose-cli/cli/formatter"
32+
"github.com/docker/compose-cli/utils"
33+
"github.com/docker/docker/pkg/stringid"
34+
35+
units "github.com/docker/go-units"
36+
)
37+
38+
type imageOptions struct {
39+
*projectOptions
40+
Quiet bool
41+
}
42+
43+
func imagesCommand(p *projectOptions) *cobra.Command {
44+
opts := imageOptions{
45+
projectOptions: p,
46+
}
47+
imgCmd := &cobra.Command{
48+
Use: "images [SERVICE...]",
49+
Short: "List images used by the created containers",
50+
RunE: func(cmd *cobra.Command, args []string) error {
51+
return runImages(cmd.Context(), opts, args)
52+
},
53+
}
54+
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
55+
return imgCmd
56+
}
57+
58+
func runImages(ctx context.Context, opts imageOptions, services []string) error {
59+
c, err := client.New(ctx)
60+
if err != nil {
61+
return err
62+
}
63+
64+
projectName, err := opts.toProjectName()
65+
if err != nil {
66+
return err
67+
}
68+
69+
images, err := c.ComposeService().Images(ctx, projectName, compose.ImagesOptions{
70+
Services: services,
71+
})
72+
if err != nil {
73+
return err
74+
}
75+
76+
if opts.Quiet {
77+
ids := []string{}
78+
for _, img := range images {
79+
id := img.ID
80+
if i := strings.IndexRune(img.ID, ':'); i >= 0 {
81+
id = id[i+1:]
82+
}
83+
if !utils.StringContains(ids, id) {
84+
ids = append(ids, id)
85+
}
86+
}
87+
for _, img := range ids {
88+
fmt.Println(img)
89+
}
90+
return nil
91+
}
92+
93+
sort.Slice(images, func(i, j int) bool {
94+
return images[i].ContainerName < images[j].ContainerName
95+
})
96+
97+
return formatter.Print(images, formatter.PRETTY, os.Stdout,
98+
func(w io.Writer) {
99+
for _, img := range images {
100+
id := stringid.TruncateID(img.ID)
101+
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
102+
103+
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, img.Repository, img.Tag, id, size)
104+
}
105+
},
106+
"Container", "Repository", "Tag", "Image Id", "Size")
107+
}

ecs/images.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package ecs
18+
19+
import (
20+
"context"
21+
22+
"github.com/docker/compose-cli/api/compose"
23+
"github.com/docker/compose-cli/api/errdefs"
24+
)
25+
26+
func (b *ecsAPIService) Images(ctx context.Context, projectName string, options compose.ImagesOptions) ([]compose.ImageSummary, error) {
27+
return nil, errdefs.ErrNotImplemented
28+
}

ecs/local/compose.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,7 @@ func (e ecsLocalSimulation) Events(ctx context.Context, project string, options
207207
func (e ecsLocalSimulation) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
208208
return "", 0, errdefs.ErrNotImplemented
209209
}
210+
211+
func (e ecsLocalSimulation) Images(ctx context.Context, projectName string, options compose.ImagesOptions) ([]compose.ImageSummary, error) {
212+
return nil, errdefs.ErrNotImplemented
213+
}

kube/compose.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,7 @@ func (s *composeService) Events(ctx context.Context, project string, options com
271271
func (s *composeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
272272
return "", 0, errdefs.ErrNotImplemented
273273
}
274+
275+
func (s *composeService) Images(ctx context.Context, projectName string, options compose.ImagesOptions) ([]compose.ImageSummary, error) {
276+
return nil, errdefs.ErrNotImplemented
277+
}

local/compose/images.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package compose
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
24+
moby "github.com/docker/docker/api/types"
25+
"github.com/docker/docker/api/types/filters"
26+
"golang.org/x/sync/errgroup"
27+
28+
"github.com/docker/compose-cli/api/compose"
29+
"github.com/docker/compose-cli/utils"
30+
)
31+
32+
func (s *composeService) Images(ctx context.Context, projectName string, options compose.ImagesOptions) ([]compose.ImageSummary, error) {
33+
allContainers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
34+
Filters: filters.NewArgs(projectFilter(projectName)),
35+
})
36+
if err != nil {
37+
return nil, err
38+
}
39+
containers := []moby.Container{}
40+
if len(options.Services) > 0 {
41+
// filter service containers
42+
for _, c := range allContainers {
43+
if utils.StringContains(options.Services, c.Labels[compose.ServiceTag]) {
44+
containers = append(containers, c)
45+
46+
}
47+
}
48+
} else {
49+
containers = allContainers
50+
}
51+
52+
imageIDs := []string{}
53+
// aggregate image IDs
54+
for _, c := range containers {
55+
if !utils.StringContains(imageIDs, c.ImageID) {
56+
imageIDs = append(imageIDs, c.ImageID)
57+
}
58+
}
59+
60+
images := map[string]moby.ImageInspect{}
61+
eg, ctx := errgroup.WithContext(ctx)
62+
for _, img := range imageIDs {
63+
img := img
64+
eg.Go(func() error {
65+
inspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, img)
66+
if err != nil {
67+
return err
68+
}
69+
images[img] = inspect
70+
return nil
71+
})
72+
}
73+
err = eg.Wait()
74+
75+
if err != nil {
76+
return nil, err
77+
}
78+
summary := make([]compose.ImageSummary, len(containers))
79+
for i, container := range containers {
80+
img, ok := images[container.ImageID]
81+
if !ok {
82+
return nil, fmt.Errorf("failed to retrieve image for container %s", getCanonicalContainerName(container))
83+
}
84+
if len(img.RepoTags) == 0 {
85+
return nil, fmt.Errorf("no image tag found for %s", img.ID)
86+
}
87+
tag := ""
88+
repository := ""
89+
repotag := strings.Split(img.RepoTags[0], ":")
90+
repository = repotag[0]
91+
if len(repotag) > 1 {
92+
tag = repotag[1]
93+
}
94+
95+
summary[i] = compose.ImageSummary{
96+
ID: img.ID,
97+
ContainerName: getCanonicalContainerName(container),
98+
Repository: repository,
99+
Tag: tag,
100+
Size: img.Size,
101+
}
102+
}
103+
return summary, nil
104+
}

local/e2e/compose/compose_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ func TestLocalComposeUp(t *testing.T) {
109109
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_db_1 db running 5432/tcp`})
110110
})
111111

112+
t.Run("images", func(t *testing.T) {
113+
res := c.RunDockerCmd("compose", "-p", projectName, "images")
114+
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_db_1 gtardif/sentences-db latest`})
115+
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_web_1 gtardif/sentences-web latest`})
116+
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo_words_1 gtardif/sentences-api latest`})
117+
})
118+
112119
t.Run("down", func(t *testing.T) {
113120
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
114121
})

0 commit comments

Comments
 (0)