Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nerdctl support #561

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ With valid `source` options as such:
- `docker`: Docker engine (the default option)
- `docker-archive`: A Docker Tar Archive from disk
- `podman`: Podman engine (linux only)
- `nerdctl`: Nerdctl engine

## Installation

Expand Down Expand Up @@ -246,7 +247,7 @@ Key Binding | Description

No configuration is necessary, however, you can create a config file and override values:
```yaml
# supported options are "docker" and "podman"
# supported options are "docker", "podman" and "nerdctl"
container-engine: docker
# continue with analysis even if there are errors parsing the image archive
ignore-errors: false
Expand Down
12 changes: 10 additions & 2 deletions dive/get_image_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/dive/image/docker"
"github.com/wagoodman/dive/dive/image/nerdctl"
"github.com/wagoodman/dive/dive/image/podman"
)

Expand All @@ -15,14 +16,15 @@ const (
SourceDockerEngine
SourcePodmanEngine
SourceDockerArchive
SourceNerdctlEngine
)

type ImageSource int

var ImageSources = []string{SourceDockerEngine.String(), SourcePodmanEngine.String(), SourceDockerArchive.String()}
var ImageSources = []string{SourceDockerEngine.String(), SourcePodmanEngine.String(), SourceDockerArchive.String(), SourceNerdctlEngine.String()}

func (r ImageSource) String() string {
return [...]string{"unknown", "docker", "podman", "docker-archive"}[r]
return [...]string{"unknown", "docker", "podman", "docker-archive", "nerdctl"}[r]
}

func ParseImageSource(r string) ImageSource {
Expand All @@ -33,6 +35,8 @@ func ParseImageSource(r string) ImageSource {
return SourcePodmanEngine
case SourceDockerArchive.String():
return SourceDockerArchive
case SourceNerdctlEngine.String():
return SourceNerdctlEngine
case "docker-tar":
return SourceDockerArchive
default:
Expand All @@ -53,6 +57,8 @@ func DeriveImageSource(image string) (ImageSource, string) {
return SourceDockerEngine, imageSource
case SourcePodmanEngine.String():
return SourcePodmanEngine, imageSource
case SourceNerdctlEngine.String():
return SourceNerdctlEngine, imageSource
case SourceDockerArchive.String():
return SourceDockerArchive, imageSource
case "docker-tar":
Expand All @@ -67,6 +73,8 @@ func GetImageResolver(r ImageSource) (image.Resolver, error) {
return docker.NewResolverFromEngine(), nil
case SourcePodmanEngine:
return podman.NewResolverFromEngine(), nil
case SourceNerdctlEngine:
return nerdctl.NewResolverFromEngine(), nil
case SourceDockerArchive:
return docker.NewResolverFromArchive(), nil
}
Expand Down
26 changes: 26 additions & 0 deletions dive/image/nerdctl/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nerdctl

import (
"os"
)

func buildImageFromCli(buildArgs []string) (string, error) {
iidfile, err := os.CreateTemp("/tmp", "dive.*.iid")
if err != nil {
return "", err
}
defer os.Remove(iidfile.Name())

allArgs := append([]string{"--iidfile", iidfile.Name()}, buildArgs...)
err = runNerdctlCmd("build", allArgs...)
if err != nil {
return "", err
}

imageId, err := os.ReadFile(iidfile.Name())
if err != nil {
return "", err
}

return string(imageId), nil
}
54 changes: 54 additions & 0 deletions dive/image/nerdctl/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package nerdctl

import (
"fmt"
"io"
"os"
"os/exec"

"github.com/wagoodman/dive/utils"
)

// runNerdctlCmd runs a given nerdctl command in the current tty
func runNerdctlCmd(cmdStr string, args ...string) error {
if !isNerdctlBinaryAvailable() {
return fmt.Errorf("cannot find nerdctl executable")
}

allArgs := utils.CleanArgs(append([]string{cmdStr}, args...))

cmd := exec.Command("nerdctl", allArgs...)
cmd.Env = os.Environ()

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin

return cmd.Run()
}

func streamNerdctlCmd(args ...string) (io.Reader, error) {
if !isNerdctlBinaryAvailable() {
return nil, fmt.Errorf("cannot find nerdctl executable")
}

cmd := exec.Command("nerdctl", utils.CleanArgs(args)...)
cmd.Env = os.Environ()

reader, writer, err := os.Pipe()
if err != nil {
return nil, err
}

defer writer.Close()

cmd.Stdout = writer
cmd.Stderr = os.Stderr

return reader, cmd.Start()
}

func isNerdctlBinaryAvailable() bool {
_, err := exec.LookPath("nerdctl")
return err == nil
}
68 changes: 68 additions & 0 deletions dive/image/nerdctl/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package nerdctl

import (
"fmt"
"io"

"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/dive/image/docker"
)

type resolver struct{}

func NewResolverFromEngine() *resolver {
return &resolver{}
}

func (r *resolver) Build(args []string) (*image.Image, error) {
id, err := buildImageFromCli(args)

if err != nil {
return nil, err
}

return r.Fetch(id)
}

func (r *resolver) Fetch(id string) (*image.Image, error) {
img, err := r.resolveFromDockerArchive(id)

if err == nil {
return img, err
}

return nil, fmt.Errorf("unable to resolve image '%s': %+v", id, err)
}

func (r *resolver) resolveFromDockerArchive(id string) (*image.Image, error) {
reader, err := streamNerdctlCmd("image", "save", id)

if err != nil {
return nil, err
}

img, err := docker.NewImageArchive(io.NopCloser(reader))

if err != nil {
fmt.Println("Handler not available locally. Trying to pull '" + id + "'...")
err = runNerdctlCmd("pull", id)

if err != nil {
return nil, err
}

reader, err = streamNerdctlCmd("image", "save", id)

if err != nil {
return nil, err
}

img, err = docker.NewImageArchive(io.NopCloser(reader))

if err != nil {
return nil, err
}
}

return img.ToImage()
}