@@ -2,13 +2,16 @@ package cache
2
2
3
3
import (
4
4
"context"
5
+ "crypto"
6
+ "encoding/hex"
5
7
"fmt"
8
+ "github.com/solo-io/go-utils/contextutils"
9
+ "go.uber.org/zap"
6
10
"io"
7
- "log"
8
11
"net/http"
9
12
"path"
10
13
"strconv"
11
- "sync "
14
+ "strings "
12
15
"time"
13
16
14
17
"github.com/pkg/errors"
@@ -32,75 +35,33 @@ type Cache interface {
32
35
type CacheImpl struct {
33
36
Puller pull.ImagePuller
34
37
38
+ logger * zap.SugaredLogger
39
+
35
40
cacheState cacheState
36
41
}
37
42
38
43
func NewCache (puller pull.ImagePuller ) Cache {
39
- return & CacheImpl {
40
- Puller : puller ,
41
- }
42
- }
43
-
44
- type cacheState struct {
45
- images map [string ]pull.Image
46
- imagesLock sync.RWMutex
47
- }
48
-
49
- func (c * cacheState ) add (image pull.Image ) {
50
- desc , err := image .Descriptor ()
51
- if err != nil {
52
- // image is missing descriptor, should never happen
53
- // TODO: better logging impl
54
- log .Printf ("error: image %v missing code descriptor" , image .Ref ())
55
- return
56
- }
57
- if c .find (desc .Digest ) != nil {
58
- // check existence for idempotence
59
- // technically metadata can be different, but it's fine for now.
60
- return
61
- }
62
- c .imagesLock .Lock ()
63
- if c .images == nil {
64
- c .images = make (map [string ]pull.Image )
65
- }
66
- c .images [image .Ref ()] = image
67
- c .imagesLock .Unlock ()
44
+ return NewCacheWithConext (context .Background (), puller )
68
45
}
69
46
70
- func (c * cacheState ) find (digest digest.Digest ) pull.Image {
71
- c .imagesLock .RLock ()
72
- defer c .imagesLock .RUnlock ()
73
- if c .images == nil {
74
- return nil
75
- }
76
- for _ , image := range c .images {
77
- desc , err := image .Descriptor ()
78
- if err != nil {
79
- log .Printf ("error: image %v missing code descriptor" , image .Ref ())
80
- return nil
81
- }
82
-
83
- if desc .Digest == digest {
84
- return image
85
- }
47
+ func NewCacheWithConext (ctx context.Context , puller pull.ImagePuller ) Cache {
48
+ return & CacheImpl {
49
+ Puller : puller ,
50
+ logger : contextutils .LoggerFrom (ctx ),
86
51
}
87
- return nil
88
- }
89
- func (c * cacheState ) findImage (image string ) pull.Image {
90
- c .imagesLock .RLock ()
91
- defer c .imagesLock .RUnlock ()
92
- return c .images [image ]
93
52
}
94
53
95
54
func (c * CacheImpl ) Add (ctx context.Context , ref string ) (digest.Digest , error ) {
96
55
if img := c .cacheState .findImage (ref ); img != nil {
56
+ c .logger .Debugf ("found cached image ref %v" , ref )
97
57
desc , err := img .Descriptor ()
98
58
if err != nil {
99
59
return "" , err
100
60
}
101
61
return desc .Digest , nil
102
62
}
103
63
64
+ c .logger .Debugf ("attempting to pull image %v" , ref )
104
65
image , err := c .Puller .Pull (ctx , ref )
105
66
if err != nil {
106
67
return "" , err
@@ -113,6 +74,8 @@ func (c *CacheImpl) Add(ctx context.Context, ref string) (digest.Digest, error)
113
74
114
75
c .cacheState .add (image )
115
76
77
+ c .logger .Debugf ("pulled image %v (digest: %v)" , ref , desc .Digest )
78
+
116
79
return desc .Digest , nil
117
80
}
118
81
@@ -125,11 +88,33 @@ func (c *CacheImpl) Get(ctx context.Context, digest digest.Digest) (model.Filter
125
88
}
126
89
127
90
func (c * CacheImpl ) ServeHTTP (rw http.ResponseWriter , r * http.Request ) {
91
+ // we support two paths:
92
+ // /<HASH> - used in gloo
93
+ // /image-name - used here to cache on demand
94
+ _ , file := path .Split (r .URL .Path )
95
+ switch {
96
+ case len (file ) == hex .EncodedLen (crypto .SHA256 .Size ()):
97
+ c .ServeHTTPSha (rw , r , file )
98
+ default :
99
+ // assume that the path is a ref. add it to cache
100
+ ref := strings .TrimPrefix (r .URL .Path , "/" )
101
+ c .logger .Debugf ("serving http request for image ref %v" , ref )
102
+ desc , err := c .Add (r .Context (), ref )
103
+ if err != nil {
104
+ c .logger .Errorf ("failed to add or fetch descriptor %v: %v" , ref , err )
105
+ http .Error (rw , err .Error (), http .StatusBadRequest )
106
+ return
107
+ }
108
+ c .ServeHTTPSha (rw , r , desc .Encoded ())
109
+ }
110
+ }
111
+
112
+ func (c * CacheImpl ) ServeHTTPSha (rw http.ResponseWriter , r * http.Request , sha string ) {
128
113
// parse the url
129
114
ctx := r .Context ()
130
- _ , file := path .Split (r .URL .Path )
131
- image := c .cacheState .find (digest .Digest ("sha256:" + file ))
115
+ image := c .cacheState .find (digest .Digest ("sha256:" + sha ))
132
116
if image == nil {
117
+ c .logger .Errorf ("image with sha %v not found" , sha )
133
118
http .NotFound (rw , r )
134
119
return
135
120
}
@@ -142,6 +127,7 @@ func (c *CacheImpl) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
142
127
143
128
filter , err := image .FetchFilter (ctx )
144
129
if err != nil {
130
+ c .logger .Errorf ("failed fetching image content" )
145
131
http .NotFound (rw , r )
146
132
return
147
133
}
@@ -151,12 +137,14 @@ func (c *CacheImpl) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
151
137
152
138
rw .Header ().Set ("Content-Type" , desc .MediaType )
153
139
rw .Header ().Set ("Etag" , "\" " + string (desc .Digest )+ "\" " )
140
+ c .logger .Debugf ("writing image content..." )
154
141
if rs , ok := filter .(io.ReadSeeker ); ok {
155
142
// content of digests never changes so set mod time to a constant
156
143
// don't use zero time because serve content doesn't use that.
157
144
modTime := time .Date (2000 , time .January , 1 , 0 , 0 , 0 , 0 , time .UTC )
158
- http .ServeContent (rw , r , file , modTime , rs )
145
+ http .ServeContent (rw , r , sha , modTime , rs )
159
146
} else {
147
+ c .logger .Debugf ("writing image content" )
160
148
rw .Header ().Add ("Content-Length" , strconv .Itoa (int (desc .Size )))
161
149
if r .Method != "HEAD" {
162
150
_ , err = io .Copy (rw , filter )
@@ -166,4 +154,5 @@ func (c *CacheImpl) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
166
154
}
167
155
}
168
156
}
157
+ c .logger .Debugf ("finished writing %v: %v bytes" , image .Ref (), desc .Size )
169
158
}
0 commit comments