Skip to content

Commit b5df6ef

Browse files
authored
feat: Update container inspect with size option (runfinch#157)
* chore: add sizeRW and SizeRootFs Signed-off-by: Arjun Raja Yogidas <[email protected]> * chore: add tests Signed-off-by: Arjun Raja Yogidas <[email protected]> * chore: update implementation Signed-off-by: Arjun Raja Yogidas <[email protected]> --------- Signed-off-by: Arjun Raja Yogidas <[email protected]>
1 parent db169fb commit b5df6ef

File tree

14 files changed

+234
-39
lines changed

14 files changed

+234
-39
lines changed

api/handlers/container/container.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type Service interface {
2626
Stop(ctx context.Context, cid string, timeout *time.Duration) error
2727
Restart(ctx context.Context, cid string, timeout time.Duration) error
2828
Create(ctx context.Context, image string, cmd []string, createOpt ncTypes.ContainerCreateOptions, netOpt ncTypes.NetworkOptions) (string, error)
29-
Inspect(ctx context.Context, cid string) (*types.Container, error)
29+
Inspect(ctx context.Context, cid string, size bool) (*types.Container, error)
3030
WriteFilesAsTarArchive(filePath string, writer io.Writer, slashDot bool) error
3131
Attach(ctx context.Context, cid string, opts *types.AttachOptions) error
3232
List(ctx context.Context, listOpts ncTypes.ContainerListOptions) ([]types.ContainerListItem, error)

api/handlers/container/container_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ var _ = Describe("Container API", func() {
104104
})
105105
It("should call container inspect method", func() {
106106
// setup mocks
107-
service.EXPECT().Inspect(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error from inspect api"))
107+
service.EXPECT().Inspect(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error from inspect api"))
108108
req, _ = http.NewRequest(http.MethodGet, "/containers/123/json", nil)
109109
// call the API to check if it returns the error generated from inspect method
110110
router.ServeHTTP(rr, req)

api/handlers/container/inspect.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package container
55

66
import (
77
"net/http"
8+
"strconv"
89

910
"github.com/containerd/containerd/v2/pkg/namespaces"
1011
"github.com/gorilla/mux"
@@ -16,8 +17,12 @@ import (
1617

1718
func (h *handler) inspect(w http.ResponseWriter, r *http.Request) {
1819
cid := mux.Vars(r)["id"]
20+
sizeflag, err := strconv.ParseBool(r.URL.Query().Get("size"))
21+
if err != nil {
22+
sizeflag = false
23+
}
1924
ctx := namespaces.WithNamespace(r.Context(), h.Config.Namespace)
20-
c, err := h.service.Inspect(ctx, cid)
25+
c, err := h.service.Inspect(ctx, cid, sizeflag)
2126
// map the error into http status code and send response.
2227
if err != nil {
2328
var code int

api/handlers/container/inspect_test.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ var _ = Describe("Container Inspect API", func() {
3232
req *http.Request
3333
resp types.Container
3434
respJSON []byte
35+
req2 *http.Request
36+
err error
3537
)
3638
BeforeEach(func() {
3739
mockCtrl = gomock.NewController(GinkgoT())
@@ -42,29 +44,60 @@ var _ = Describe("Container Inspect API", func() {
4244
h = newHandler(service, &c, logger)
4345
rr = httptest.NewRecorder()
4446
cid = "123"
45-
var err error
46-
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/containers/%s/json", cid), nil)
47-
Expect(err).Should(BeNil())
48-
req = mux.SetURLVars(req, map[string]string{"id": "123"})
47+
48+
// Create a helper function to create and set up requests
49+
createRequest := func(sizeParam bool) *http.Request {
50+
url := fmt.Sprintf("/containers/%s/json", cid)
51+
if sizeParam {
52+
url += "?size=true"
53+
}
54+
req, err := http.NewRequest(http.MethodGet, url, nil)
55+
Expect(err).Should(BeNil())
56+
return mux.SetURLVars(req, map[string]string{"id": cid})
57+
}
58+
59+
// Create both requests using the helper function
60+
req = createRequest(false)
61+
req2 = createRequest(true)
62+
63+
sizeRw := int64(1000)
64+
sizeRootFs := int64(5000)
4965
resp = types.Container{
50-
ID: cid,
51-
Image: "test-image",
52-
Name: "/test-container",
66+
ID: cid,
67+
Image: "test-image",
68+
Name: "/test-container",
69+
SizeRw: &sizeRw,
70+
SizeRootFs: &sizeRootFs,
5371
}
5472
respJSON, err = json.Marshal(resp)
5573
Expect(err).Should(BeNil())
5674
})
5775
Context("handler", func() {
5876
It("should return inspect object and 200 status code upon success", func() {
59-
service.EXPECT().Inspect(gomock.Any(), cid).Return(&resp, nil)
77+
service.EXPECT().Inspect(gomock.Any(), cid, false).Return(&resp, nil)
6078

6179
// handler should return response object with 200 status code
6280
h.inspect(rr, req)
6381
Expect(rr.Body).Should(MatchJSON(respJSON))
6482
Expect(rr).Should(HaveHTTPStatus(http.StatusOK))
6583
})
84+
It("should return inspect object with size information and 200 status code upon success", func() {
85+
service.EXPECT().Inspect(gomock.Any(), cid, true).Return(&resp, nil)
86+
87+
h.inspect(rr, req2)
88+
Expect(rr.Body).Should(MatchJSON(respJSON))
89+
Expect(rr).Should(HaveHTTPStatus(http.StatusOK))
90+
91+
var returnedResp types.Container
92+
err := json.Unmarshal(rr.Body.Bytes(), &returnedResp)
93+
Expect(err).Should(BeNil())
94+
Expect(returnedResp.SizeRw).ShouldNot(BeNil())
95+
Expect(*returnedResp.SizeRw).Should(Equal(int64(1000)))
96+
Expect(returnedResp.SizeRootFs).ShouldNot(BeNil())
97+
Expect(*returnedResp.SizeRootFs).Should(Equal(int64(5000)))
98+
})
6699
It("should return 404 status code if container was not found", func() {
67-
service.EXPECT().Inspect(gomock.Any(), cid).Return(nil, errdefs.NewNotFound(fmt.Errorf("no such container")))
100+
service.EXPECT().Inspect(gomock.Any(), cid, false).Return(nil, errdefs.NewNotFound(fmt.Errorf("no such container")))
68101
logger.EXPECT().Debugf(gomock.Any(), gomock.Any())
69102

70103
// handler should return error message with 404 status code
@@ -73,7 +106,7 @@ var _ = Describe("Container Inspect API", func() {
73106
Expect(rr).Should(HaveHTTPStatus(http.StatusNotFound))
74107
})
75108
It("should return 500 status code if service returns an error message", func() {
76-
service.EXPECT().Inspect(gomock.Any(), cid).Return(nil, fmt.Errorf("error"))
109+
service.EXPECT().Inspect(gomock.Any(), cid, false).Return(nil, fmt.Errorf("error"))
77110
logger.EXPECT().Debugf(gomock.Any(), gomock.Any())
78111

79112
// handler should return error message

api/types/container_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ type Container struct {
173173
// TODO: ExecIDs []string
174174
// TODO: HostConfig *container.HostConfig
175175
// TODO: GraphDriver GraphDriverData
176-
// TODO: SizeRw *int64 `json:",omitempty"`
177-
// TODO: SizeRootFs *int64 `json:",omitempty"`
176+
SizeRw *int64 `json:",omitempty"`
177+
SizeRootFs *int64 `json:",omitempty"`
178178

179179
Mounts []dockercompat.MountPoint
180180
Config *ContainerConfig

e2e/e2e_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func TestRun(t *testing.T) {
4848
tests.ContainerAttach(opt)
4949
tests.ContainerLogs(opt)
5050
tests.ContainerKill(opt)
51+
tests.ContainerInspect(opt)
5152

5253
// functional test for volume APIs
5354
tests.VolumeList(opt)

e2e/tests/container_inspect.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package tests
5+
6+
import (
7+
"encoding/json"
8+
"fmt"
9+
"io"
10+
"net/http"
11+
12+
. "github.com/onsi/ginkgo/v2"
13+
. "github.com/onsi/gomega"
14+
"github.com/runfinch/common-tests/command"
15+
"github.com/runfinch/common-tests/option"
16+
17+
"github.com/runfinch/finch-daemon/api/types"
18+
"github.com/runfinch/finch-daemon/e2e/client"
19+
)
20+
21+
// ContainerInspect tests the `GET containers/{id}/json` API.
22+
func ContainerInspect(opt *option.Option) {
23+
Describe("inspect container", func() {
24+
var (
25+
uClient *http.Client
26+
version string
27+
containerId string
28+
containerName string
29+
wantContainerName string
30+
)
31+
BeforeEach(func() {
32+
uClient = client.NewClient(GetDockerHostUrl())
33+
version = GetDockerApiVersion()
34+
containerName = testContainerName
35+
wantContainerName = fmt.Sprintf("/%s", containerName)
36+
containerId = command.StdoutStr(opt, "run", "-d", "--name", containerName, defaultImage, "sleep", "infinity")
37+
})
38+
AfterEach(func() {
39+
command.RemoveAll(opt)
40+
})
41+
42+
It("should inspect the container by ID", func() {
43+
res, err := uClient.Get(client.ConvertToFinchUrl(version, fmt.Sprintf("/containers/%s/json", containerId)))
44+
Expect(err).Should(BeNil())
45+
Expect(res.StatusCode).Should(Equal(http.StatusOK))
46+
var got types.Container
47+
err = json.NewDecoder(res.Body).Decode(&got)
48+
Expect(err).Should(BeNil())
49+
Expect(got.ID).Should(Equal(containerId))
50+
Expect(got.Name).Should(Equal(wantContainerName))
51+
Expect(got.State.Status).Should(Equal("running"))
52+
})
53+
54+
It("should inspect the container by name", func() {
55+
res, err := uClient.Get(client.ConvertToFinchUrl(version, fmt.Sprintf("/containers/%s/json", containerName)))
56+
Expect(err).Should(BeNil())
57+
Expect(res.StatusCode).Should(Equal(http.StatusOK))
58+
var got types.Container
59+
err = json.NewDecoder(res.Body).Decode(&got)
60+
Expect(err).Should(BeNil())
61+
Expect(got.ID).Should(Equal(containerId))
62+
Expect(got.Name).Should(Equal(wantContainerName))
63+
Expect(got.State.Status).Should(Equal("running"))
64+
})
65+
66+
It("should return size information when size parameter is true", func() {
67+
res, err := uClient.Get(client.ConvertToFinchUrl(version, fmt.Sprintf("/containers/%s/json?size=1", containerId)))
68+
Expect(err).Should(BeNil())
69+
Expect(res.StatusCode).Should(Equal(http.StatusOK))
70+
var got types.Container
71+
err = json.NewDecoder(res.Body).Decode(&got)
72+
Expect(err).Should(BeNil())
73+
Expect(got.SizeRw).ShouldNot(BeNil())
74+
Expect(got.SizeRootFs).ShouldNot(BeNil())
75+
})
76+
77+
It("should return 404 error when container does not exist", func() {
78+
res, err := uClient.Get(client.ConvertToFinchUrl(version, "/containers/nonexistent/json"))
79+
Expect(err).Should(BeNil())
80+
Expect(res.StatusCode).Should(Equal(http.StatusNotFound))
81+
body, err := io.ReadAll(res.Body)
82+
Expect(err).Should(BeNil())
83+
defer res.Body.Close()
84+
Expect(body).Should(MatchJSON(`{"message": "no such container: nonexistent"}`))
85+
})
86+
})
87+
}

internal/backend/container.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
package backend
55

66
import (
7+
"bytes"
78
"context"
9+
"encoding/json"
10+
"fmt"
811
"io"
912
"os"
1013
"os/exec"
@@ -27,7 +30,7 @@ type NerdctlContainerSvc interface {
2730
StartContainer(ctx context.Context, container containerd.Container) error
2831
StopContainer(ctx context.Context, container containerd.Container, timeout *time.Duration) error
2932
CreateContainer(ctx context.Context, args []string, netManager containerutil.NetworkOptionsManager, options types.ContainerCreateOptions) (containerd.Container, func(), error)
30-
InspectContainer(ctx context.Context, c containerd.Container) (*dockercompat.Container, error)
33+
InspectContainer(ctx context.Context, c containerd.Container, size bool) (*dockercompat.Container, error)
3134
InspectNetNS(ctx context.Context, pid int) (*native.NetNS, error)
3235
NewNetworkingOptionsManager(types.NetworkOptions) (containerutil.NetworkOptionsManager, error)
3336
ListContainers(ctx context.Context, options types.ContainerListOptions) ([]container.ListItem, error)
@@ -61,12 +64,33 @@ func (w *NerdctlWrapper) CreateContainer(ctx context.Context, args []string, net
6164
return container.Create(ctx, w.clientWrapper.client, args, netManager, options)
6265
}
6366

64-
func (w *NerdctlWrapper) InspectContainer(ctx context.Context, c containerd.Container) (*dockercompat.Container, error) {
65-
n, err := containerinspector.Inspect(ctx, c)
67+
func (w *NerdctlWrapper) InspectContainer(ctx context.Context, c containerd.Container, sizeFlag bool) (*dockercompat.Container, error) {
68+
var buf bytes.Buffer
69+
options := types.ContainerInspectOptions{
70+
Mode: "dockercompat",
71+
Stdout: &buf,
72+
Size: sizeFlag,
73+
GOptions: types.GlobalCommandOptions{
74+
Snapshotter: w.globalOptions.Snapshotter,
75+
},
76+
}
77+
78+
err := container.Inspect(ctx, w.clientWrapper.client, []string{c.ID()}, options)
6679
if err != nil {
6780
return nil, err
6881
}
69-
return dockercompat.ContainerFromNative(n)
82+
83+
// Parse the JSON response
84+
var containers []*dockercompat.Container
85+
if err := json.Unmarshal(buf.Bytes(), &containers); err != nil {
86+
return nil, err
87+
}
88+
89+
if len(containers) != 1 {
90+
return nil, fmt.Errorf("expected 1 container, got %d", len(containers))
91+
}
92+
93+
return containers[0], nil
7094
}
7195

7296
func (w *NerdctlWrapper) InspectNetNS(ctx context.Context, pid int) (*native.NetNS, error) {

internal/service/container/inspect.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import (
1818

1919
const networkPrefix = "unknown-eth"
2020

21-
func (s *service) Inspect(ctx context.Context, cid string) (*types.Container, error) {
21+
func (s *service) Inspect(ctx context.Context, cid string, sizeFlag bool) (*types.Container, error) {
2222
c, err := s.getContainer(ctx, cid)
2323
if err != nil {
2424
return nil, err
2525
}
2626

27-
inspect, err := s.nctlContainerSvc.InspectContainer(ctx, c)
27+
inspect, err := s.nctlContainerSvc.InspectContainer(ctx, c, sizeFlag)
2828
if err != nil {
2929
return nil, err
3030
}
@@ -47,6 +47,8 @@ func (s *service) Inspect(ctx context.Context, cid string) (*types.Container, er
4747
AppArmorProfile: inspect.AppArmorProfile,
4848
Mounts: inspect.Mounts,
4949
NetworkSettings: inspect.NetworkSettings,
50+
SizeRw: inspect.SizeRw,
51+
SizeRootFs: inspect.SizeRootFs,
5052
}
5153

5254
cont.Config = &types.ContainerConfig{

0 commit comments

Comments
 (0)