PoC: zfs support
Some checks failed
Validations / Static analysis (push) Failing after 32m53s
Validations / Unit tests (ubuntu-latest) (push) Failing after 10m54s
Validations / Build snapshot artifacts (push) Failing after 32m5s
Validations / Acceptance tests (Linux) (push) Has been skipped
Validations / Acceptance tests (Mac) (push) Has been skipped
Validations / Acceptance tests (Windows) (push) Has been skipped

This commit is contained in:
norohind 2025-02-27 21:28:51 +01:00
parent 925cdd8648
commit 534bb92185
Signed by: norohind
SSH Key Fingerprint: SHA256:SnI4bWnejM2/YEQ5hpH58TUohiQpnjoKN6tXUQlobE0
4 changed files with 135 additions and 1 deletions

View File

@ -2,6 +2,7 @@ package dive
import (
"fmt"
"github.com/wagoodman/dive/dive/image/zfs"
"net/url"
"strings"
@ -15,6 +16,7 @@ const (
SourceDockerEngine
SourcePodmanEngine
SourceDockerArchive
SourceZFS
)
type ImageSource int
@ -22,10 +24,11 @@ type ImageSource int
var ImageSources = []string{SourceDockerEngine.String(), SourcePodmanEngine.String(), SourceDockerArchive.String()}
func (r ImageSource) String() string {
return [...]string{"unknown", "docker", "podman", "docker-archive"}[r]
return [...]string{"unknown", "docker", "podman", "docker-archive", "zfs"}[r]
}
func ParseImageSource(r string) ImageSource {
switch r {
case SourceDockerEngine.String():
return SourceDockerEngine
@ -57,6 +60,8 @@ func DeriveImageSource(image string) (ImageSource, string) {
return SourceDockerArchive, imageSource
case "docker-tar":
return SourceDockerArchive, imageSource
case SourceZFS.String():
return SourceZFS, imageSource
}
return SourceUnknown, ""
}
@ -69,6 +74,8 @@ func GetImageResolver(r ImageSource) (image.Resolver, error) {
return podman.NewResolverFromEngine(), nil
case SourceDockerArchive:
return docker.NewResolverFromArchive(), nil
case SourceZFS:
return zfs.NewResolverFromEngine(), nil
}
return nil, fmt.Errorf("unable to determine image resolver")

124
dive/image/zfs/resolver.go Normal file
View File

@ -0,0 +1,124 @@
package zfs
import (
"errors"
"fmt"
"github.com/mistifyio/go-zfs"
"github.com/sirupsen/logrus"
"github.com/wagoodman/dive/dive/filetree"
"github.com/wagoodman/dive/dive/image"
"os"
"os/exec"
"strings"
)
type resolver struct{}
func NewResolverFromEngine() *resolver {
return &resolver{}
}
func (r *resolver) Build(args []string) (*image.Image, error) {
return nil, errors.New("can't build ZFS")
}
func (r *resolver) Fetch(id string) (*image.Image, error) {
img, err := r.resolveFromZfsDataset(id)
if err == nil {
return img, err
}
return nil, fmt.Errorf("unable to resolve image '%s': %+v", id, err)
}
func filterOutDatasetSnapshots(snapshots []*zfs.Dataset, datasetName string) []*zfs.Dataset {
var result []*zfs.Dataset
for _, snap := range snapshots {
if strings.HasPrefix(snap.Name, datasetName+"@") {
result = append(result, snap)
}
}
return result
}
func iterate(path string) ([]string, error) {
cmd := exec.Command("find", path, "-type", "f", "-o", "-type", "d")
//cmd.Stderr = os.Stderr
stdout, _ := cmd.Output()
//if err != nil {
// println("Error is")
// println(err.Error())
// panic(err)
//}
//println("Returning iterate")
//println(stdout)
//time.Sleep(100000)
return strings.Split(string(stdout), "\n"), nil
}
func (r *resolver) resolveFromZfsDataset(id string) (*image.Image, error) {
ds, err := zfs.GetDataset(id)
if err != nil {
return nil, err
}
snapshots, err := ds.Snapshots()
snapshots = filterOutDatasetSnapshots(snapshots, ds.Name)
if err != nil {
return nil, err
}
var trees []*filetree.FileTree
var layers []*image.Layer
dsMountpoint := ds.Mountpoint
if dsMountpoint == "" {
return nil, errors.New("Failed to find mountpoint for " + ds.Name)
}
for idx, snap := range snapshots {
snapName := strings.TrimPrefix(snap.Name, ds.Name+"@")
logrus.Info("Processing snapshot " + snapName)
snapMountpoint := dsMountpoint + ".zfs/snapshot/" + snapName
tree := filetree.NewFileTree()
files, err := iterate(snapMountpoint)
if err != nil {
return nil, err
}
for _, realPath := range files {
virtualPath := strings.TrimPrefix(realPath, snapMountpoint)
if virtualPath == "" {
continue
}
osStat, err := os.Lstat(realPath)
if err != nil {
return nil, err
}
//print("Adding virtualPath: " + virtualPath)
println("Adding realPath: " + realPath)
_, _, err = tree.AddPath(virtualPath, filetree.NewFileInfo(realPath, virtualPath, osStat))
if err != nil {
return nil, err
}
}
trees = append(trees, tree)
layers = append(layers, &image.Layer{
Id: snapName,
Index: idx,
Command: "zfs snapshot command",
Size: uint64(tree.VisibleSize()),
Tree: tree,
Names: make([]string, 0),
Digest: "zfs snapshot digest-" + snapName,
})
}
logrus.Info("Returning resolved zfs image obj")
return &image.Image{
Trees: trees,
Layers: layers,
}, nil
}

1
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/google/uuid v1.1.1
github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b
github.com/lunixbochs/vtclean v1.0.0
github.com/mistifyio/go-zfs v2.1.1+incompatible
github.com/mitchellh/go-homedir v1.1.0
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee
github.com/sergi/go-diff v1.0.0

2
go.sum
View File

@ -112,6 +112,8 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8=
github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=