156 lines
4.6 KiB
Go
156 lines
4.6 KiB
Go
|
package oci
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
|
||
|
"github.com/containers/image/manifest"
|
||
|
"github.com/containers/image/types"
|
||
|
)
|
||
|
|
||
|
type ociManifest struct {
|
||
|
SchemaVersion int `json:"schemaVersion"`
|
||
|
MediaType string `json:"mediaType"`
|
||
|
Config descriptor `json:"config"`
|
||
|
Layers []descriptor `json:"layers"`
|
||
|
Annotations map[string]string `json:"annotations"`
|
||
|
}
|
||
|
|
||
|
type descriptor struct {
|
||
|
Digest string `json:"digest"`
|
||
|
MediaType string `json:"mediaType"`
|
||
|
Size int64 `json:"size"`
|
||
|
}
|
||
|
|
||
|
type ociImageDestination struct {
|
||
|
ref ociReference
|
||
|
}
|
||
|
|
||
|
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||
|
func newImageDestination(ref ociReference) types.ImageDestination {
|
||
|
return &ociImageDestination{ref: ref}
|
||
|
}
|
||
|
|
||
|
// 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 *ociImageDestination) Reference() types.ImageReference {
|
||
|
return d.ref
|
||
|
}
|
||
|
|
||
|
func createManifest(m []byte) ([]byte, string, error) {
|
||
|
om := ociManifest{}
|
||
|
mt := manifest.GuessMIMEType(m)
|
||
|
switch mt {
|
||
|
case manifest.DockerV2Schema1MIMEType:
|
||
|
// There a simple reason about not yet implementing this.
|
||
|
// OCI image-spec assure about backward compatibility with docker v2s2 but not v2s1
|
||
|
// generating a v2s2 is a migration docker does when upgrading to 1.10.3
|
||
|
// and I don't think we should bother about this now (I don't want to have migration code here in skopeo)
|
||
|
return nil, "", fmt.Errorf("can't create OCI manifest from Docker V2 schema 1 manifest")
|
||
|
case manifest.DockerV2Schema2MIMEType:
|
||
|
if err := json.Unmarshal(m, &om); err != nil {
|
||
|
return nil, "", err
|
||
|
}
|
||
|
om.MediaType = manifest.OCIV1ImageManifestMIMEType
|
||
|
for i := range om.Layers {
|
||
|
om.Layers[i].MediaType = manifest.OCIV1ImageSerializationMIMEType
|
||
|
}
|
||
|
om.Config.MediaType = manifest.OCIV1ImageSerializationConfigMIMEType
|
||
|
b, err := json.Marshal(om)
|
||
|
if err != nil {
|
||
|
return nil, "", err
|
||
|
}
|
||
|
return b, om.MediaType, nil
|
||
|
case manifest.DockerV2ListMIMEType:
|
||
|
return nil, "", fmt.Errorf("can't create OCI manifest from Docker V2 schema 2 manifest list")
|
||
|
case manifest.OCIV1ImageManifestListMIMEType:
|
||
|
return nil, "", fmt.Errorf("can't create OCI manifest from OCI manifest list")
|
||
|
case manifest.OCIV1ImageManifestMIMEType:
|
||
|
return m, om.MediaType, nil
|
||
|
}
|
||
|
return nil, "", fmt.Errorf("Unrecognized manifest media type")
|
||
|
}
|
||
|
|
||
|
func (d *ociImageDestination) PutManifest(m []byte) error {
|
||
|
// TODO(mitr, runcom): this breaks signatures entirely since at this point we're creating a new manifest
|
||
|
// and signatures don't apply anymore. Will fix.
|
||
|
ociMan, mt, err := createManifest(m)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
digest, err := manifest.Digest(ociMan)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
desc := descriptor{}
|
||
|
desc.Digest = digest
|
||
|
// TODO(runcom): beaware and add support for OCI manifest list
|
||
|
desc.MediaType = mt
|
||
|
desc.Size = int64(len(ociMan))
|
||
|
data, err := json.Marshal(desc)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := ioutil.WriteFile(d.ref.blobPath(digest), ociMan, 0644); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// TODO(runcom): ugly here?
|
||
|
if err := ioutil.WriteFile(d.ref.ociLayoutPath(), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
descriptorPath := d.ref.descriptorPath(d.ref.tag)
|
||
|
if err := ensureParentDirectoryExists(descriptorPath); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return ioutil.WriteFile(descriptorPath, data, 0644)
|
||
|
}
|
||
|
|
||
|
func (d *ociImageDestination) PutBlob(digest string, stream io.Reader) error {
|
||
|
blobPath := d.ref.blobPath(digest)
|
||
|
if err := ensureParentDirectoryExists(blobPath); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
blob, err := os.Create(blobPath)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer blob.Close()
|
||
|
if _, err := io.Copy(blob, stream); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if err := blob.Sync(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ensureParentDirectoryExists ensures the parent of the supplied path exists.
|
||
|
func ensureParentDirectoryExists(path string) error {
|
||
|
parent := filepath.Dir(path)
|
||
|
if _, err := os.Stat(parent); err != nil && os.IsNotExist(err) {
|
||
|
if err := os.MkdirAll(parent, 0755); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (d *ociImageDestination) SupportedManifestMIMETypes() []string {
|
||
|
return []string{
|
||
|
manifest.OCIV1ImageManifestMIMEType,
|
||
|
manifest.DockerV2Schema2MIMEType,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
|
||
|
if len(signatures) != 0 {
|
||
|
return fmt.Errorf("Pushing signatures for OCI images is not supported")
|
||
|
}
|
||
|
return nil
|
||
|
}
|