package repocli import ( "context" "encoding/json" "errors" "fmt" "os" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( MediaTypeOIIv1 = "application/vnd.oci.image.index.v1+json" MediaTypeDDMLv2 = "application/vnd.docker.distribution.manifest.list.v2+json" MediaTypeDDMv2 = "application/vnd.docker.distribution.manifest.v2+json" MediaTypeOIMv1 = "application/vnd.oci.image.manifest.v1+json" ) type Downloader struct { cli *Client } func NewDownloader(client *Client) *Downloader { return &Downloader{ cli: client, } } func (down *Downloader) Pull(ctx context.Context, rawref, dir, osname, arch string) error { var err error ref, err := ParseReference(rawref) if err != nil { return err } rawrepo := ref.Repo() tag := ref.Tag() exist, mime, man, err := down.cli.GetManifest(ctx, rawrepo, tag) if err != nil { return err } if !exist { err = errors.New("Manifest not found") return err } var digstr string if mime == MediaTypeOIIv1 || mime == MediaTypeDDMLv2 { var index ocispec.Index err = json.Unmarshal(man, &index) if err != nil { return err } for _, descr := range index.Manifests { if descr.Platform != nil { cond := descr.Platform.Architecture == arch cond = cond && descr.Platform.OS == osname if cond { tag = descr.Digest.String() digstr = descr.Digest.String() } } } } fmt.Printf("Tag: %s\n", tag) exist, mime, man, err = down.cli.GetManifest(ctx, rawrepo, tag) if err != nil { return err } fmt.Printf("Mime: %s\n", mime) if !exist { err = errors.New("Manifest not found") return err } if mime != MediaTypeOIMv1 && mime != MediaTypeDDMv2 { err = errors.New("Unknown manifest media type") return err } var manifest ocispec.Manifest err = json.Unmarshal(man, &manifest) if err != nil { return err } // Write image manifest imager := NewEmptyImager(dir) err = imager.WriteManifest(ctx, digstr, mime, man) if err != nil { return err } layers := make([]ocispec.Descriptor, 0) layers = append(layers, manifest.Config) layers = append(layers, manifest.Layers...) for _, layer := range layers { wrapfunc := func() error { tmpfile, err := os.CreateTemp(dir, "*.bin") if err != nil { return err } defer tmpfile.Close() tmpname := tmpfile.Name() defer os.Remove(tmpname) digstr := layer.Digest.String() exist, err := down.cli.GetBlob(ctx, rawrepo, tmpfile, digstr) if err != nil { return err } if !exist { err = errors.New("Layer not found") return err } tmpfile.Seek(0, 0) size := layer.Size err = imager.WriteLayer(ctx, digstr, size, tmpfile) if err != nil { return err } return err } err = wrapfunc() if err != nil { return err } } return err }