diff --git a/dive/get_image_resolver.go b/dive/get_image_resolver.go index 05d95ab..21b0d77 100644 --- a/dive/get_image_resolver.go +++ b/dive/get_image_resolver.go @@ -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") diff --git a/dive/image/zfs/resolver.go b/dive/image/zfs/resolver.go new file mode 100644 index 0000000..6752e05 --- /dev/null +++ b/dive/image/zfs/resolver.go @@ -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 +} diff --git a/go.mod b/go.mod index a3fa807..b340ffc 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 729cb9e..53a70af 100644 --- a/go.sum +++ b/go.sum @@ -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=