2023-07-17 00:02:22 +00:00
package server
import (
"bytes"
2023-07-25 21:08:51 +00:00
"context"
2023-07-17 00:02:22 +00:00
"crypto/sha256"
2023-08-29 03:50:24 +00:00
"encoding/hex"
2023-07-17 00:02:22 +00:00
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
2023-08-22 01:38:31 +00:00
"net/url"
2023-07-17 00:02:22 +00:00
"os"
"path/filepath"
2023-08-22 01:24:42 +00:00
"runtime"
2023-07-17 00:02:22 +00:00
"strconv"
"strings"
2023-09-01 22:12:20 +00:00
"text/template"
2023-07-17 00:02:22 +00:00
2023-11-29 19:11:42 +00:00
"golang.org/x/exp/slices"
2023-07-17 00:02:22 +00:00
"github.com/jmorganca/ollama/api"
2023-07-21 20:33:56 +00:00
"github.com/jmorganca/ollama/llm"
2023-07-17 00:02:22 +00:00
"github.com/jmorganca/ollama/parser"
2023-08-22 01:24:42 +00:00
"github.com/jmorganca/ollama/version"
2023-07-17 00:02:22 +00:00
)
2023-07-21 22:42:19 +00:00
type RegistryOptions struct {
Insecure bool
Username string
Password string
2023-08-10 18:34:25 +00:00
Token string
2023-07-21 22:42:19 +00:00
}
2023-07-17 00:02:22 +00:00
type Model struct {
2023-11-30 18:30:23 +00:00
Name string ` json:"name" `
2023-12-01 19:37:17 +00:00
Config ConfigV2
2023-11-30 18:30:23 +00:00
ShortName string
ModelPath string
OriginalModel string
AdapterPaths [ ] string
ProjectorPaths [ ] string
Template string
System string
License [ ] string
Digest string
2023-12-11 21:56:22 +00:00
Size int64
2023-11-30 18:30:23 +00:00
Options map [ string ] interface { }
2023-07-17 00:02:22 +00:00
}
2023-12-05 19:57:33 +00:00
type PromptVars struct {
System string
Prompt string
Response string
First bool
}
2023-08-08 04:55:34 +00:00
2023-12-05 19:57:33 +00:00
func ( m * Model ) Prompt ( p PromptVars ) ( string , error ) {
var prompt strings . Builder
2023-11-22 19:46:49 +00:00
// Use the "missingkey=zero" option to handle missing variables without panicking
tmpl , err := template . New ( "" ) . Option ( "missingkey=zero" ) . Parse ( m . Template )
2023-07-17 21:21:27 +00:00
if err != nil {
return "" , err
}
2023-12-08 19:20:19 +00:00
if p . System == "" {
2023-12-12 19:43:19 +00:00
// use the default system message for this model if one is not specified
2023-12-08 19:20:19 +00:00
p . System = m . System
}
2023-11-22 19:46:49 +00:00
vars := map [ string ] any {
"System" : p . System ,
"Prompt" : p . Prompt ,
"Response" : p . Response ,
"First" : p . First ,
2023-07-17 21:21:27 +00:00
}
2023-12-05 19:57:33 +00:00
var sb strings . Builder
2023-11-22 19:46:49 +00:00
if err := tmpl . Execute ( & sb , vars ) ; err != nil {
2023-12-05 19:57:33 +00:00
return "" , err
}
prompt . WriteString ( sb . String ( ) )
prompt . WriteString ( p . Response )
return prompt . String ( ) , nil
}
2023-07-17 21:21:27 +00:00
2023-12-12 21:28:58 +00:00
func ( m * Model ) ChatPrompt ( msgs [ ] api . Message ) ( string , [ ] api . ImageData , error ) {
2023-12-05 19:57:33 +00:00
// build the prompt from the list of messages
var prompt strings . Builder
2023-12-12 21:28:58 +00:00
var currentImages [ ] api . ImageData
2023-12-05 19:57:33 +00:00
currentVars := PromptVars {
First : true ,
2023-12-04 23:01:06 +00:00
}
2023-12-05 19:57:33 +00:00
writePrompt := func ( ) error {
p , err := m . Prompt ( currentVars )
if err != nil {
return err
}
prompt . WriteString ( p )
currentVars = PromptVars { }
return nil
}
for _ , msg := range msgs {
2023-12-08 21:44:24 +00:00
switch strings . ToLower ( msg . Role ) {
2023-12-05 19:57:33 +00:00
case "system" :
2023-12-08 21:44:24 +00:00
if currentVars . System != "" {
2023-12-05 19:57:33 +00:00
if err := writePrompt ( ) ; err != nil {
2023-12-12 21:28:58 +00:00
return "" , nil , err
2023-12-05 19:57:33 +00:00
}
}
currentVars . System = msg . Content
case "user" :
2023-12-08 21:44:24 +00:00
if currentVars . Prompt != "" {
2023-12-05 19:57:33 +00:00
if err := writePrompt ( ) ; err != nil {
2023-12-12 21:28:58 +00:00
return "" , nil , err
2023-12-05 19:57:33 +00:00
}
}
currentVars . Prompt = msg . Content
2023-12-12 21:28:58 +00:00
currentImages = msg . Images
2023-12-05 19:57:33 +00:00
case "assistant" :
currentVars . Response = msg . Content
if err := writePrompt ( ) ; err != nil {
2023-12-12 21:28:58 +00:00
return "" , nil , err
2023-12-05 19:57:33 +00:00
}
default :
2023-12-12 21:28:58 +00:00
return "" , nil , fmt . Errorf ( "invalid role: %s, role must be one of [system, user, assistant]" , msg . Role )
2023-12-05 19:57:33 +00:00
}
2023-07-17 21:21:27 +00:00
}
2023-12-05 19:57:33 +00:00
// Append the last set of vars if they are non-empty
if currentVars . Prompt != "" || currentVars . System != "" {
if err := writePrompt ( ) ; err != nil {
2023-12-12 21:28:58 +00:00
return "" , nil , err
2023-12-05 19:57:33 +00:00
}
}
2023-12-12 21:28:58 +00:00
return prompt . String ( ) , currentImages , nil
2023-07-17 21:21:27 +00:00
}
2023-07-17 00:02:22 +00:00
type ManifestV2 struct {
SchemaVersion int ` json:"schemaVersion" `
MediaType string ` json:"mediaType" `
2023-11-22 21:28:49 +00:00
Config * Layer ` json:"config" `
2023-07-17 00:02:22 +00:00
Layers [ ] * Layer ` json:"layers" `
}
type ConfigV2 struct {
2023-12-09 10:05:43 +00:00
ModelFormat string ` json:"model_format" `
ModelFamily string ` json:"model_family" `
ModelFamilies [ ] string ` json:"model_families" `
ModelType string ` json:"model_type" `
FileType string ` json:"file_type" `
2023-07-21 20:33:56 +00:00
// required by spec
2023-07-17 00:02:22 +00:00
Architecture string ` json:"architecture" `
OS string ` json:"os" `
2023-12-01 19:37:17 +00:00
RootFS RootFS ` json:"rootfs" `
2023-07-17 00:02:22 +00:00
}
2023-11-29 19:11:42 +00:00
func ( c * ConfigV2 ) SetModelFormat ( format string ) {
if c . ModelFormat == "" {
c . ModelFormat = format
}
}
func ( c * ConfigV2 ) SetModelFamily ( families ... string ) {
for _ , family := range families {
if c . ModelFamily == "" {
c . ModelFamily = family
}
if ! slices . Contains ( c . ModelFamilies , family ) {
c . ModelFamilies = append ( c . ModelFamilies , family )
}
}
}
func ( c * ConfigV2 ) SetModelType ( modelType string ) {
if c . ModelType == "" {
c . ModelType = modelType
}
}
func ( c * ConfigV2 ) SetFileType ( fileType string ) {
if c . FileType == "" {
c . FileType = fileType
}
}
2023-07-17 00:02:22 +00:00
type RootFS struct {
Type string ` json:"type" `
DiffIDs [ ] string ` json:"diff_ids" `
}
2023-09-28 17:00:34 +00:00
func ( m * ManifestV2 ) GetTotalSize ( ) ( total int64 ) {
2023-07-18 16:09:45 +00:00
for _ , layer := range m . Layers {
total += layer . Size
}
2023-09-28 17:00:34 +00:00
2023-07-18 16:09:45 +00:00
total += m . Config . Size
return total
}
2023-08-29 03:50:24 +00:00
func GetManifest ( mp ModelPath ) ( * ManifestV2 , string , error ) {
2023-10-27 14:19:59 +00:00
fp , err := mp . GetManifestPath ( )
2023-07-17 18:03:55 +00:00
if err != nil {
2023-08-29 03:50:24 +00:00
return nil , "" , err
2023-07-17 18:03:55 +00:00
}
2023-07-17 21:21:27 +00:00
2023-07-22 06:02:12 +00:00
if _ , err = os . Stat ( fp ) ; err != nil {
2023-08-29 03:50:24 +00:00
return nil , "" , err
2023-07-17 00:02:22 +00:00
}
var manifest * ManifestV2
2023-07-17 21:21:27 +00:00
bts , err := os . ReadFile ( fp )
2023-07-17 00:02:22 +00:00
if err != nil {
2023-08-29 03:50:24 +00:00
return nil , "" , fmt . Errorf ( "couldn't open file '%s'" , fp )
2023-07-17 00:02:22 +00:00
}
2023-08-29 03:50:24 +00:00
shaSum := sha256 . Sum256 ( bts )
shaStr := hex . EncodeToString ( shaSum [ : ] )
2023-07-17 21:21:27 +00:00
if err := json . Unmarshal ( bts , & manifest ) ; err != nil {
2023-08-29 03:50:24 +00:00
return nil , "" , err
2023-07-17 00:02:22 +00:00
}
2023-08-29 03:50:24 +00:00
return manifest , shaStr , nil
2023-07-17 00:02:22 +00:00
}
func GetModel ( name string ) ( * Model , error ) {
2023-08-22 16:39:42 +00:00
mp := ParseModelPath ( name )
2023-08-29 03:50:24 +00:00
manifest , digest , err := GetManifest ( mp )
2023-07-17 00:02:22 +00:00
if err != nil {
return nil , err
}
model := & Model {
2023-10-19 14:39:58 +00:00
Name : mp . GetFullTagname ( ) ,
ShortName : mp . GetShortTagname ( ) ,
Digest : digest ,
Template : "{{ .Prompt }}" ,
License : [ ] string { } ,
2023-12-11 21:56:22 +00:00
Size : manifest . GetTotalSize ( ) ,
2023-07-17 00:02:22 +00:00
}
2023-12-01 19:37:17 +00:00
filename , err := GetBlobsPath ( manifest . Config . Digest )
if err != nil {
return nil , err
}
configFile , err := os . Open ( filename )
if err != nil {
return nil , err
}
defer configFile . Close ( )
if err := json . NewDecoder ( configFile ) . Decode ( & model . Config ) ; err != nil {
return nil , err
}
2023-07-17 00:02:22 +00:00
for _ , layer := range manifest . Layers {
2023-07-18 05:44:21 +00:00
filename , err := GetBlobsPath ( layer . Digest )
2023-07-17 18:03:55 +00:00
if err != nil {
return nil , err
}
2023-07-17 00:02:22 +00:00
switch layer . MediaType {
case "application/vnd.ollama.image.model" :
model . ModelPath = filename
2023-09-06 18:04:17 +00:00
model . OriginalModel = layer . From
2023-08-04 22:56:40 +00:00
case "application/vnd.ollama.image.embed" :
2023-10-16 15:07:37 +00:00
// Deprecated in versions > 0.1.2
// TODO: remove this warning in a future version
log . Print ( "WARNING: model contains embeddings, but embeddings in modelfiles have been deprecated and will be ignored." )
2023-08-04 00:16:05 +00:00
case "application/vnd.ollama.image.adapter" :
model . AdapterPaths = append ( model . AdapterPaths , filename )
2023-11-30 18:30:23 +00:00
case "application/vnd.ollama.image.projector" :
model . ProjectorPaths = append ( model . ProjectorPaths , filename )
2023-07-17 21:21:27 +00:00
case "application/vnd.ollama.image.template" :
bts , err := os . ReadFile ( filename )
if err != nil {
return nil , err
}
model . Template = string ( bts )
case "application/vnd.ollama.image.system" :
bts , err := os . ReadFile ( filename )
2023-07-17 00:02:22 +00:00
if err != nil {
return nil , err
}
2023-07-17 21:21:27 +00:00
model . System = string ( bts )
2023-07-20 02:43:00 +00:00
case "application/vnd.ollama.image.prompt" :
bts , err := os . ReadFile ( filename )
if err != nil {
return nil , err
}
model . Template = string ( bts )
2023-07-17 00:02:22 +00:00
case "application/vnd.ollama.image.params" :
2023-07-17 19:08:10 +00:00
params , err := os . Open ( filename )
if err != nil {
return nil , err
}
defer params . Close ( )
2023-07-17 00:02:22 +00:00
2023-07-31 19:07:04 +00:00
// parse model options parameters into a map so that we can see which fields have been specified explicitly
2023-08-01 17:36:31 +00:00
if err = json . NewDecoder ( params ) . Decode ( & model . Options ) ; err != nil {
2023-07-31 19:07:04 +00:00
return nil , err
}
2023-09-06 18:04:17 +00:00
case "application/vnd.ollama.image.license" :
bts , err := os . ReadFile ( filename )
if err != nil {
return nil , err
}
model . License = append ( model . License , string ( bts ) )
2023-07-17 00:02:22 +00:00
}
}
return model , nil
}
2023-11-21 20:43:17 +00:00
func realpath ( mfDir , from string ) string {
abspath , err := filepath . Abs ( from )
2023-11-14 20:30:34 +00:00
if err != nil {
2023-11-21 20:43:17 +00:00
return from
2023-09-11 18:46:35 +00:00
}
2023-11-14 20:30:34 +00:00
home , err := os . UserHomeDir ( )
2023-07-20 04:55:15 +00:00
if err != nil {
2023-11-14 20:30:34 +00:00
return abspath
2023-07-17 00:02:22 +00:00
}
2023-11-21 20:43:17 +00:00
if from == "~" {
2023-11-14 20:30:34 +00:00
return home
2023-11-21 20:43:17 +00:00
} else if strings . HasPrefix ( from , "~/" ) {
return filepath . Join ( home , from [ 2 : ] )
}
if _ , err := os . Stat ( filepath . Join ( mfDir , from ) ) ; err == nil {
// this is a file relative to the Modelfile
return filepath . Join ( mfDir , from )
2023-07-17 00:02:22 +00:00
}
2023-11-14 20:30:34 +00:00
return abspath
}
2023-11-21 20:43:17 +00:00
func CreateModel ( ctx context . Context , name , modelFileDir string , commands [ ] parser . Command , fn func ( resp api . ProgressResponse ) ) error {
2023-07-21 20:33:56 +00:00
config := ConfigV2 {
OS : "linux" ,
2023-11-14 20:30:34 +00:00
Architecture : "amd64" ,
2023-11-22 21:28:49 +00:00
RootFS : RootFS {
Type : "layers" ,
} ,
2023-07-21 20:33:56 +00:00
}
2023-11-14 20:30:34 +00:00
deleteMap := make ( map [ string ] struct { } )
2023-11-22 21:28:49 +00:00
var layers Layers
2023-11-14 20:30:34 +00:00
2023-07-28 15:29:00 +00:00
params := make ( map [ string ] [ ] string )
2023-11-14 20:30:34 +00:00
fromParams := make ( map [ string ] any )
2023-07-17 00:02:22 +00:00
for _ , c := range commands {
2023-11-14 20:30:34 +00:00
log . Printf ( "[%s] - %s" , c . Name , c . Args )
mediatype := fmt . Sprintf ( "application/vnd.ollama.image.%s" , c . Name )
2023-07-17 00:02:22 +00:00
switch c . Name {
case "model" :
2023-11-15 18:59:38 +00:00
if strings . HasPrefix ( c . Args , "@" ) {
blobPath , err := GetBlobsPath ( strings . TrimPrefix ( c . Args , "@" ) )
if err != nil {
return err
}
c . Args = blobPath
}
2023-11-21 20:43:17 +00:00
bin , err := os . Open ( realpath ( modelFileDir , c . Args ) )
2023-07-17 00:02:22 +00:00
if err != nil {
2023-11-14 20:30:34 +00:00
// not a file on disk so must be a model reference
modelpath := ParseModelPath ( c . Args )
manifest , _ , err := GetManifest ( modelpath )
switch {
case errors . Is ( err , os . ErrNotExist ) :
fn ( api . ProgressResponse { Status : "pulling model" } )
if err := PullModel ( ctx , c . Args , & RegistryOptions { } , fn ) ; err != nil {
2023-07-25 18:25:13 +00:00
return err
}
2023-11-14 20:30:34 +00:00
manifest , _ , err = GetManifest ( modelpath )
2023-07-21 20:33:56 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
case err != nil :
return err
2023-07-17 00:02:22 +00:00
}
2023-07-21 20:33:56 +00:00
2023-10-06 20:05:32 +00:00
fn ( api . ProgressResponse { Status : "reading model metadata" } )
2023-11-14 20:30:34 +00:00
fromConfigPath , err := GetBlobsPath ( manifest . Config . Digest )
2023-08-18 04:52:11 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
fromConfigFile , err := os . Open ( fromConfigPath )
2023-08-18 04:52:11 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
defer fromConfigFile . Close ( )
2023-08-18 04:52:11 +00:00
2023-11-14 20:30:34 +00:00
var fromConfig ConfigV2
if err := json . NewDecoder ( fromConfigFile ) . Decode ( & fromConfig ) ; err != nil {
2023-08-18 04:52:11 +00:00
return err
}
2023-11-29 19:11:42 +00:00
config . SetModelFormat ( fromConfig . ModelFormat )
config . SetModelFamily ( append ( fromConfig . ModelFamilies , fromConfig . ModelFamily ) ... )
config . SetModelType ( fromConfig . ModelType )
config . SetFileType ( fromConfig . FileType )
2023-08-18 04:52:11 +00:00
2023-11-14 20:30:34 +00:00
for _ , layer := range manifest . Layers {
deleteMap [ layer . Digest ] = struct { } { }
if layer . MediaType == "application/vnd.ollama.image.params" {
fromParamsPath , err := GetBlobsPath ( layer . Digest )
2023-09-05 18:05:03 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
fromParamsFile , err := os . Open ( fromParamsPath )
2023-09-05 18:05:03 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
defer fromParamsFile . Close ( )
2023-09-05 18:05:03 +00:00
2023-11-14 20:30:34 +00:00
if err := json . NewDecoder ( fromParamsFile ) . Decode ( & fromParams ) ; err != nil {
2023-09-05 18:05:03 +00:00
return err
}
}
2023-11-22 21:28:49 +00:00
layer , err := NewLayerFromLayer ( layer . Digest , layer . MediaType , modelpath . GetShortTagname ( ) )
2023-07-17 00:02:22 +00:00
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
2023-11-22 21:28:49 +00:00
layers . Add ( layer )
2023-07-17 00:02:22 +00:00
}
2023-11-14 20:30:34 +00:00
deleteMap [ manifest . Config . Digest ] = struct { } { }
continue
2023-07-17 00:02:22 +00:00
}
2023-11-14 20:30:34 +00:00
defer bin . Close ( )
2023-08-04 00:16:05 +00:00
2023-11-24 19:57:20 +00:00
var offset int64
for {
fn ( api . ProgressResponse { Status : "creating model layer" } )
2023-08-04 00:16:05 +00:00
2023-11-24 19:57:20 +00:00
bin . Seek ( offset , io . SeekStart )
ggml , err := llm . DecodeGGML ( bin )
if errors . Is ( err , io . EOF ) {
break
} else if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
2023-11-29 19:11:42 +00:00
config . SetModelFormat ( ggml . Name ( ) )
config . SetModelFamily ( ggml . ModelFamily ( ) )
config . SetModelType ( ggml . ModelType ( ) )
config . SetFileType ( ggml . FileType ( ) )
2023-08-04 00:16:05 +00:00
2023-11-24 19:57:20 +00:00
mediatype := mediatype
if ggml . ModelFamily ( ) == "clip" {
mediatype = "application/vnd.ollama.image.projector"
}
2023-08-04 00:16:05 +00:00
2023-11-24 19:57:20 +00:00
sr := io . NewSectionReader ( bin , offset , ggml . Size )
layer , err := NewLayer ( sr , mediatype )
if err != nil {
return err
}
layers . Add ( layer )
offset += ggml . Size
}
2023-11-14 20:30:34 +00:00
case "adapter" :
2023-12-01 18:50:55 +00:00
if strings . HasPrefix ( c . Args , "@" ) {
blobPath , err := GetBlobsPath ( strings . TrimPrefix ( c . Args , "@" ) )
if err != nil {
return err
}
c . Args = blobPath
}
2023-12-05 19:57:33 +00:00
2023-11-14 20:30:34 +00:00
fn ( api . ProgressResponse { Status : "creating adapter layer" } )
2023-11-21 20:43:17 +00:00
bin , err := os . Open ( realpath ( modelFileDir , c . Args ) )
2023-08-04 00:16:05 +00:00
if err != nil {
2023-11-14 20:30:34 +00:00
return err
2023-08-04 00:16:05 +00:00
}
2023-11-14 20:30:34 +00:00
defer bin . Close ( )
2023-08-04 00:16:05 +00:00
2023-11-22 21:28:49 +00:00
layer , err := NewLayer ( bin , mediatype )
2023-08-04 00:16:05 +00:00
if err != nil {
2023-11-14 20:30:34 +00:00
return err
2023-08-04 00:16:05 +00:00
}
2023-08-08 20:56:48 +00:00
2023-11-22 21:28:49 +00:00
layers . Add ( layer )
2023-11-14 20:30:34 +00:00
case "license" :
fn ( api . ProgressResponse { Status : "creating license layer" } )
2023-11-22 21:28:49 +00:00
bin := strings . NewReader ( c . Args )
layer , err := NewLayer ( bin , mediatype )
2023-08-08 20:56:48 +00:00
if err != nil {
return err
}
2023-11-22 21:28:49 +00:00
layers . Add ( layer )
2023-11-14 20:30:34 +00:00
case "template" , "system" :
fn ( api . ProgressResponse { Status : fmt . Sprintf ( "creating %s layer" , c . Name ) } )
2023-11-22 21:28:49 +00:00
bin := strings . NewReader ( c . Args )
layer , err := NewLayer ( bin , mediatype )
2023-07-17 00:02:22 +00:00
if err != nil {
2023-07-17 21:21:27 +00:00
return err
2023-07-17 00:02:22 +00:00
}
2023-07-17 21:21:27 +00:00
2023-11-22 21:28:49 +00:00
layers . Replace ( layer )
2023-07-17 00:02:22 +00:00
default :
2023-07-28 15:29:00 +00:00
params [ c . Name ] = append ( params [ c . Name ] , c . Args )
2023-07-17 00:02:22 +00:00
}
}
2023-07-17 19:08:10 +00:00
if len ( params ) > 0 {
2023-11-14 20:30:34 +00:00
fn ( api . ProgressResponse { Status : "creating parameters layer" } )
2023-09-02 18:38:51 +00:00
2023-11-29 17:56:42 +00:00
formattedParams , err := api . FormatParams ( params )
2023-07-17 00:02:22 +00:00
if err != nil {
2023-11-14 20:30:34 +00:00
return err
2023-07-17 00:02:22 +00:00
}
2023-08-04 22:56:40 +00:00
2023-11-14 20:30:34 +00:00
for k , v := range fromParams {
2023-09-05 18:05:03 +00:00
if _ , ok := formattedParams [ k ] ; ! ok {
formattedParams [ k ] = v
}
}
2023-12-11 21:56:22 +00:00
// xxx - can this be removed?
2023-09-12 17:52:57 +00:00
if config . ModelType == "65B" {
2023-11-14 20:30:34 +00:00
if gqa , ok := formattedParams [ "gqa" ] . ( int ) ; ok && gqa == 8 {
2023-09-12 17:52:57 +00:00
config . ModelType = "70B"
}
}
2023-11-14 20:30:34 +00:00
var b bytes . Buffer
if err := json . NewEncoder ( & b ) . Encode ( formattedParams ) ; err != nil {
2023-08-04 22:56:40 +00:00
return err
}
2023-11-14 20:30:34 +00:00
fn ( api . ProgressResponse { Status : "creating config layer" } )
2023-11-22 21:28:49 +00:00
layer , err := NewLayer ( & b , "application/vnd.ollama.image.params" )
2023-07-17 00:02:22 +00:00
if err != nil {
2023-11-14 20:30:34 +00:00
return err
2023-07-17 00:02:22 +00:00
}
2023-11-14 20:30:34 +00:00
2023-11-22 21:28:49 +00:00
layers . Replace ( layer )
2023-08-04 22:56:40 +00:00
}
2023-11-22 21:28:49 +00:00
digests := make ( [ ] string , len ( layers . items ) )
for i , layer := range layers . items {
digests [ i ] = layer . Digest
2023-07-17 00:02:22 +00:00
}
2023-11-22 21:28:49 +00:00
config . RootFS . DiffIDs = digests
2023-11-14 20:30:34 +00:00
2023-11-22 21:28:49 +00:00
var b bytes . Buffer
if err := json . NewEncoder ( & b ) . Encode ( config ) ; err != nil {
2023-07-17 00:02:22 +00:00
return err
}
2023-11-22 21:28:49 +00:00
configLayer , err := NewLayer ( & b , "application/vnd.docker.container.image.v1+json" )
if err != nil {
2023-07-17 00:02:22 +00:00
return err
}
2023-11-22 21:28:49 +00:00
delete ( deleteMap , configLayer . Digest )
2023-07-17 00:02:22 +00:00
2023-11-22 21:28:49 +00:00
for _ , layer := range append ( layers . items , configLayer ) {
committed , err := layer . Commit ( )
2023-07-17 18:03:55 +00:00
if err != nil {
return err
}
2023-07-17 00:02:22 +00:00
2023-11-22 21:28:49 +00:00
status := "writing layer"
if ! committed {
status = "using already created layer"
2023-07-17 00:02:22 +00:00
}
2023-11-22 21:28:49 +00:00
fn ( api . ProgressResponse { Status : fmt . Sprintf ( "%s %s" , status , layer . Digest ) } )
2023-07-17 00:02:22 +00:00
2023-11-22 21:28:49 +00:00
delete ( deleteMap , layer . Digest )
2023-07-17 00:02:22 +00:00
}
2023-11-22 21:28:49 +00:00
fn ( api . ProgressResponse { Status : "writing manifest" } )
if err := WriteManifest ( name , configLayer , layers . items ) ; err != nil {
2023-10-27 14:19:59 +00:00
return err
}
2023-07-17 00:02:22 +00:00
2023-11-22 21:28:49 +00:00
if noprune := os . Getenv ( "OLLAMA_NOPRUNE" ) ; noprune == "" {
if err := deleteUnusedLayers ( nil , deleteMap , false ) ; err != nil {
return err
2023-07-17 00:02:22 +00:00
}
}
2023-11-22 21:28:49 +00:00
fn ( api . ProgressResponse { Status : "success" } )
return nil
2023-07-17 00:02:22 +00:00
}
2023-07-24 15:27:28 +00:00
func CopyModel ( src , dest string ) error {
2023-08-22 16:39:42 +00:00
srcModelPath := ParseModelPath ( src )
2023-10-27 14:19:59 +00:00
srcPath , err := srcModelPath . GetManifestPath ( )
2023-08-22 04:56:56 +00:00
if err != nil {
return err
}
2023-08-22 16:39:42 +00:00
destModelPath := ParseModelPath ( dest )
2023-10-27 14:19:59 +00:00
destPath , err := destModelPath . GetManifestPath ( )
2023-07-24 15:27:28 +00:00
if err != nil {
return err
}
2023-10-27 14:19:59 +00:00
if err := os . MkdirAll ( filepath . Dir ( destPath ) , 0 o755 ) ; err != nil {
return err
}
2023-07-24 15:27:28 +00:00
// copy the file
2023-07-28 17:38:15 +00:00
input , err := os . ReadFile ( srcPath )
2023-07-24 15:27:28 +00:00
if err != nil {
fmt . Println ( "Error reading file:" , err )
return err
}
2023-07-28 17:38:15 +00:00
err = os . WriteFile ( destPath , input , 0 o644 )
2023-07-24 15:27:28 +00:00
if err != nil {
fmt . Println ( "Error reading file:" , err )
return err
}
return nil
}
2023-11-14 20:30:34 +00:00
func deleteUnusedLayers ( skipModelPath * ModelPath , deleteMap map [ string ] struct { } , dryRun bool ) error {
2023-07-20 23:09:23 +00:00
fp , err := GetManifestPath ( )
if err != nil {
return err
}
2023-08-30 18:31:12 +00:00
walkFunc := func ( path string , info os . FileInfo , _ error ) error {
if info . IsDir ( ) {
return nil
2023-07-20 23:09:23 +00:00
}
2023-08-30 18:31:12 +00:00
dir , file := filepath . Split ( path )
dir = strings . Trim ( strings . TrimPrefix ( dir , fp ) , string ( os . PathSeparator ) )
tag := strings . Join ( [ ] string { dir , file } , ":" )
fmp := ParseModelPath ( tag )
2023-07-20 23:09:23 +00:00
2023-08-30 18:31:12 +00:00
// skip the manifest we're trying to delete
2023-09-11 18:46:35 +00:00
if skipModelPath != nil && skipModelPath . GetFullTagname ( ) == fmp . GetFullTagname ( ) {
2023-08-30 18:31:12 +00:00
return nil
2023-07-20 23:09:23 +00:00
}
2023-08-30 18:31:12 +00:00
// save (i.e. delete from the deleteMap) any files used in other manifests
manifest , _ , err := GetManifest ( fmp )
if err != nil {
return nil
}
for _ , layer := range manifest . Layers {
delete ( deleteMap , layer . Digest )
}
delete ( deleteMap , manifest . Config . Digest )
2023-07-20 23:09:23 +00:00
return nil
2023-08-30 18:31:12 +00:00
}
if err := filepath . Walk ( fp , walkFunc ) ; err != nil {
2023-07-31 22:26:18 +00:00
return err
}
2023-07-20 23:09:23 +00:00
// only delete the files which are still in the deleteMap
2023-11-14 20:30:34 +00:00
for k := range deleteMap {
fp , err := GetBlobsPath ( k )
if err != nil {
log . Printf ( "couldn't get file path for '%s': %v" , k , err )
continue
}
if ! dryRun {
if err := os . Remove ( fp ) ; err != nil {
log . Printf ( "couldn't remove file '%s': %v" , fp , err )
2023-07-22 00:30:40 +00:00
continue
}
2023-11-14 20:30:34 +00:00
} else {
log . Printf ( "wanted to remove: %s" , fp )
2023-07-20 23:09:23 +00:00
}
}
2023-09-11 18:46:35 +00:00
return nil
}
func PruneLayers ( ) error {
2023-11-14 20:30:34 +00:00
deleteMap := make ( map [ string ] struct { } )
2023-09-11 18:46:35 +00:00
p , err := GetBlobsPath ( "" )
if err != nil {
return err
}
blobs , err := os . ReadDir ( p )
if err != nil {
log . Printf ( "couldn't read dir '%s': %v" , p , err )
return err
}
for _ , blob := range blobs {
name := blob . Name ( )
if runtime . GOOS == "windows" {
name = strings . ReplaceAll ( name , "-" , ":" )
}
2023-11-14 22:27:51 +00:00
if strings . HasPrefix ( name , "sha256:" ) {
deleteMap [ name ] = struct { } { }
}
2023-09-11 18:46:35 +00:00
}
log . Printf ( "total blobs: %d" , len ( deleteMap ) )
err = deleteUnusedLayers ( nil , deleteMap , false )
if err != nil {
return err
}
log . Printf ( "total unused blobs removed: %d" , len ( deleteMap ) )
return nil
}
2023-09-27 00:28:14 +00:00
func PruneDirectory ( path string ) error {
info , err := os . Lstat ( path )
if err != nil {
return err
}
if info . IsDir ( ) && info . Mode ( ) & os . ModeSymlink == 0 {
entries , err := os . ReadDir ( path )
if err != nil {
return err
}
for _ , entry := range entries {
if err := PruneDirectory ( filepath . Join ( path , entry . Name ( ) ) ) ; err != nil {
return err
}
}
entries , err = os . ReadDir ( path )
if err != nil {
return err
}
if len ( entries ) > 0 {
return nil
}
return os . Remove ( path )
}
return nil
}
2023-09-11 18:46:35 +00:00
func DeleteModel ( name string ) error {
mp := ParseModelPath ( name )
manifest , _ , err := GetManifest ( mp )
if err != nil {
return err
}
2023-11-14 20:30:34 +00:00
deleteMap := make ( map [ string ] struct { } )
2023-09-11 18:46:35 +00:00
for _ , layer := range manifest . Layers {
2023-11-14 20:30:34 +00:00
deleteMap [ layer . Digest ] = struct { } { }
2023-09-11 18:46:35 +00:00
}
2023-11-14 20:30:34 +00:00
deleteMap [ manifest . Config . Digest ] = struct { } { }
2023-09-11 18:46:35 +00:00
err = deleteUnusedLayers ( & mp , deleteMap , false )
if err != nil {
return err
}
2023-10-27 14:19:59 +00:00
fp , err := mp . GetManifestPath ( )
2023-07-20 23:09:23 +00:00
if err != nil {
return err
}
err = os . Remove ( fp )
if err != nil {
log . Printf ( "couldn't remove manifest file '%s': %v" , fp , err )
return err
}
return nil
}
2023-09-06 18:04:17 +00:00
func ShowModelfile ( model * Model ) ( string , error ) {
2023-10-17 22:40:06 +00:00
var mt struct {
2023-09-06 18:04:17 +00:00
* Model
2023-10-17 22:40:06 +00:00
From string
2023-10-17 22:53:46 +00:00
Parameters map [ string ] [ ] any
2023-09-06 18:04:17 +00:00
}
2023-10-17 22:53:46 +00:00
mt . Parameters = make ( map [ string ] [ ] any )
2023-09-06 18:04:17 +00:00
for k , v := range model . Options {
2023-10-17 22:53:46 +00:00
if s , ok := v . ( [ ] any ) ; ok {
mt . Parameters [ k ] = s
continue
2023-09-06 18:04:17 +00:00
}
2023-10-17 22:53:46 +00:00
mt . Parameters [ k ] = [ ] any { v }
2023-09-06 18:04:17 +00:00
}
2023-10-17 22:40:06 +00:00
mt . Model = model
mt . From = model . ModelPath
2023-09-06 18:04:17 +00:00
2023-10-17 22:40:06 +00:00
if model . OriginalModel != "" {
2023-11-10 20:21:35 +00:00
mt . From = model . OriginalModel
2023-09-06 18:04:17 +00:00
}
modelFile := ` # Modelfile generated by "ollama show"
# To build a new Modelfile based on this one , replace the FROM line with :
# FROM { { . ShortName } }
FROM { { . From } }
TEMPLATE "" "{{ .Template }}" ""
2023-10-17 22:25:43 +00:00
{ { - if . System } }
2023-09-06 18:04:17 +00:00
SYSTEM "" "{{ .System }}" ""
2023-10-17 22:25:43 +00:00
{ { - end } }
2023-10-17 22:28:38 +00:00
{ { - range $ adapter := . AdapterPaths } }
ADAPTER { { $ adapter } }
{ { - end } }
2023-10-17 22:40:06 +00:00
2023-10-17 22:53:46 +00:00
{ { - range $ k , $ v := . Parameters } }
{ { - range $ parameter := $ v } }
PARAMETER { { $ k } } { { printf "%#v" $ parameter } }
{ { - end } }
2023-10-17 22:40:06 +00:00
{ { - end } } `
2023-09-06 18:04:17 +00:00
tmpl , err := template . New ( "" ) . Parse ( modelFile )
if err != nil {
log . Printf ( "error parsing template: %q" , err )
return "" , err
}
var buf bytes . Buffer
if err = tmpl . Execute ( & buf , mt ) ; err != nil {
log . Printf ( "error executing template: %q" , err )
return "" , err
}
return buf . String ( ) , nil
}
2023-08-11 22:41:55 +00:00
func PushModel ( ctx context . Context , name string , regOpts * RegistryOptions , fn func ( api . ProgressResponse ) ) error {
2023-08-22 16:39:42 +00:00
mp := ParseModelPath ( name )
2023-07-19 01:51:30 +00:00
fn ( api . ProgressResponse { Status : "retrieving manifest" } )
2023-08-22 16:39:42 +00:00
if mp . ProtocolScheme == "http" && ! regOpts . Insecure {
return fmt . Errorf ( "insecure protocol http" )
}
2023-08-29 03:50:24 +00:00
manifest , _ , err := GetManifest ( mp )
2023-07-17 00:02:22 +00:00
if err != nil {
2023-07-19 01:51:30 +00:00
fn ( api . ProgressResponse { Status : "couldn't retrieve manifest" } )
2023-07-17 00:02:22 +00:00
return err
}
var layers [ ] * Layer
2023-08-01 01:37:40 +00:00
layers = append ( layers , manifest . Layers ... )
2023-11-22 21:28:49 +00:00
layers = append ( layers , manifest . Config )
2023-07-17 00:02:22 +00:00
for _ , layer := range layers {
2023-10-09 17:24:27 +00:00
if err := uploadBlob ( ctx , mp , layer , regOpts , fn ) ; err != nil {
2023-07-17 00:02:22 +00:00
log . Printf ( "error uploading blob: %v" , err )
2023-11-16 21:44:18 +00:00
if errors . Is ( err , errUnauthorized ) {
return fmt . Errorf ( "unable to push %s, make sure this namespace exists and you are authorized to push to it" , ParseModelPath ( name ) . GetNamespaceRepository ( ) )
}
2023-07-17 00:02:22 +00:00
return err
}
2023-07-19 01:51:30 +00:00
}
2023-07-23 00:31:26 +00:00
fn ( api . ProgressResponse { Status : "pushing manifest" } )
2023-08-22 01:38:31 +00:00
requestURL := mp . BaseURL ( )
requestURL = requestURL . JoinPath ( "v2" , mp . GetNamespaceRepository ( ) , "manifests" , mp . Tag )
2023-07-17 00:02:22 +00:00
manifestJSON , err := json . Marshal ( manifest )
if err != nil {
return err
}
2023-08-22 01:24:42 +00:00
headers := make ( http . Header )
headers . Set ( "Content-Type" , "application/vnd.docker.distribution.manifest.v2+json" )
2023-11-02 20:10:58 +00:00
resp , err := makeRequestWithRetry ( ctx , http . MethodPut , requestURL , headers , bytes . NewReader ( manifestJSON ) , regOpts )
2023-07-17 00:02:22 +00:00
if err != nil {
return err
}
defer resp . Body . Close ( )
2023-07-23 00:31:26 +00:00
fn ( api . ProgressResponse { Status : "success" } )
2023-07-17 00:02:22 +00:00
return nil
}
2023-07-25 21:08:51 +00:00
func PullModel ( ctx context . Context , name string , regOpts * RegistryOptions , fn func ( api . ProgressResponse ) ) error {
2023-08-22 16:39:42 +00:00
mp := ParseModelPath ( name )
2023-09-11 18:46:35 +00:00
var manifest * ManifestV2
var err error
var noprune string
// build deleteMap to prune unused layers
2023-11-14 20:30:34 +00:00
deleteMap := make ( map [ string ] struct { } )
2023-09-11 18:46:35 +00:00
if noprune = os . Getenv ( "OLLAMA_NOPRUNE" ) ; noprune == "" {
manifest , _ , err = GetManifest ( mp )
if err != nil && ! errors . Is ( err , os . ErrNotExist ) {
return err
}
if manifest != nil {
for _ , l := range manifest . Layers {
2023-11-14 20:30:34 +00:00
deleteMap [ l . Digest ] = struct { } { }
2023-09-11 18:46:35 +00:00
}
2023-11-14 20:30:34 +00:00
deleteMap [ manifest . Config . Digest ] = struct { } { }
2023-09-11 18:46:35 +00:00
}
}
2023-08-22 16:39:42 +00:00
if mp . ProtocolScheme == "http" && ! regOpts . Insecure {
return fmt . Errorf ( "insecure protocol http" )
2023-08-22 04:56:56 +00:00
}
2023-07-17 00:02:22 +00:00
2023-07-19 01:51:30 +00:00
fn ( api . ProgressResponse { Status : "pulling manifest" } )
2023-07-17 00:02:22 +00:00
2023-09-11 18:46:35 +00:00
manifest , err = pullModelManifest ( ctx , mp , regOpts )
2023-07-17 00:02:22 +00:00
if err != nil {
2023-07-24 21:48:17 +00:00
return fmt . Errorf ( "pull model manifest: %s" , err )
2023-07-17 00:02:22 +00:00
}
var layers [ ] * Layer
2023-07-20 18:18:00 +00:00
layers = append ( layers , manifest . Layers ... )
2023-11-22 21:28:49 +00:00
layers = append ( layers , manifest . Config )
2023-07-17 00:02:22 +00:00
for _ , layer := range layers {
2023-08-15 18:07:19 +00:00
if err := downloadBlob (
ctx ,
downloadOpts {
mp : mp ,
digest : layer . Digest ,
regOpts : regOpts ,
fn : fn ,
} ) ; err != nil {
2023-07-17 00:02:22 +00:00
return err
}
2023-09-11 18:46:35 +00:00
delete ( deleteMap , layer . Digest )
2023-07-17 00:02:22 +00:00
}
2023-09-11 18:46:35 +00:00
delete ( deleteMap , manifest . Config . Digest )
2023-07-17 00:02:22 +00:00
2023-07-20 18:44:05 +00:00
fn ( api . ProgressResponse { Status : "verifying sha256 digest" } )
for _ , layer := range layers {
if err := verifyBlob ( layer . Digest ) ; err != nil {
2023-07-24 18:53:01 +00:00
if errors . Is ( err , errDigestMismatch ) {
// something went wrong, delete the blob
fp , err := GetBlobsPath ( layer . Digest )
if err != nil {
return err
}
if err := os . Remove ( fp ) ; err != nil {
// log this, but return the original error
log . Printf ( "couldn't remove file with digest mismatch '%s': %v" , fp , err )
}
}
2023-07-20 18:44:05 +00:00
return err
}
}
2023-07-19 01:51:30 +00:00
fn ( api . ProgressResponse { Status : "writing manifest" } )
2023-07-17 00:02:22 +00:00
2023-07-17 18:03:55 +00:00
manifestJSON , err := json . Marshal ( manifest )
2023-07-17 00:02:22 +00:00
if err != nil {
return err
}
2023-10-27 14:19:59 +00:00
fp , err := mp . GetManifestPath ( )
2023-07-17 00:02:22 +00:00
if err != nil {
return err
}
2023-10-27 14:19:59 +00:00
if err := os . MkdirAll ( filepath . Dir ( fp ) , 0 o755 ) ; err != nil {
return err
}
2023-07-17 00:02:22 +00:00
2023-07-20 18:18:00 +00:00
err = os . WriteFile ( fp , manifestJSON , 0 o644 )
2023-07-17 00:02:22 +00:00
if err != nil {
log . Printf ( "couldn't write to %s" , fp )
return err
}
2023-09-11 18:46:35 +00:00
if noprune == "" {
fn ( api . ProgressResponse { Status : "removing any unused layers" } )
err = deleteUnusedLayers ( nil , deleteMap , false )
if err != nil {
return err
}
}
2023-07-19 01:51:30 +00:00
fn ( api . ProgressResponse { Status : "success" } )
2023-07-17 00:02:22 +00:00
return nil
}
2023-08-11 22:41:55 +00:00
func pullModelManifest ( ctx context . Context , mp ModelPath , regOpts * RegistryOptions ) ( * ManifestV2 , error ) {
2023-08-22 01:38:31 +00:00
requestURL := mp . BaseURL ( ) . JoinPath ( "v2" , mp . GetNamespaceRepository ( ) , "manifests" , mp . Tag )
2023-07-17 00:02:22 +00:00
2023-08-22 01:24:42 +00:00
headers := make ( http . Header )
headers . Set ( "Accept" , "application/vnd.docker.distribution.manifest.v2+json" )
2023-11-02 20:13:32 +00:00
resp , err := makeRequestWithRetry ( ctx , http . MethodGet , requestURL , headers , nil , regOpts )
2023-07-17 00:02:22 +00:00
if err != nil {
return nil , err
}
defer resp . Body . Close ( )
var m * ManifestV2
if err := json . NewDecoder ( resp . Body ) . Decode ( & m ) ; err != nil {
return nil , err
}
return m , err
}
// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
2023-09-28 17:00:34 +00:00
func GetSHA256Digest ( r io . Reader ) ( string , int64 ) {
2023-07-19 00:14:12 +00:00
h := sha256 . New ( )
n , err := io . Copy ( h , r )
if err != nil {
log . Fatal ( err )
}
2023-09-28 17:00:34 +00:00
return fmt . Sprintf ( "sha256:%x" , h . Sum ( nil ) ) , n
2023-07-17 00:02:22 +00:00
}
2023-11-16 21:44:18 +00:00
var errUnauthorized = fmt . Errorf ( "unauthorized" )
2023-08-22 01:38:31 +00:00
func makeRequestWithRetry ( ctx context . Context , method string , requestURL * url . URL , headers http . Header , body io . ReadSeeker , regOpts * RegistryOptions ) ( * http . Response , error ) {
2023-11-17 19:22:35 +00:00
resp , err := makeRequest ( ctx , method , requestURL , headers , body , regOpts )
if err != nil {
if ! errors . Is ( err , context . Canceled ) {
log . Printf ( "request failed: %v" , err )
}
2023-11-19 05:19:53 +00:00
2023-11-17 19:22:35 +00:00
return nil , err
}
switch {
case resp . StatusCode == http . StatusUnauthorized :
// Handle authentication error with one retry
auth := resp . Header . Get ( "www-authenticate" )
authRedir := ParseAuthRedirectString ( auth )
token , err := getAuthToken ( ctx , authRedir )
2023-08-17 19:35:29 +00:00
if err != nil {
return nil , err
}
2023-11-17 19:22:35 +00:00
regOpts . Token = token
if body != nil {
_ , err = body . Seek ( 0 , io . SeekStart )
2023-08-17 19:35:29 +00:00
if err != nil {
return nil , err
}
}
2023-11-19 05:19:53 +00:00
resp , err := makeRequest ( ctx , method , requestURL , headers , body , regOpts )
if resp . StatusCode == http . StatusUnauthorized {
return nil , errUnauthorized
}
return resp , err
2023-11-17 19:22:35 +00:00
case resp . StatusCode == http . StatusNotFound :
return nil , os . ErrNotExist
case resp . StatusCode >= http . StatusBadRequest :
responseBody , err := io . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "%d: %s" , resp . StatusCode , err )
}
return nil , fmt . Errorf ( "%d: %s" , resp . StatusCode , responseBody )
2023-08-17 19:35:29 +00:00
}
2023-11-17 19:22:35 +00:00
return resp , nil
2023-08-17 19:35:29 +00:00
}
2023-08-22 01:38:31 +00:00
func makeRequest ( ctx context . Context , method string , requestURL * url . URL , headers http . Header , body io . Reader , regOpts * RegistryOptions ) ( * http . Response , error ) {
2023-09-08 00:24:31 +00:00
if requestURL . Scheme != "http" && regOpts != nil && regOpts . Insecure {
2023-08-22 01:38:31 +00:00
requestURL . Scheme = "http"
2023-07-21 22:42:19 +00:00
}
2023-08-22 01:38:31 +00:00
req , err := http . NewRequestWithContext ( ctx , method , requestURL . String ( ) , body )
2023-07-17 00:02:22 +00:00
if err != nil {
return nil , err
}
2023-08-22 01:24:42 +00:00
if headers != nil {
req . Header = headers
}
2023-09-07 18:49:36 +00:00
if regOpts != nil {
if regOpts . Token != "" {
req . Header . Set ( "Authorization" , "Bearer " + regOpts . Token )
} else if regOpts . Username != "" && regOpts . Password != "" {
req . SetBasicAuth ( regOpts . Username , regOpts . Password )
}
2023-07-17 00:02:22 +00:00
}
2023-08-22 01:24:42 +00:00
req . Header . Set ( "User-Agent" , fmt . Sprintf ( "ollama/%s (%s %s) Go/%s" , version . Version , runtime . GOARCH , runtime . GOOS , runtime . Version ( ) ) )
2023-07-17 00:02:22 +00:00
2023-09-14 17:05:29 +00:00
if s := req . Header . Get ( "Content-Length" ) ; s != "" {
contentLength , err := strconv . ParseInt ( s , 10 , 64 )
if err != nil {
return nil , err
}
req . ContentLength = contentLength
}
2023-10-09 18:42:36 +00:00
proxyURL , err := http . ProxyFromEnvironment ( req )
if err != nil {
return nil , err
}
client := http . Client {
Transport : & http . Transport {
Proxy : http . ProxyURL ( proxyURL ) ,
} ,
}
resp , err := client . Do ( req )
2023-07-17 00:02:22 +00:00
if err != nil {
return nil , err
}
return resp , nil
}
2023-07-20 18:44:05 +00:00
2023-08-10 18:34:25 +00:00
func getValue ( header , key string ) string {
startIdx := strings . Index ( header , key + "=" )
if startIdx == - 1 {
return ""
}
// Move the index to the starting quote after the key.
startIdx += len ( key ) + 2
endIdx := startIdx
for endIdx < len ( header ) {
if header [ endIdx ] == '"' {
if endIdx + 1 < len ( header ) && header [ endIdx + 1 ] != ',' { // If the next character isn't a comma, continue
endIdx ++
continue
}
break
}
endIdx ++
}
return header [ startIdx : endIdx ]
}
func ParseAuthRedirectString ( authStr string ) AuthRedirect {
authStr = strings . TrimPrefix ( authStr , "Bearer " )
return AuthRedirect {
Realm : getValue ( authStr , "realm" ) ,
Service : getValue ( authStr , "service" ) ,
Scope : getValue ( authStr , "scope" ) ,
}
}
2023-07-24 18:53:01 +00:00
var errDigestMismatch = fmt . Errorf ( "digest mismatch, file must be downloaded again" )
2023-07-20 18:44:05 +00:00
func verifyBlob ( digest string ) error {
fp , err := GetBlobsPath ( digest )
if err != nil {
return err
}
f , err := os . Open ( fp )
if err != nil {
return err
}
defer f . Close ( )
fileDigest , _ := GetSHA256Digest ( f )
if digest != fileDigest {
2023-07-24 18:53:01 +00:00
return fmt . Errorf ( "%w: want %s, got %s" , errDigestMismatch , digest , fileDigest )
2023-07-20 18:44:05 +00:00
}
return nil
}