123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- package registry
- import (
- "cmp"
- "context"
- "encoding/xml"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
- "github.com/ollama/ollama/x/client/ollama"
- "github.com/ollama/ollama/x/registry/apitype"
- )
- type Client struct {
- BaseURL string
- HTTPClient *http.Client
- }
- func (c *Client) oclient() *ollama.Client {
- return (*ollama.Client)(c)
- }
- type PushParams struct {
- CompleteParts []apitype.CompletePart
- }
- // Push pushes a manifest to the server.
- func (c *Client) Push(ctx context.Context, ref string, manifest []byte, p *PushParams) ([]apitype.Requirement, error) {
- p = cmp.Or(p, &PushParams{})
- // TODO(bmizerany): backoff
- v, err := ollama.Do[apitype.PushResponse](ctx, c.oclient(), "POST", "/v1/push", &apitype.PushRequest{
- Name: ref,
- Manifest: manifest,
- CompleteParts: p.CompleteParts,
- })
- if err != nil {
- return nil, err
- }
- return v.Requirements, nil
- }
- func PushLayer(ctx context.Context, body io.ReaderAt, url string, off, n int64) (apitype.CompletePart, error) {
- var zero apitype.CompletePart
- if off < 0 {
- return zero, errors.New("off must be >0")
- }
- file := io.NewSectionReader(body, off, n)
- req, err := http.NewRequest("PUT", url, file)
- if err != nil {
- return zero, err
- }
- req.ContentLength = n
- // TODO(bmizerany): take content type param
- req.Header.Set("Content-Type", "text/plain")
- if n >= 0 {
- req.Header.Set("x-amz-copy-source-range", fmt.Sprintf("bytes=%d-%d", off, off+n-1))
- }
- res, err := http.DefaultClient.Do(req)
- if err != nil {
- return zero, err
- }
- defer res.Body.Close()
- if res.StatusCode != 200 {
- e := parseS3Error(res)
- return zero, fmt.Errorf("unexpected status code: %d; %w", res.StatusCode, e)
- }
- etag := strings.Trim(res.Header.Get("ETag"), `"`)
- cp := apitype.CompletePart{
- URL: url,
- ETag: etag,
- // TODO(bmizerany): checksum
- }
- return cp, nil
- }
- type s3Error struct {
- XMLName xml.Name `xml:"Error"`
- Code string `xml:"Code"`
- Message string `xml:"Message"`
- Resource string `xml:"Resource"`
- RequestId string `xml:"RequestId"`
- }
- func (e *s3Error) Error() string {
- return fmt.Sprintf("S3 (%s): %s: %s: %s", e.RequestId, e.Resource, e.Code, e.Message)
- }
- // parseS3Error parses an XML error response from S3.
- func parseS3Error(res *http.Response) error {
- var se *s3Error
- if err := xml.NewDecoder(res.Body).Decode(&se); err != nil {
- return err
- }
- return se
- }
|