2017-09-05 16:19:42 +00:00
|
|
|
// +build !containers_image_ostree_stub
|
|
|
|
|
2017-04-27 18:00:07 +00:00
|
|
|
package ostree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2017-11-03 17:36:13 +00:00
|
|
|
"compress/gzip"
|
|
|
|
"encoding/base64"
|
2017-04-27 18:00:07 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
2018-02-19 17:53:21 +00:00
|
|
|
"strings"
|
|
|
|
"syscall"
|
2017-07-20 20:31:51 +00:00
|
|
|
"time"
|
2018-02-19 17:53:21 +00:00
|
|
|
"unsafe"
|
2017-04-27 18:00:07 +00:00
|
|
|
|
|
|
|
"github.com/containers/image/manifest"
|
|
|
|
"github.com/containers/image/types"
|
|
|
|
"github.com/containers/storage/pkg/archive"
|
|
|
|
"github.com/opencontainers/go-digest"
|
2018-02-19 17:53:21 +00:00
|
|
|
selinux "github.com/opencontainers/selinux/go-selinux"
|
2017-07-20 20:31:51 +00:00
|
|
|
"github.com/ostreedev/ostree-go/pkg/otbuiltin"
|
2017-11-03 17:36:13 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/vbatts/tar-split/tar/asm"
|
|
|
|
"github.com/vbatts/tar-split/tar/storage"
|
2017-04-27 18:00:07 +00:00
|
|
|
)
|
|
|
|
|
2018-02-19 17:53:21 +00:00
|
|
|
// #cgo pkg-config: glib-2.0 gobject-2.0 ostree-1 libselinux
|
2017-11-03 17:36:13 +00:00
|
|
|
// #include <glib.h>
|
|
|
|
// #include <glib-object.h>
|
|
|
|
// #include <gio/gio.h>
|
|
|
|
// #include <stdlib.h>
|
|
|
|
// #include <ostree.h>
|
|
|
|
// #include <gio/ginputstream.h>
|
2018-02-19 17:53:21 +00:00
|
|
|
// #include <selinux/selinux.h>
|
|
|
|
// #include <selinux/label.h>
|
2017-11-03 17:36:13 +00:00
|
|
|
import "C"
|
|
|
|
|
2017-04-27 18:00:07 +00:00
|
|
|
type blobToImport struct {
|
|
|
|
Size int64
|
|
|
|
Digest digest.Digest
|
|
|
|
BlobPath string
|
|
|
|
}
|
|
|
|
|
|
|
|
type descriptor struct {
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
Digest digest.Digest `json:"digest"`
|
|
|
|
}
|
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
type fsLayersSchema1 struct {
|
|
|
|
BlobSum digest.Digest `json:"blobSum"`
|
|
|
|
}
|
|
|
|
|
2017-04-27 18:00:07 +00:00
|
|
|
type manifestSchema struct {
|
2017-11-03 17:36:13 +00:00
|
|
|
LayersDescriptors []descriptor `json:"layers"`
|
|
|
|
FSLayers []fsLayersSchema1 `json:"fsLayers"`
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ostreeImageDestination struct {
|
2017-11-03 17:36:13 +00:00
|
|
|
ref ostreeReference
|
|
|
|
manifest string
|
|
|
|
schema manifestSchema
|
|
|
|
tmpDirPath string
|
|
|
|
blobs map[string]*blobToImport
|
|
|
|
digest digest.Digest
|
|
|
|
signaturesLen int
|
|
|
|
repo *C.struct_OstreeRepo
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// newImageDestination returns an ImageDestination for writing to an existing ostree.
|
|
|
|
func newImageDestination(ref ostreeReference, tmpDirPath string) (types.ImageDestination, error) {
|
|
|
|
tmpDirPath = filepath.Join(tmpDirPath, ref.branchName)
|
|
|
|
if err := ensureDirectoryExists(tmpDirPath); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-11-03 17:36:13 +00:00
|
|
|
return &ostreeImageDestination{ref, "", manifestSchema{}, tmpDirPath, map[string]*blobToImport{}, "", 0, nil}, nil
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
|
|
|
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
|
|
|
|
func (d *ostreeImageDestination) Reference() types.ImageReference {
|
|
|
|
return d.ref
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close removes resources associated with an initialized ImageDestination, if any.
|
|
|
|
func (d *ostreeImageDestination) Close() error {
|
2017-11-03 17:36:13 +00:00
|
|
|
if d.repo != nil {
|
|
|
|
C.g_object_unref(C.gpointer(d.repo))
|
|
|
|
}
|
2017-04-27 18:00:07 +00:00
|
|
|
return os.RemoveAll(d.tmpDirPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ostreeImageDestination) SupportedManifestMIMETypes() []string {
|
|
|
|
return []string{
|
|
|
|
manifest.DockerV2Schema2MediaType,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
|
|
|
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
|
|
|
func (d *ostreeImageDestination) SupportsSignatures() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
|
|
|
func (d *ostreeImageDestination) ShouldCompressLayers() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
|
|
|
// uploaded to the image destination, true otherwise.
|
|
|
|
func (d *ostreeImageDestination) AcceptsForeignLayerURLs() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-07-20 20:31:51 +00:00
|
|
|
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
|
|
|
|
func (d *ostreeImageDestination) MustMatchRuntimeOS() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-04-27 18:00:07 +00:00
|
|
|
func (d *ostreeImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
|
|
|
tmpDir, err := ioutil.TempDir(d.tmpDirPath, "blob")
|
|
|
|
if err != nil {
|
|
|
|
return types.BlobInfo{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
blobPath := filepath.Join(tmpDir, "content")
|
|
|
|
blobFile, err := os.Create(blobPath)
|
|
|
|
if err != nil {
|
|
|
|
return types.BlobInfo{}, err
|
|
|
|
}
|
|
|
|
defer blobFile.Close()
|
|
|
|
|
|
|
|
digester := digest.Canonical.Digester()
|
|
|
|
tee := io.TeeReader(stream, digester.Hash())
|
|
|
|
|
|
|
|
size, err := io.Copy(blobFile, tee)
|
|
|
|
if err != nil {
|
|
|
|
return types.BlobInfo{}, err
|
|
|
|
}
|
|
|
|
computedDigest := digester.Digest()
|
|
|
|
if inputInfo.Size != -1 && size != inputInfo.Size {
|
|
|
|
return types.BlobInfo{}, errors.Errorf("Size mismatch when copying %s, expected %d, got %d", computedDigest, inputInfo.Size, size)
|
|
|
|
}
|
|
|
|
if err := blobFile.Sync(); err != nil {
|
|
|
|
return types.BlobInfo{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hash := computedDigest.Hex()
|
|
|
|
d.blobs[hash] = &blobToImport{Size: size, Digest: computedDigest, BlobPath: blobPath}
|
|
|
|
return types.BlobInfo{Digest: computedDigest, Size: size}, nil
|
|
|
|
}
|
|
|
|
|
2018-02-19 17:53:21 +00:00
|
|
|
func fixFiles(selinuxHnd *C.struct_selabel_handle, root string, dir string, usermode bool) error {
|
2017-04-27 18:00:07 +00:00
|
|
|
entries, err := ioutil.ReadDir(dir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, info := range entries {
|
|
|
|
fullpath := filepath.Join(dir, info.Name())
|
2017-05-17 17:18:35 +00:00
|
|
|
if info.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 {
|
|
|
|
if err := os.Remove(fullpath); err != nil {
|
2017-04-27 18:00:07 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-05-17 17:18:35 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-02-19 17:53:21 +00:00
|
|
|
|
|
|
|
if selinuxHnd != nil {
|
|
|
|
relPath, err := filepath.Rel(root, fullpath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Handle /exports/hostfs as a special case. Files under this directory are copied to the host,
|
|
|
|
// thus we benefit from maintaining the same SELinux label they would have on the host as we could
|
|
|
|
// use hard links instead of copying the files.
|
|
|
|
relPath = fmt.Sprintf("/%s", strings.TrimPrefix(relPath, "exports/hostfs/"))
|
|
|
|
|
|
|
|
relPathC := C.CString(relPath)
|
|
|
|
defer C.free(unsafe.Pointer(relPathC))
|
|
|
|
var context *C.char
|
|
|
|
|
|
|
|
res, err := C.selabel_lookup_raw(selinuxHnd, &context, relPathC, C.int(info.Mode()&os.ModePerm))
|
|
|
|
if int(res) < 0 && err != syscall.ENOENT {
|
|
|
|
return errors.Wrapf(err, "cannot selabel_lookup_raw %s", relPath)
|
|
|
|
}
|
|
|
|
if int(res) == 0 {
|
|
|
|
defer C.freecon(context)
|
|
|
|
fullpathC := C.CString(fullpath)
|
|
|
|
defer C.free(unsafe.Pointer(fullpathC))
|
|
|
|
res, err = C.lsetfilecon_raw(fullpathC, context)
|
|
|
|
if int(res) < 0 {
|
|
|
|
return errors.Wrapf(err, "cannot setfilecon_raw %s", fullpath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 17:18:35 +00:00
|
|
|
if info.IsDir() {
|
|
|
|
if usermode {
|
|
|
|
if err := os.Chmod(fullpath, info.Mode()|0700); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-02-19 17:53:21 +00:00
|
|
|
err = fixFiles(selinuxHnd, root, fullpath, usermode)
|
2017-04-27 18:00:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-09-13 17:01:06 +00:00
|
|
|
} else if usermode && (info.Mode().IsRegular()) {
|
2017-04-27 18:00:07 +00:00
|
|
|
if err := os.Chmod(fullpath, info.Mode()|0600); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-20 20:31:51 +00:00
|
|
|
func (d *ostreeImageDestination) ostreeCommit(repo *otbuiltin.Repo, branch string, root string, metadata []string) error {
|
|
|
|
opts := otbuiltin.NewCommitOptions()
|
|
|
|
opts.AddMetadataString = metadata
|
|
|
|
opts.Timestamp = time.Now()
|
|
|
|
// OCI layers have no parent OSTree commit
|
|
|
|
opts.Parent = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
|
|
_, err := repo.Commit(root, branch, opts)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
func generateTarSplitMetadata(output *bytes.Buffer, file string) error {
|
|
|
|
mfz := gzip.NewWriter(output)
|
|
|
|
defer mfz.Close()
|
|
|
|
metaPacker := storage.NewJSONPacker(mfz)
|
|
|
|
|
|
|
|
stream, err := os.OpenFile(file, os.O_RDONLY, 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer stream.Close()
|
|
|
|
|
2018-02-19 17:53:21 +00:00
|
|
|
gzReader, err := archive.DecompressStream(stream)
|
2017-11-03 17:36:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer gzReader.Close()
|
|
|
|
|
|
|
|
its, err := asm.NewInputTarStream(gzReader, metaPacker, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(ioutil.Discard, its)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-19 17:53:21 +00:00
|
|
|
func (d *ostreeImageDestination) importBlob(selinuxHnd *C.struct_selabel_handle, repo *otbuiltin.Repo, blob *blobToImport) error {
|
2017-04-27 18:00:07 +00:00
|
|
|
ostreeBranch := fmt.Sprintf("ociimage/%s", blob.Digest.Hex())
|
|
|
|
destinationPath := filepath.Join(d.tmpDirPath, blob.Digest.Hex(), "root")
|
|
|
|
if err := ensureDirectoryExists(destinationPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
os.Remove(blob.BlobPath)
|
|
|
|
os.RemoveAll(destinationPath)
|
|
|
|
}()
|
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
var tarSplitOutput bytes.Buffer
|
|
|
|
if err := generateTarSplitMetadata(&tarSplitOutput, blob.BlobPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-27 18:00:07 +00:00
|
|
|
if os.Getuid() == 0 {
|
|
|
|
if err := archive.UntarPath(blob.BlobPath, destinationPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-02-19 17:53:21 +00:00
|
|
|
if err := fixFiles(selinuxHnd, destinationPath, destinationPath, false); err != nil {
|
2017-05-17 17:18:35 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-04-27 18:00:07 +00:00
|
|
|
} else {
|
|
|
|
os.MkdirAll(destinationPath, 0755)
|
|
|
|
if err := exec.Command("tar", "-C", destinationPath, "--no-same-owner", "--no-same-permissions", "--delay-directory-restore", "-xf", blob.BlobPath).Run(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-02-19 17:53:21 +00:00
|
|
|
if err := fixFiles(selinuxHnd, destinationPath, destinationPath, true); err != nil {
|
2017-04-27 18:00:07 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-11-03 17:36:13 +00:00
|
|
|
return d.ostreeCommit(repo, ostreeBranch, destinationPath, []string{fmt.Sprintf("docker.size=%d", blob.Size),
|
|
|
|
fmt.Sprintf("tarsplit.output=%s", base64.StdEncoding.EncodeToString(tarSplitOutput.Bytes()))})
|
|
|
|
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
func (d *ostreeImageDestination) importConfig(repo *otbuiltin.Repo, blob *blobToImport) error {
|
2017-04-27 18:00:07 +00:00
|
|
|
ostreeBranch := fmt.Sprintf("ociimage/%s", blob.Digest.Hex())
|
2017-11-03 17:36:13 +00:00
|
|
|
destinationPath := filepath.Dir(blob.BlobPath)
|
2017-04-27 18:00:07 +00:00
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
return d.ostreeCommit(repo, ostreeBranch, destinationPath, []string{fmt.Sprintf("docker.size=%d", blob.Size)})
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ostreeImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
2017-11-03 17:36:13 +00:00
|
|
|
|
|
|
|
if d.repo == nil {
|
|
|
|
repo, err := openRepo(d.ref.repo)
|
|
|
|
if err != nil {
|
|
|
|
return false, 0, err
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
2017-11-03 17:36:13 +00:00
|
|
|
d.repo = repo
|
|
|
|
}
|
|
|
|
branch := fmt.Sprintf("ociimage/%s", info.Digest.Hex())
|
|
|
|
|
|
|
|
found, data, err := readMetadata(d.repo, branch, "docker.size")
|
|
|
|
if err != nil || !found {
|
|
|
|
return found, -1, err
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
2017-11-03 17:36:13 +00:00
|
|
|
|
|
|
|
size, err := strconv.ParseInt(data, 10, 64)
|
2017-04-27 18:00:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, -1, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, size, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ostreeImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
2017-05-17 17:18:35 +00:00
|
|
|
// PutManifest writes manifest to the destination.
|
|
|
|
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
|
|
|
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
|
|
|
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
2017-10-10 14:11:06 +00:00
|
|
|
func (d *ostreeImageDestination) PutManifest(manifestBlob []byte) error {
|
|
|
|
d.manifest = string(manifestBlob)
|
2017-04-27 18:00:07 +00:00
|
|
|
|
2017-10-10 14:11:06 +00:00
|
|
|
if err := json.Unmarshal(manifestBlob, &d.schema); err != nil {
|
2017-04-27 18:00:07 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestPath := filepath.Join(d.tmpDirPath, d.ref.manifestPath())
|
|
|
|
if err := ensureParentDirectoryExists(manifestPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-10-10 14:11:06 +00:00
|
|
|
digest, err := manifest.Digest(manifestBlob)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.digest = digest
|
|
|
|
|
|
|
|
return ioutil.WriteFile(manifestPath, manifestBlob, 0644)
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ostreeImageDestination) PutSignatures(signatures [][]byte) error {
|
|
|
|
path := filepath.Join(d.tmpDirPath, d.ref.signaturePath(0))
|
|
|
|
if err := ensureParentDirectoryExists(path); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, sig := range signatures {
|
|
|
|
signaturePath := filepath.Join(d.tmpDirPath, d.ref.signaturePath(i))
|
|
|
|
if err := ioutil.WriteFile(signaturePath, sig, 0644); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-11-03 17:36:13 +00:00
|
|
|
d.signaturesLen = len(signatures)
|
2017-04-27 18:00:07 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ostreeImageDestination) Commit() error {
|
2017-07-20 20:31:51 +00:00
|
|
|
repo, err := otbuiltin.OpenRepo(d.ref.repo)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = repo.PrepareTransaction()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-02-19 17:53:21 +00:00
|
|
|
var selinuxHnd *C.struct_selabel_handle
|
|
|
|
|
|
|
|
if os.Getuid() == 0 && selinux.GetEnabled() {
|
|
|
|
selinuxHnd, err = C.selabel_open(C.SELABEL_CTX_FILE, nil, 0)
|
|
|
|
if selinuxHnd == nil {
|
|
|
|
return errors.Wrapf(err, "cannot open the SELinux DB")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer C.selabel_close(selinuxHnd)
|
|
|
|
}
|
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
checkLayer := func(hash string) error {
|
2017-04-27 18:00:07 +00:00
|
|
|
blob := d.blobs[hash]
|
|
|
|
// if the blob is not present in d.blobs then it is already stored in OSTree,
|
|
|
|
// and we don't need to import it.
|
|
|
|
if blob == nil {
|
2017-11-03 17:36:13 +00:00
|
|
|
return nil
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
2018-02-19 17:53:21 +00:00
|
|
|
err := d.importBlob(selinuxHnd, repo, blob)
|
2017-04-27 18:00:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-11-03 17:36:13 +00:00
|
|
|
|
|
|
|
delete(d.blobs, hash)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, layer := range d.schema.LayersDescriptors {
|
|
|
|
hash := layer.Digest.Hex()
|
|
|
|
if err = checkLayer(hash); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, layer := range d.schema.FSLayers {
|
|
|
|
hash := layer.BlobSum.Hex()
|
|
|
|
if err = checkLayer(hash); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-04-27 18:00:07 +00:00
|
|
|
}
|
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
// Import the other blobs that are not layers
|
|
|
|
for _, blob := range d.blobs {
|
|
|
|
err := d.importConfig(repo, blob)
|
2017-04-27 18:00:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestPath := filepath.Join(d.tmpDirPath, "manifest")
|
2017-07-20 20:31:51 +00:00
|
|
|
|
2017-11-03 17:36:13 +00:00
|
|
|
metadata := []string{fmt.Sprintf("docker.manifest=%s", string(d.manifest)),
|
|
|
|
fmt.Sprintf("signatures=%d", d.signaturesLen),
|
|
|
|
fmt.Sprintf("docker.digest=%s", string(d.digest))}
|
2017-07-20 20:31:51 +00:00
|
|
|
err = d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata)
|
|
|
|
|
|
|
|
_, err = repo.CommitTransaction()
|
2017-04-27 18:00:07 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func ensureDirectoryExists(path string) error {
|
|
|
|
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
|
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ensureParentDirectoryExists(path string) error {
|
|
|
|
return ensureDirectoryExists(filepath.Dir(path))
|
|
|
|
}
|