Skip to content

Commit 5ba7305

Browse files
feat: handle dockercompat inspect for devcontainers (runfinch#1121)
Signed-off-by: Shubharanshu Mahapatra <[email protected]>
1 parent c716780 commit 5ba7305

9 files changed

+341
-20
lines changed

Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,12 @@ download-licenses:
244244
# - github.com/runfinch/finch is ignored because we don't have to check our own license.
245245
# Moreover, if we don't ignore it, the following error will occur:
246246
# `module github.com/runfinch/finch has empty version, defaults to HEAD. The license URL may be incorrect. Please verify!`.
247+
# `module github.com/multiformats/go-base36 has a Apache license and MIT license but not written as LICENSE format`
247248
check-licenses: GOBIN = $(CURDIR)/tools_bin
248249
check-licenses:
249250
go mod download
250251
GOBIN=$(GOBIN) go install github.com/google/go-licenses
251-
$(GOBIN)/go-licenses check --ignore golang.org/x,github.com/runfinch/finch --allowed_licenses Apache-2.0,BSD-2-Clause,BSD-3-Clause,ISC,MIT --include_tests ./...
252+
$(GOBIN)/go-licenses check --ignore golang.org/x,github.com/runfinch/finch --ignore github.com/multiformats/go-base36 --allowed_licenses Apache-2.0,BSD-2-Clause,BSD-3-Clause,ISC,MIT --include_tests ./...
252253

253254
.PHONY: test-unit
254255
test-unit:

cmd/finch/devcontainer_patch.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package main denotes the entry point of finch CLI.
5+
// TODO: Remove all instances of these calls once supported upstream
6+
package main
7+
8+
import (
9+
"bytes"
10+
"encoding/json"
11+
"fmt"
12+
"strings"
13+
14+
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
15+
"github.com/docker/go-connections/nat"
16+
"github.com/sirupsen/logrus"
17+
18+
"github.com/runfinch/finch/pkg/command"
19+
)
20+
21+
// Config is from https://github.com/moby/moby/blob/8dbd90ec00daa26dc45d7da2431c965dec99e8b4/api/types/container/config.go#L37-L69
22+
type Config struct {
23+
Hostname string `json:",omitempty"` // Hostname
24+
User string `json:",omitempty"` // User that will run the command(s) inside the container, also support user:group
25+
AttachStdin bool // Attach the standard input, makes possible user interaction
26+
ExposedPorts nat.PortSet `json:",omitempty"` // List of exposed ports
27+
Env []string `json:",omitempty"` // List of environment variable to set in the container
28+
Cmd []string `json:",omitempty"` // Command to run when starting the container
29+
Volumes map[string]struct{} `json:",omitempty"` // List of volumes (mounts) used for the container
30+
WorkingDir string `json:",omitempty"` // Current directory (PWD) in the command will be launched
31+
Entrypoint []string `json:",omitempty"` // Entrypoint to run when starting the container
32+
Labels map[string]string `json:",omitempty"` // List of labels set to this containerT
33+
Image string `json:",omitempty"`
34+
}
35+
36+
// Container mimics a `docker container inspect` object.
37+
// From https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L340-L374
38+
type Container struct {
39+
ID string `json:"Id"`
40+
Created string
41+
Path string
42+
Args []string
43+
State *dockercompat.ContainerState
44+
Image string
45+
ResolvConfPath string
46+
HostnamePath string
47+
LogPath string
48+
Name string
49+
RestartCount int
50+
Driver string
51+
Platform string
52+
AppArmorProfile string
53+
SizeRw *int64 `json:",omitempty"`
54+
SizeRootFs *int64 `json:",omitempty"`
55+
Mounts []dockercompat.MountPoint
56+
Config *Config
57+
NetworkSettings *dockercompat.NetworkSettings
58+
}
59+
60+
func prettyPrintJSON(input string) {
61+
var mergedData []Container
62+
jsonObjects := strings.Split(input, "\n")
63+
64+
for i, jsonStr := range jsonObjects {
65+
if len(jsonStr) == 0 {
66+
continue
67+
}
68+
var container Container
69+
err := json.Unmarshal([]byte(jsonStr), &container)
70+
if err != nil {
71+
logrus.Error("Error parsing JSON at index: ", i, err)
72+
continue
73+
}
74+
75+
if container.Config != nil {
76+
container.Config.Image = container.Image
77+
}
78+
79+
if container.State != nil {
80+
container.State.StartedAt = "0001-01-01T00:00:00Z"
81+
}
82+
83+
if container.NetworkSettings == nil {
84+
container.NetworkSettings = &dockercompat.NetworkSettings{
85+
Ports: &nat.PortMap{},
86+
}
87+
}
88+
89+
mergedData = append(mergedData, container)
90+
}
91+
92+
finalJSON, err := json.MarshalIndent(mergedData, "", " ")
93+
if err != nil {
94+
logrus.Error("Error marshaling final JSON: ", err)
95+
return
96+
}
97+
98+
fmt.Println(string(finalJSON))
99+
}
100+
101+
func inspectContainerOutputHandler(cmd command.Command) error {
102+
var stdoutBuf bytes.Buffer
103+
cmd.SetStdout(&stdoutBuf)
104+
err := cmd.Run()
105+
if err != nil {
106+
return err
107+
}
108+
prettyPrintJSON(stdoutBuf.String())
109+
return err
110+
}

cmd/finch/nerdctl.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type nerdctlCommandCreator struct {
4545

4646
type (
4747
argHandler func(systemDeps NerdctlCommandSystemDeps, fc *config.Finch, args []string, index int) error
48-
commandHandler func(systemDeps NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string) error
48+
commandHandler func(systemDeps NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string, inspectType *string) error
4949
)
5050

5151
func newNerdctlCommandCreator(
@@ -256,7 +256,7 @@ func handleDockerBuildLoad(_ NerdctlCommandSystemDeps, fc *config.Finch, nerdctl
256256
return nil
257257
}
258258

259-
func handleBuildx(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string) error {
259+
func handleBuildx(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string, _ *string) error {
260260
if fc == nil || !fc.DockerCompat {
261261
return nil
262262
}
@@ -279,7 +279,7 @@ func handleBuildx(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string,
279279
return nil
280280
}
281281

282-
func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string) error {
282+
func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string, inspectType *string) error {
283283
if fc == nil || !fc.DockerCompat {
284284
return nil
285285
}
@@ -289,10 +289,10 @@ func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmd
289289
}
290290

291291
modeDockerCompat := `--mode=dockercompat`
292-
inspectType := ""
293292
sizeArg := ""
294293
savedArgs := []string{}
295294
skip := false
295+
*inspectType = ""
296296

297297
for idx, arg := range *args {
298298
if skip {
@@ -301,13 +301,13 @@ func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmd
301301
}
302302

303303
if (arg == "--type") && (idx < len(*args)-1) {
304-
inspectType = (*args)[idx+1]
304+
*inspectType = (*args)[idx+1]
305305
skip = true
306306
continue
307307
}
308308

309309
if strings.Contains(arg, "--type") && strings.Contains(arg, "=") {
310-
inspectType = strings.Split(arg, "=")[1]
310+
*inspectType = strings.Split(arg, "=")[1]
311311
continue
312312
}
313313

@@ -319,7 +319,7 @@ func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmd
319319
savedArgs = append(savedArgs, arg)
320320
}
321321

322-
switch inspectType {
322+
switch *inspectType {
323323
case "image":
324324
*cmdName = "image inspect"
325325
*args = append([]string{modeDockerCompat}, savedArgs...)
@@ -336,8 +336,9 @@ func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmd
336336
case "":
337337
*cmdName = "inspect"
338338
*args = append([]string{modeDockerCompat}, savedArgs...)
339+
*inspectType = "container"
339340
default:
340-
return fmt.Errorf("unsupported inspect type: %s", inspectType)
341+
return fmt.Errorf("unsupported inspect type: %s", *inspectType)
341342
}
342343

343344
return nil

cmd/finch/nerdctl_darwin_test.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package main
77

88
import (
9+
"bytes"
910
"errors"
1011
"fmt"
1112
"os"
@@ -1306,7 +1307,10 @@ func TestNerdctlCommand_run_inspectCommand(t *testing.T) {
13061307
ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false)
13071308
ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false)
13081309
c := mocks.NewCommand(ctrl)
1309-
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "da24").Return(c)
1310+
var stdoutBuf bytes.Buffer
1311+
c.EXPECT().SetStdout(&stdoutBuf)
1312+
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "da24",
1313+
"--format", "{{json .}}").Return(c)
13101314
c.EXPECT().Run()
13111315
},
13121316
},
@@ -1339,7 +1343,10 @@ func TestNerdctlCommand_run_inspectCommand(t *testing.T) {
13391343
ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false)
13401344
ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false)
13411345
c := mocks.NewCommand(ctrl)
1342-
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de").Return(c)
1346+
var stdoutBuf bytes.Buffer
1347+
c.EXPECT().SetStdout(&stdoutBuf)
1348+
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de",
1349+
"--format", "{{json .}}").Return(c)
13431350
c.EXPECT().Run()
13441351
},
13451352
},
@@ -1448,7 +1455,10 @@ func TestNerdctlCommand_run_inspectCommand(t *testing.T) {
14481455
ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false)
14491456
ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false)
14501457
c := mocks.NewCommand(ctrl)
1451-
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de").Return(c)
1458+
var stdoutBuf bytes.Buffer
1459+
c.EXPECT().SetStdout(&stdoutBuf)
1460+
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de",
1461+
"--format", "{{json .}}").Return(c)
14521462
c.EXPECT().Run()
14531463
},
14541464
},

cmd/finch/nerdctl_native.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
2121
cmdHandler commandHandler
2222
aMap map[string]argHandler
2323
err error
24+
inspectType string
2425
)
2526

2627
// eat the debug arg, and set the log level to avoid nerdctl parsing this flag
@@ -48,7 +49,7 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
4849

4950
// First check if the command has a command handler
5051
if hasCmdHandler {
51-
err := cmdHandler(nc.systemDeps, nc.fc, &cmdName, &args)
52+
err := cmdHandler(nc.systemDeps, nc.fc, &cmdName, &args, &inspectType)
5253
if err != nil {
5354
return err
5455
}
@@ -81,7 +82,14 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
8182
)
8283
}
8384

85+
if inspectType == "container" && nc.fc.DockerCompat && !slices.Contains(cmdArgs, "--format") {
86+
cmdArgs = append(cmdArgs, "--format", "{{json .}}")
87+
cmd := nc.ncc.Create(cmdArgs...)
88+
return inspectContainerOutputHandler(cmd)
89+
}
90+
8491
return nc.ncc.Create(cmdArgs...).Run()
92+
8593
}
8694

8795
var osAliasMap = map[string]string{}

cmd/finch/nerdctl_remote.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
3737
cmdHandler commandHandler
3838
aMap map[string]argHandler
3939
firstOptPos int
40+
inspectType string
4041
)
4142

4243
// accumulate distributed map entities
@@ -80,7 +81,7 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
8081

8182
// First check if the command has command handler
8283
if hasCmdHandler {
83-
err := cmdHandler(nc.systemDeps, nc.fc, &cmdName, &args)
84+
err := cmdHandler(nc.systemDeps, nc.fc, &cmdName, &args, &inspectType)
8485
if err != nil {
8586
return err
8687
}
@@ -333,6 +334,12 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
333334
return nc.ncc.RunWithReplacingStdout([]command.Replacement{{Source: "nerdctl", Target: "finch"}}, runArgs...)
334335
}
335336

337+
if inspectType == "container" && nc.fc.DockerCompat && !slices.Contains(runArgs, "--format") {
338+
runArgs = append(runArgs, "--format", "{{json .}}")
339+
cmd := nc.ncc.Create(runArgs...)
340+
return inspectContainerOutputHandler(cmd)
341+
}
342+
336343
return nc.ncc.Create(runArgs...).Run()
337344
}
338345

cmd/finch/nerdctl_windows.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func handleSecretOption(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, ne
262262
}
263263

264264
// cp command handler, takes command arguments and converts hostpath to wsl path in place. It ignores all other arguments.
265-
func cpHandler(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, _ *string, nerdctlCmdArgs *[]string) error {
265+
func cpHandler(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, _ *string, nerdctlCmdArgs *[]string, _ *string) error {
266266
for i, arg := range *nerdctlCmdArgs {
267267
// -L and --follow-symlink don't have to be processed
268268
if strings.HasPrefix(arg, "-") || arg == "cp" {
@@ -285,7 +285,7 @@ func cpHandler(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, _ *string,
285285
}
286286

287287
// this is the handler for image build command. It translates build context to wsl path.
288-
func imageBuildHandler(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, _ *string, nerdctlCmdArgs *[]string) error {
288+
func imageBuildHandler(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, _ *string, nerdctlCmdArgs *[]string, _ *string) error {
289289
var err error
290290
argLen := len(*nerdctlCmdArgs) - 1
291291
// -h/--help don't have buildcontext, just return

0 commit comments

Comments
 (0)