... wip still broke
This commit is contained in:
parent
76a202c04e
commit
def4d902bf
@ -15,8 +15,8 @@ import (
|
||||
|
||||
// Common API Errors
|
||||
var (
|
||||
errUnqualifiedRef = oweb.Mistake("invalid", "name", "must be fully qualified")
|
||||
errRefNotFound = oweb.Mistake("not_found", "name", "no such model")
|
||||
errUnqualifiedRef = oweb.Invalid("invalid", "name", "must be fully qualified")
|
||||
errRefNotFound = oweb.Invalid("not_found", "name", "no such model")
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
@ -253,6 +253,17 @@ func ParseRef(s string) Ref {
|
||||
return r
|
||||
}
|
||||
|
||||
// Complete is the same as ParseRef(s).Complete().
|
||||
//
|
||||
// Future versions may be faster than calling ParseRef(s).Complete(), so if
|
||||
// need to know if a ref is complete and don't need the ref, use this
|
||||
// function.
|
||||
func Complete(s string) bool {
|
||||
// TODO(bmizerany): fast-path this with a quick scan withput
|
||||
// allocating strings
|
||||
return ParseRef(s).Complete()
|
||||
}
|
||||
|
||||
func (r Ref) Valid() bool {
|
||||
// Name is required
|
||||
if !isValidPart(r.name) {
|
||||
|
@ -92,12 +92,23 @@ type Error struct {
|
||||
|
||||
// Field is the field in the request that caused the error, if any.
|
||||
Field string `json:"field,omitempty"`
|
||||
|
||||
// Value is the value of the field that caused the error, if any.
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
var b strings.Builder
|
||||
b.WriteString("ollama: ")
|
||||
b.WriteString(e.Code)
|
||||
if e.Field != "" {
|
||||
b.WriteString(" ")
|
||||
b.WriteString(e.Field)
|
||||
}
|
||||
if e.Value != "" {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.Value)
|
||||
}
|
||||
if e.Message != "" {
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.Message)
|
||||
|
@ -21,12 +21,13 @@ func Missing(field string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func Mistake(code, field, message string) error {
|
||||
func Invalid(field, value, format string, args ...any) error {
|
||||
return &ollama.Error{
|
||||
Status: 400,
|
||||
Code: code,
|
||||
Code: "invalid",
|
||||
Field: field,
|
||||
Message: fmt.Sprintf("%s: %s", field, message),
|
||||
Value: value,
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +70,7 @@ func DecodeUserJSON[T any](field string, r io.Reader) (*T, error) {
|
||||
if errors.As(err, &se) {
|
||||
msg = fmt.Sprintf("%s (%q) is not a %s", se.Field, se.Value, se.Type)
|
||||
}
|
||||
return nil, Mistake("invalid_json", field, msg)
|
||||
return nil, Invalid("invalid_json", field, "", msg)
|
||||
}
|
||||
|
||||
func DecodeJSON[T any](r io.Reader) (*T, error) {
|
||||
|
@ -84,7 +84,7 @@ func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
ref := blob.ParseRef(pr.Ref)
|
||||
if !ref.Complete() {
|
||||
return oweb.Mistake("invalid", "name", "must be complete")
|
||||
return oweb.Invalid("name", pr.Ref, "must be complete")
|
||||
}
|
||||
|
||||
m, err := oweb.DecodeUserJSON[apitype.Manifest]("manifest", bytes.NewReader(pr.Manifest))
|
||||
@ -107,24 +107,30 @@ func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
|
||||
// Check if this is a part upload, if not, skip
|
||||
uploadID := q.Get("uploadId")
|
||||
if uploadID == "" {
|
||||
// not a part upload
|
||||
continue
|
||||
}
|
||||
partNumber, err := strconv.Atoi(q.Get("partNumber"))
|
||||
|
||||
// PartNumber is required
|
||||
queryPartNumber := q.Get("partNumber")
|
||||
partNumber, err := strconv.Atoi(queryPartNumber)
|
||||
if err != nil {
|
||||
return oweb.Mistake("invalid", "url", "invalid or missing PartNumber")
|
||||
return oweb.Invalid("partNumber", queryPartNumber, "invalid or missing PartNumber")
|
||||
}
|
||||
|
||||
// ETag is required
|
||||
if mcp.ETag == "" {
|
||||
return oweb.Mistake("invalid", "etag", "missing")
|
||||
}
|
||||
cp, ok := completePartsByUploadID[uploadID]
|
||||
if !ok {
|
||||
cp = completeParts{key: u.Path}
|
||||
completePartsByUploadID[uploadID] = cp
|
||||
return oweb.Missing("etag")
|
||||
}
|
||||
|
||||
cp := completePartsByUploadID[uploadID]
|
||||
cp.key = u.Path
|
||||
cp.parts = append(cp.parts, minio.CompletePart{
|
||||
PartNumber: partNumber,
|
||||
ETag: mcp.ETag,
|
||||
@ -136,8 +142,11 @@ func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) error {
|
||||
var zeroOpts minio.PutObjectOptions
|
||||
_, err := mcc.CompleteMultipartUpload(r.Context(), bucketTODO, cp.key, uploadID, cp.parts, zeroOpts)
|
||||
if err != nil {
|
||||
// log and continue; put backpressure on the client
|
||||
log.Printf("error completing upload: %v", err)
|
||||
var e minio.ErrorResponse
|
||||
if errors.As(err, &e) && e.Code == "NoSuchUpload" {
|
||||
return oweb.Invalid("uploadId", uploadID, "unknown uploadId")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,39 @@ import (
|
||||
"kr.dev/diff"
|
||||
)
|
||||
|
||||
// const ref = "registry.ollama.ai/x/y:latest+Z"
|
||||
// const manifest = `{
|
||||
// "layers": [
|
||||
// {"digest": "sha256-1", "size": 1},
|
||||
// {"digest": "sha256-2", "size": 2},
|
||||
// {"digest": "sha256-3", "size": 3}
|
||||
// ]
|
||||
// }`
|
||||
|
||||
// ts := newTestServer(t)
|
||||
// ts.pushNotOK(ref, `{}`, &ollama.Error{
|
||||
// Status: 400,
|
||||
// Code: "invalid",
|
||||
// Message: "name must be fully qualified",
|
||||
// })
|
||||
|
||||
// ts.push(ref, `{
|
||||
// "layers": [
|
||||
// {"digest": "sha256-1", "size": 1},
|
||||
// {"digest": "sha256-2", "size": 2},
|
||||
// {"digest": "sha256-3", "size": 3}
|
||||
// ]
|
||||
// }`)
|
||||
|
||||
type tWriter struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (w tWriter) Write(p []byte) (n int, err error) {
|
||||
w.t.Logf("%s", p)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TestPushBasic(t *testing.T) {
|
||||
const MB = 1024 * 1024
|
||||
|
||||
@ -41,6 +74,8 @@ func TestPushBasic(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
const ref = "registry.ollama.ai/x/y:latest+Z"
|
||||
|
||||
// Upload two small layers and one large layer that will
|
||||
// trigger a multipart upload.
|
||||
manifest := []byte(`{
|
||||
@ -49,9 +84,7 @@ func TestPushBasic(t *testing.T) {
|
||||
{"digest": "sha256-2", "size": 2},
|
||||
{"digest": "sha256-3", "size": 11000000}
|
||||
]
|
||||
}`)
|
||||
|
||||
const ref = "registry.ollama.ai/x/y:latest+Z"
|
||||
}`)
|
||||
|
||||
hs := httptest.NewServer(&Server{
|
||||
minioClient: mc,
|
||||
|
Loading…
x
Reference in New Issue
Block a user