image save: make output tarball OCI compliant
- Moves blobs for the tarball into the OCI compliant paths (`blobs/<alg>/<digest>`)
- Removes old `VERSION` file which has no meaning and can't be used anyway in the new format
- Adds oci-layout, oci manifest, and index files to the outputted tars

For the new files this just goes ahead and uses OCI media types (instead of old docker types) since these are new and should not break anything.

All this works because the old docker layout is dependent on the `manifest.json` file at the root of the tar which has paths to each of the blobs in the tarball. The change updates the paths in the manifest to point to the new paths.

This is the same technique used by buildkit to support both OCI tar formats as well as legacy docker ones.

image/tarexport/tarexport.go
package tarexport // import "github.com/docker/docker/image/tarexport"

import (
	"github.com/docker/distribution"
	"github.com/docker/docker/image"
	"github.com/docker/docker/layer"
	refstore "github.com/docker/docker/reference"
)

const (
	manifestFileName           = "manifest.json"
	legacyLayerFileName        = "layer.tar"
	legacyConfigFileName       = "json"
	legacyVersionFileName      = "VERSION"
	legacyRepositoriesFileName = "repositories"
)

type manifestItem struct {
	Config       string
	RepoTags     []string
	Layers       []string
	Parent       image.ID                                 `json:",omitempty"`
	LayerSources map[layer.DiffID]distribution.Descriptor `json:",omitempty"`
}

type tarexporter struct {
	is             image.Store
	lss            layer.Store
	rs             refstore.Store
	loggerImgEvent LogImageEvent
}

// LogImageEvent defines interface for event generation related to image tar(load and save) operations
type LogImageEvent interface {
	// LogImageEvent generates an event related to an image operation
	LogImageEvent(imageID, refName, action string)
}

// NewTarExporter returns new Exporter for tar packages
func NewTarExporter(is image.Store, lss layer.Store, rs refstore.Store, loggerImgEvent LogImageEvent) image.Exporter {
	return &tarexporter{
		is:             is,
		lss:            lss,
		rs:             rs,
		loggerImgEvent: loggerImgEvent,
	}
}

package tarexport // import "github.com/docker/docker/image/tarexport"

import (
	"github.com/docker/distribution"
	"github.com/docker/docker/image"
	"github.com/docker/docker/layer"
	refstore "github.com/docker/docker/reference"
)

const (
	manifestFileName           = "manifest.json"
	legacyLayerFileName        = "layer.tar"
	legacyConfigFileName       = "json"
	legacyRepositoriesFileName = "repositories"

	ociIndexFileName  = "index.json"
	ociLayoutFilename = "oci-layout"
	ociLayoutContent  = `{"imageLayoutVersion": "1.0.0"}`
)

type manifestItem struct {
	Config       string
	RepoTags     []string
	Layers       []string
	Parent       image.ID                                 `json:",omitempty"`
	LayerSources map[layer.DiffID]distribution.Descriptor `json:",omitempty"`
}

type tarexporter struct {
	is             image.Store
	lss            layer.Store
	rs             refstore.Store
	loggerImgEvent LogImageEvent
}

// LogImageEvent defines interface for event generation related to image tar(load and save) operations
type LogImageEvent interface {
	// LogImageEvent generates an event related to an image operation
	LogImageEvent(imageID, refName, action string)
}

// NewTarExporter returns new Exporter for tar packages
func NewTarExporter(is image.Store, lss layer.Store, rs refstore.Store, loggerImgEvent LogImageEvent) image.Exporter {
	return &tarexporter{
		is:             is,
		lss:            lss,
		rs:             rs,
		loggerImgEvent: loggerImgEvent,
	}
}

Can we just use https://github.com/containerd/containerd/blob/main/images/archive/exporter.go ?
For containerd-integration mode.