package repocli import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" ocidigest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) type Imager struct { index ocispec.Index place string } func NewEmptyImager(place string) *Imager { imager := &Imager{ index: ocispec.Index{ MediaType: MediaTypeOIIv1, Manifests: make([]ocispec.Descriptor, 0), }, place: place, } imager.index.SchemaVersion = 2 return imager } func NewImagerFromPlace(place string) (*Imager, error) { var err error imager := &Imager{ index: ocispec.Index{ MediaType: MediaTypeOIIv1, Manifests: make([]ocispec.Descriptor, 0), }, place: place, } imager.index.SchemaVersion = 2 // Read and check layout version lpath := filepath.Join(imager.place, ocispec.ImageLayoutFile) lfile, err := os.OpenFile(lpath, os.O_RDONLY, 0) if err != nil { return imager, err } defer lfile.Close() buffer := bytes.NewBuffer(nil) if err != nil { return imager, err } _, err = io.Copy(buffer, lfile) if err != nil { return imager, err } layout := ocispec.ImageLayout{} err = json.Unmarshal(buffer.Bytes(), &layout) if err != nil { return imager, err } if layout.Version != ocispec.ImageLayoutVersion { err = fmt.Errorf("Unknown layout version: %s", layout.Version) return imager, err } // Read image ipath := filepath.Join(imager.place, ocispec.ImageIndexFile) ifile, err := os.OpenFile(ipath, os.O_RDONLY, 0) if err != nil { return imager, err } defer ifile.Close() buffer = bytes.NewBuffer(nil) _, err = io.Copy(buffer, ifile) if err != nil { return imager, err } err = json.Unmarshal(buffer.Bytes(), &imager.index) if err != nil { return imager, err } return imager, err } func (ima *Imager) ReadManifest(ctx context.Context, digstr string) (bool, string, []byte, error) { var exist bool var mime string var man []byte var err error digobj, err := ocidigest.Parse(digstr) if err != nil { return exist, mime, man, err } var size int64 for _, descr := range ima.index.Manifests { if digstr == descr.Digest.Encoded() { size = descr.Size mime = descr.MediaType exist = true } } subdir := string(digobj.Algorithm()) mpath := filepath.Join(ima.place, ocispec.ImageBlobsDir, subdir, digobj.Encoded()) mfile, err := os.OpenFile(mpath, os.O_RDONLY, 0) if err != nil { return exist, mime, man, err } defer mfile.Close() buffer := bytes.NewBuffer(nil) verifier := digobj.Verifier() mwriter := io.MultiWriter(verifier, buffer) readsize, err := io.Copy(mwriter, mfile) man = buffer.Bytes() if size != readsize { err = errors.New("Mismatch manigest sizes") return exist, mime, man, err } if !verifier.Verified() { err = errors.New("Mismatch manifest digests") return exist, mime, man, err } return exist, mime, man, err } func (ima *Imager) WriteManifest(ctx context.Context, digest, mime string, payload []byte) error { var err error digobj, err := ocidigest.Parse(digest) if err != nil { return err } subdir := string(digobj.Algorithm()) blobdir := filepath.Join(ima.place, ocispec.ImageBlobsDir, subdir) err = os.MkdirAll(blobdir, 0750) if err != nil { return err } mpath := filepath.Join(blobdir, digobj.Encoded()) mfile, err := os.OpenFile(mpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0640) if err != nil { return err } defer mfile.Close() reader := bytes.NewReader(payload) verifier := digobj.Verifier() mwriter := io.MultiWriter(verifier, mfile) recsize, err := io.Copy(mwriter, reader) size := int64(len(payload)) if size != recsize { err = errors.New("Mismatch manigest sizes") return err } if !verifier.Verified() { err = errors.New("Mismatch manifest digests") return err } descr := ocispec.Descriptor{ MediaType: mime, Digest: digobj, Size: size, } ima.index.Manifests = append(ima.index.Manifests, descr) return err } func (ima *Imager) WriteIndex(ctx context.Context) error { indexdat, err := json.Marshal(ima.index) if err != nil { return err } // Flush index ipath := filepath.Join(ima.place, ocispec.ImageIndexFile) ifile, err := os.OpenFile(ipath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0640) if err != nil { return err } defer ifile.Close() reader := bytes.NewReader(indexdat) recsize, err := io.Copy(ifile, reader) if err != nil { return err } if recsize != int64(len(indexdat)) { err = errors.New("Mismatch index sizes") return err } // Flush layout lpath := filepath.Join(ima.place, ocispec.ImageLayoutFile) lfile, err := os.OpenFile(lpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0640) if err != nil { return err } defer lfile.Close() layout := ocispec.ImageLayout{ Version: ocispec.ImageLayoutVersion, } layoutdat, err := json.Marshal(layout) reader = bytes.NewReader(layoutdat) if err != nil { return err } recsize, err = io.Copy(lfile, reader) if err != nil { return err } if recsize != int64(len(layoutdat)) { err = errors.New("Mismatch layout sizes") return err } return err } func (ima *Imager) WriteLayer(ctx context.Context, digest string, size int64, reader io.Reader) error { var err error digobj, err := ocidigest.Parse(digest) if err != nil { return err } subdir := string(digobj.Algorithm()) blobdir := filepath.Join(ima.place, ocispec.ImageBlobsDir, subdir) err = os.MkdirAll(blobdir, 0750) if err != nil { return err } fpath := filepath.Join(blobdir, digobj.Encoded()) file, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0640) if err != nil { return err } verifier := digobj.Verifier() mwriter := io.MultiWriter(verifier, file) recsize, err := io.Copy(mwriter, reader) if size != recsize { err = errors.New("Mismatch sizes") return err } if !verifier.Verified() { err = errors.New("Mismatch digests") return err } return err } func (ima *Imager) ReadLayer(ctx context.Context, digstr string, writer io.Writer) (bool, error) { var exist bool var err error digobj, err := ocidigest.Parse(digstr) if err != nil { return exist, err } subdir := string(digobj.Algorithm()) mpath := filepath.Join(ima.place, ocispec.ImageBlobsDir, subdir, digobj.Encoded()) stat, err := os.Stat(mpath) if err != nil { return exist, err } size := stat.Size() mfile, err := os.OpenFile(mpath, os.O_RDONLY, 0) if err != nil { return exist, err } defer mfile.Close() verifier := digobj.Verifier() mwriter := io.MultiWriter(verifier, writer) readsize, err := io.Copy(mwriter, mfile) if size != readsize { err = errors.New("Mismatch layer sizes") return exist, err } if !verifier.Verified() { err = errors.New("Mismatch layer digests") return exist, err } exist = true return exist, err }