|
|
|
@ -2,57 +2,62 @@ package image
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"encoding/json" |
|
|
|
|
"fmt" |
|
|
|
|
"io/ioutil" |
|
|
|
|
"os" |
|
|
|
|
"path" |
|
|
|
|
|
|
|
|
|
"git.wegmueller.it/opencloud/opencloud/config" |
|
|
|
|
"git.wegmueller.it/opencloud/opencloud/image/reference" |
|
|
|
|
"git.wegmueller.it/opencloud/opencloud/zfs" |
|
|
|
|
"github.com/opencontainers/go-digest" |
|
|
|
|
specsv1 "github.com/opencontainers/image-spec/specs-go/v1" |
|
|
|
|
"github.com/spf13/viper" |
|
|
|
|
"github.com/ztrue/tracerr" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
|
BlobsDirectory = "blobs" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// image.Image represents the structure a predefined Image is unpacked on disk
|
|
|
|
|
// image.Image represents the structure of a predefined Image which unpacked on disk
|
|
|
|
|
// Every Image consists of layers and those layers map 1:1 to OCI Layers
|
|
|
|
|
// The Child Dataset rootfs contains the unpacked Root filesystem
|
|
|
|
|
// The Rootfs Dataset has one snapshot for every Layer of the image.
|
|
|
|
|
// Every Snapshot thus maps to one tarfile
|
|
|
|
|
// Every Container has exactly one Image
|
|
|
|
|
// The VFS mountpoint of the dataset contains the oci_layout of the image
|
|
|
|
|
// Every Layer is a child dataset of the images root dataset.
|
|
|
|
|
// Layers are cloned from their Parent Layer in the Chain
|
|
|
|
|
// Every Tag has a Manifest describing the Hierarchy of layers in it's Chain.
|
|
|
|
|
// Every Layer is assumed to be unique and is thus represented by the SHA256 of the tar.gz content
|
|
|
|
|
// Every blob is inside the blobs directory as blob the index informs which one
|
|
|
|
|
// In the tags.json file there will be a link between the tags and the snapshots
|
|
|
|
|
// Every Image has one json describing which tag has which manifest tag 1:1 -> manifest
|
|
|
|
|
type Image struct { |
|
|
|
|
imagesDS *zfs.Dataset |
|
|
|
|
Reference reference.Reference `json:"reference"` |
|
|
|
|
Name string `json:"name"` |
|
|
|
|
Tags map[string]specsv1.Descriptor `json:"tags"` |
|
|
|
|
Reference reference.Reference `json:"reference"` |
|
|
|
|
Name string `json:"name"` |
|
|
|
|
Tags map[string]specsv1.Manifest `json:"tags"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func NewImage(ref reference.Reference, dataset *zfs.Dataset) *Image { |
|
|
|
|
// If dataset == nil assume we instantiate an empty image to create a new one
|
|
|
|
|
if dataset == nil { |
|
|
|
|
return &Image{ |
|
|
|
|
Reference: ref, |
|
|
|
|
Name: ref.GetImageName(), |
|
|
|
|
Tags: make(map[string]specsv1.Descriptor), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return &Image{ |
|
|
|
|
func NewImage(ref reference.Reference, dataset *zfs.Dataset) (*Image, error) { |
|
|
|
|
i := &Image{ |
|
|
|
|
Reference: ref, |
|
|
|
|
Name: ref.GetImageName(), |
|
|
|
|
Tags: make(map[string]specsv1.Descriptor), |
|
|
|
|
Tags: make(map[string]specsv1.Manifest), |
|
|
|
|
imagesDS: dataset, |
|
|
|
|
} |
|
|
|
|
if dataset == nil { |
|
|
|
|
var err error |
|
|
|
|
i.imagesDS, err = getImagesDataset() |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
i.imagesDS = dataset |
|
|
|
|
} |
|
|
|
|
return i, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (img *Image) GetLayers() []string { |
|
|
|
|
layers := make([]string, 0) |
|
|
|
|
for _, child := range img.imagesDS.Children { |
|
|
|
|
layers = append(layers, child.GetName()) |
|
|
|
|
func (img *Image) GetLayers() []digest.Digest { |
|
|
|
|
tagRef, ok := img.Tags[img.Reference.Tag] |
|
|
|
|
if !ok { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
layers := make([]digest.Digest, 0) |
|
|
|
|
for _, l := range tagRef.Layers { |
|
|
|
|
layers = append(layers, l.Digest) |
|
|
|
|
} |
|
|
|
|
return layers |
|
|
|
|
} |
|
|
|
@ -66,17 +71,17 @@ func (img *Image) GetLayerDS(name digest.Digest) *zfs.Dataset {
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (img *Image) HasLayer(name string) bool { |
|
|
|
|
func (img *Image) HasLayer(name digest.Digest) bool { |
|
|
|
|
for _, child := range img.imagesDS.Children { |
|
|
|
|
if child.GetName() == name { |
|
|
|
|
if child.GetName() == name.Encoded() { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (img *Image) GetManifest(digest digest.Digest) (manifest *specsv1.Manifest, err error) { |
|
|
|
|
manPath := img.imagesDS.VFSPath(BlobsDirectory, digest.Algorithm().String(), digest.Encoded()) |
|
|
|
|
func (img *Image) GetManifestFromDisk(digest digest.Digest) (manifest *specsv1.Manifest, err error) { |
|
|
|
|
manPath := img.imagesDS.VFSPath(config.BlobsDirectory, digest.Algorithm().String(), digest.Encoded()) |
|
|
|
|
manBlob, err := ioutil.ReadFile(manPath) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
@ -88,7 +93,7 @@ func (img *Image) GetManifest(digest digest.Digest) (manifest *specsv1.Manifest,
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (img *Image) GetConfig(digest digest.Digest) (cfg *specsv1.Image, err error) { |
|
|
|
|
cfgPath := img.imagesDS.VFSPath(BlobsDirectory, digest.Algorithm().String(), digest.Encoded()) |
|
|
|
|
cfgPath := img.imagesDS.VFSPath(config.BlobsDirectory, digest.Algorithm().String(), digest.Encoded()) |
|
|
|
|
cfgBlob, err := ioutil.ReadFile(cfgPath) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
@ -99,34 +104,41 @@ func (img *Image) GetConfig(digest digest.Digest) (cfg *specsv1.Image, err error
|
|
|
|
|
return cfg, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (img *Image) GetManifestDescriptor(tag string) specsv1.Descriptor { |
|
|
|
|
return img.Tags[tag] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func LoadImage(ref reference.Reference, dataset *zfs.Dataset) (*Image, error) { |
|
|
|
|
img := NewImage(ref, dataset) |
|
|
|
|
if err := img.Load(); err != nil { |
|
|
|
|
img, err := NewImage(ref, dataset) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
|
} |
|
|
|
|
if err = img.Load(); err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
|
} |
|
|
|
|
return img, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func CreateImage(ref reference.Reference) (*Image, error) { |
|
|
|
|
img := NewImage(ref, nil) |
|
|
|
|
if err := img.Create(ref); err != nil { |
|
|
|
|
func CreateImage(ref reference.Reference, dataset *zfs.Dataset) (*Image, error) { |
|
|
|
|
img, err := NewImage(ref, dataset) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
|
} |
|
|
|
|
err := img.Update(ref, os.Stdout) |
|
|
|
|
err = img.Update(ref, os.Stdout) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
|
} |
|
|
|
|
return img, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func CreateEmptyImage(ref reference.Reference) (*Image, error) { |
|
|
|
|
img := NewImage(ref, nil) |
|
|
|
|
if err := img.Create(ref); err != nil { |
|
|
|
|
func CreateEmptyImage(ref reference.Reference, dataset *zfs.Dataset) (*Image, error) { |
|
|
|
|
img, err := NewImage(ref, dataset) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, tracerr.Wrap(err) |
|
|
|
|
} |
|
|
|
|
return img, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getImagesDataset() (*zfs.Dataset, error) { |
|
|
|
|
rootPath := viper.GetString(config.HostDatasetConfigKey) |
|
|
|
|
if rootPath == "" { |
|
|
|
|
return nil, fmt.Errorf("can not get dataset: config %s is empty", config.HostDatasetConfigKey) |
|
|
|
|
} |
|
|
|
|
return zfs.OpenDataset(path.Join(rootPath, "images")) |
|
|
|
|
} |
|
|
|
|