Skip to content

Commit

Permalink
feat: handle dockercompat inspect for devcontainers (#1121)
Browse files Browse the repository at this point in the history
Signed-off-by: Shubharanshu Mahapatra <[email protected]>
  • Loading branch information
Shubhranshu153 authored Oct 4, 2024
1 parent c716780 commit 5ba7305
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 20 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,12 @@ download-licenses:
# - github.com/runfinch/finch is ignored because we don't have to check our own license.
# Moreover, if we don't ignore it, the following error will occur:
# `module github.com/runfinch/finch has empty version, defaults to HEAD. The license URL may be incorrect. Please verify!`.
# `module github.com/multiformats/go-base36 has a Apache license and MIT license but not written as LICENSE format`
check-licenses: GOBIN = $(CURDIR)/tools_bin
check-licenses:
go mod download
GOBIN=$(GOBIN) go install github.com/google/go-licenses
$(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 ./...
$(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 ./...

.PHONY: test-unit
test-unit:
Expand Down
110 changes: 110 additions & 0 deletions cmd/finch/devcontainer_patch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// Package main denotes the entry point of finch CLI.
// TODO: Remove all instances of these calls once supported upstream
package main

import (
"bytes"
"encoding/json"
"fmt"
"strings"

"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
"github.com/docker/go-connections/nat"
"github.com/sirupsen/logrus"

"github.com/runfinch/finch/pkg/command"
)

// Config is from https://github.com/moby/moby/blob/8dbd90ec00daa26dc45d7da2431c965dec99e8b4/api/types/container/config.go#L37-L69
type Config struct {
Hostname string `json:",omitempty"` // Hostname
User string `json:",omitempty"` // User that will run the command(s) inside the container, also support user:group
AttachStdin bool // Attach the standard input, makes possible user interaction
ExposedPorts nat.PortSet `json:",omitempty"` // List of exposed ports
Env []string `json:",omitempty"` // List of environment variable to set in the container
Cmd []string `json:",omitempty"` // Command to run when starting the container
Volumes map[string]struct{} `json:",omitempty"` // List of volumes (mounts) used for the container
WorkingDir string `json:",omitempty"` // Current directory (PWD) in the command will be launched
Entrypoint []string `json:",omitempty"` // Entrypoint to run when starting the container
Labels map[string]string `json:",omitempty"` // List of labels set to this containerT
Image string `json:",omitempty"`
}

// Container mimics a `docker container inspect` object.
// From https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L340-L374
type Container struct {
ID string `json:"Id"`
Created string
Path string
Args []string
State *dockercompat.ContainerState
Image string
ResolvConfPath string
HostnamePath string
LogPath string
Name string
RestartCount int
Driver string
Platform string
AppArmorProfile string
SizeRw *int64 `json:",omitempty"`
SizeRootFs *int64 `json:",omitempty"`
Mounts []dockercompat.MountPoint
Config *Config
NetworkSettings *dockercompat.NetworkSettings
}

func prettyPrintJSON(input string) {
var mergedData []Container
jsonObjects := strings.Split(input, "\n")

for i, jsonStr := range jsonObjects {
if len(jsonStr) == 0 {
continue
}
var container Container
err := json.Unmarshal([]byte(jsonStr), &container)
if err != nil {
logrus.Error("Error parsing JSON at index: ", i, err)
continue
}

if container.Config != nil {
container.Config.Image = container.Image
}

if container.State != nil {
container.State.StartedAt = "0001-01-01T00:00:00Z"
}

if container.NetworkSettings == nil {
container.NetworkSettings = &dockercompat.NetworkSettings{
Ports: &nat.PortMap{},
}
}

mergedData = append(mergedData, container)
}

finalJSON, err := json.MarshalIndent(mergedData, "", " ")
if err != nil {
logrus.Error("Error marshaling final JSON: ", err)
return
}

fmt.Println(string(finalJSON))
}

func inspectContainerOutputHandler(cmd command.Command) error {
var stdoutBuf bytes.Buffer
cmd.SetStdout(&stdoutBuf)
err := cmd.Run()
if err != nil {
return err
}
prettyPrintJSON(stdoutBuf.String())
return err
}
17 changes: 9 additions & 8 deletions cmd/finch/nerdctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type nerdctlCommandCreator struct {

type (
argHandler func(systemDeps NerdctlCommandSystemDeps, fc *config.Finch, args []string, index int) error
commandHandler func(systemDeps NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string) error
commandHandler func(systemDeps NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string, inspectType *string) error
)

func newNerdctlCommandCreator(
Expand Down Expand Up @@ -256,7 +256,7 @@ func handleDockerBuildLoad(_ NerdctlCommandSystemDeps, fc *config.Finch, nerdctl
return nil
}

func handleBuildx(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string) error {
func handleBuildx(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string, args *[]string, _ *string) error {
if fc == nil || !fc.DockerCompat {
return nil
}
Expand All @@ -279,7 +279,7 @@ func handleBuildx(_ NerdctlCommandSystemDeps, fc *config.Finch, cmdName *string,
return nil
}

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

modeDockerCompat := `--mode=dockercompat`
inspectType := ""
sizeArg := ""
savedArgs := []string{}
skip := false
*inspectType = ""

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

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

if strings.Contains(arg, "--type") && strings.Contains(arg, "=") {
inspectType = strings.Split(arg, "=")[1]
*inspectType = strings.Split(arg, "=")[1]
continue
}

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

switch inspectType {
switch *inspectType {
case "image":
*cmdName = "image inspect"
*args = append([]string{modeDockerCompat}, savedArgs...)
Expand All @@ -336,8 +336,9 @@ func handleDockerCompatInspect(_ NerdctlCommandSystemDeps, fc *config.Finch, cmd
case "":
*cmdName = "inspect"
*args = append([]string{modeDockerCompat}, savedArgs...)
*inspectType = "container"
default:
return fmt.Errorf("unsupported inspect type: %s", inspectType)
return fmt.Errorf("unsupported inspect type: %s", *inspectType)
}

return nil
Expand Down
16 changes: 13 additions & 3 deletions cmd/finch/nerdctl_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package main

import (
"bytes"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -1306,7 +1307,10 @@ func TestNerdctlCommand_run_inspectCommand(t *testing.T) {
ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false)
ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false)
c := mocks.NewCommand(ctrl)
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "da24").Return(c)
var stdoutBuf bytes.Buffer
c.EXPECT().SetStdout(&stdoutBuf)
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "da24",
"--format", "{{json .}}").Return(c)
c.EXPECT().Run()
},
},
Expand Down Expand Up @@ -1339,7 +1343,10 @@ func TestNerdctlCommand_run_inspectCommand(t *testing.T) {
ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false)
ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false)
c := mocks.NewCommand(ctrl)
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de").Return(c)
var stdoutBuf bytes.Buffer
c.EXPECT().SetStdout(&stdoutBuf)
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de",
"--format", "{{json .}}").Return(c)
c.EXPECT().Run()
},
},
Expand Down Expand Up @@ -1448,7 +1455,10 @@ func TestNerdctlCommand_run_inspectCommand(t *testing.T) {
ncsd.EXPECT().LookupEnv("COSIGN_PASSWORD").Return("", false)
ncsd.EXPECT().LookupEnv("COMPOSE_FILE").Return("", false)
c := mocks.NewCommand(ctrl)
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de").Return(c)
var stdoutBuf bytes.Buffer
c.EXPECT().SetStdout(&stdoutBuf)
lcc.EXPECT().Create("shell", limaInstanceName, "sudo", "-E", nerdctlCmdName, "inspect", "--mode=dockercompat", "44de",
"--format", "{{json .}}").Return(c)
c.EXPECT().Run()
},
},
Expand Down
10 changes: 9 additions & 1 deletion cmd/finch/nerdctl_native.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
cmdHandler commandHandler
aMap map[string]argHandler
err error
inspectType string
)

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

// First check if the command has a command handler
if hasCmdHandler {
err := cmdHandler(nc.systemDeps, nc.fc, &cmdName, &args)
err := cmdHandler(nc.systemDeps, nc.fc, &cmdName, &args, &inspectType)
if err != nil {
return err
}
Expand Down Expand Up @@ -81,7 +82,14 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
)
}

if inspectType == "container" && nc.fc.DockerCompat && !slices.Contains(cmdArgs, "--format") {
cmdArgs = append(cmdArgs, "--format", "{{json .}}")
cmd := nc.ncc.Create(cmdArgs...)
return inspectContainerOutputHandler(cmd)
}

return nc.ncc.Create(cmdArgs...).Run()

}

var osAliasMap = map[string]string{}
Expand Down
9 changes: 8 additions & 1 deletion cmd/finch/nerdctl_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func (nc *nerdctlCommand) run(cmdName string, args []string) error {
cmdHandler commandHandler
aMap map[string]argHandler
firstOptPos int
inspectType string
)

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

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

if inspectType == "container" && nc.fc.DockerCompat && !slices.Contains(runArgs, "--format") {
runArgs = append(runArgs, "--format", "{{json .}}")
cmd := nc.ncc.Create(runArgs...)
return inspectContainerOutputHandler(cmd)
}

return nc.ncc.Create(runArgs...).Run()
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/finch/nerdctl_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func handleSecretOption(systemDeps NerdctlCommandSystemDeps, _ *config.Finch, ne
}

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

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

0 comments on commit 5ba7305

Please sign in to comment.