1package image 2 3import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/docker/distribution/digestset" 11 "github.com/docker/docker/layer" 12 "github.com/docker/docker/pkg/system" 13 "github.com/opencontainers/go-digest" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16) 17 18// Store is an interface for creating and accessing images 19type Store interface { 20 Create(config []byte) (ID, error) 21 Get(id ID) (*Image, error) 22 Delete(id ID) ([]layer.Metadata, error) 23 Search(partialID string) (ID, error) 24 SetParent(id ID, parent ID) error 25 GetParent(id ID) (ID, error) 26 SetLastUpdated(id ID) error 27 GetLastUpdated(id ID) (time.Time, error) 28 Children(id ID) []ID 29 Map() map[ID]*Image 30 Heads() map[ID]*Image 31} 32 33// LayerGetReleaser is a minimal interface for getting and releasing images. 34type LayerGetReleaser interface { 35 Get(layer.ChainID) (layer.Layer, error) 36 Release(layer.Layer) ([]layer.Metadata, error) 37} 38 39type imageMeta struct { 40 layer layer.Layer 41 children map[ID]struct{} 42} 43 44type store struct { 45 sync.RWMutex 46 ls LayerGetReleaser 47 images map[ID]*imageMeta 48 fs StoreBackend 49 digestSet *digestset.Set 50 os string 51} 52 53// NewImageStore returns new store object for given layer store 54func NewImageStore(fs StoreBackend, os string, ls LayerGetReleaser) (Store, error) { 55 is := &store{ 56 ls: ls, 57 images: make(map[ID]*imageMeta), 58 fs: fs, 59 digestSet: digestset.NewSet(), 60 os: os, 61 } 62 63 // load all current images and retain layers 64 if err := is.restore(); err != nil { 65 return nil, err 66 } 67 68 return is, nil 69} 70 71func (is *store) restore() error { 72 err := is.fs.Walk(func(dgst digest.Digest) error { 73 img, err := is.Get(IDFromDigest(dgst)) 74 if err != nil { 75 logrus.Errorf("invalid image %v, %v", dgst, err) 76 return nil 77 } 78 var l layer.Layer 79 if chainID := img.RootFS.ChainID(); chainID != "" { 80 l, err = is.ls.Get(chainID) 81 if err != nil { 82 return err 83 } 84 } 85 if err := is.digestSet.Add(dgst); err != nil { 86 return err 87 } 88 89 imageMeta := &imageMeta{ 90 layer: l, 91 children: make(map[ID]struct{}), 92 } 93 94 is.images[IDFromDigest(dgst)] = imageMeta 95 96 return nil 97 }) 98 if err != nil { 99 return err 100 } 101 102 // Second pass to fill in children maps 103 for id := range is.images { 104 if parent, err := is.GetParent(id); err == nil { 105 if parentMeta := is.images[parent]; parentMeta != nil { 106 parentMeta.children[id] = struct{}{} 107 } 108 } 109 } 110 111 return nil 112} 113 114func (is *store) Create(config []byte) (ID, error) { 115 var img Image 116 err := json.Unmarshal(config, &img) 117 if err != nil { 118 return "", err 119 } 120 121 // TODO @jhowardmsft - LCOW Support. This will need revisiting when coalescing the image stores. 122 // Integrity check - ensure we are creating something for the correct platform 123 if system.LCOWSupported() { 124 if strings.ToLower(img.OperatingSystem()) != strings.ToLower(is.os) { 125 return "", fmt.Errorf("cannot create entry for operating system %q in image store for operating system %q", img.OperatingSystem(), is.os) 126 } 127 } 128 129 // Must reject any config that references diffIDs from the history 130 // which aren't among the rootfs layers. 131 rootFSLayers := make(map[layer.DiffID]struct{}) 132 for _, diffID := range img.RootFS.DiffIDs { 133 rootFSLayers[diffID] = struct{}{} 134 } 135 136 layerCounter := 0 137 for _, h := range img.History { 138 if !h.EmptyLayer { 139 layerCounter++ 140 } 141 } 142 if layerCounter > len(img.RootFS.DiffIDs) { 143 return "", errors.New("too many non-empty layers in History section") 144 } 145 146 dgst, err := is.fs.Set(config) 147 if err != nil { 148 return "", err 149 } 150 imageID := IDFromDigest(dgst) 151 152 is.Lock() 153 defer is.Unlock() 154 155 if _, exists := is.images[imageID]; exists { 156 return imageID, nil 157 } 158 159 layerID := img.RootFS.ChainID() 160 161 var l layer.Layer 162 if layerID != "" { 163 l, err = is.ls.Get(layerID) 164 if err != nil { 165 return "", errors.Wrapf(err, "failed to get layer %s", layerID) 166 } 167 } 168 169 imageMeta := &imageMeta{ 170 layer: l, 171 children: make(map[ID]struct{}), 172 } 173 174 is.images[imageID] = imageMeta 175 if err := is.digestSet.Add(imageID.Digest()); err != nil { 176 delete(is.images, imageID) 177 return "", err 178 } 179 180 return imageID, nil 181} 182 183type imageNotFoundError string 184 185func (e imageNotFoundError) Error() string { 186 return "No such image: " + string(e) 187} 188 189func (imageNotFoundError) NotFound() {} 190 191func (is *store) Search(term string) (ID, error) { 192 dgst, err := is.digestSet.Lookup(term) 193 if err != nil { 194 if err == digestset.ErrDigestNotFound { 195 err = imageNotFoundError(term) 196 } 197 return "", errors.WithStack(err) 198 } 199 return IDFromDigest(dgst), nil 200} 201 202func (is *store) Get(id ID) (*Image, error) { 203 // todo: Check if image is in images 204 // todo: Detect manual insertions and start using them 205 config, err := is.fs.Get(id.Digest()) 206 if err != nil { 207 return nil, err 208 } 209 210 img, err := NewFromJSON(config) 211 if err != nil { 212 return nil, err 213 } 214 img.computedID = id 215 216 img.Parent, err = is.GetParent(id) 217 if err != nil { 218 img.Parent = "" 219 } 220 221 return img, nil 222} 223 224func (is *store) Delete(id ID) ([]layer.Metadata, error) { 225 is.Lock() 226 defer is.Unlock() 227 228 imageMeta := is.images[id] 229 if imageMeta == nil { 230 return nil, fmt.Errorf("unrecognized image ID %s", id.String()) 231 } 232 for id := range imageMeta.children { 233 is.fs.DeleteMetadata(id.Digest(), "parent") 234 } 235 if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil { 236 delete(is.images[parent].children, id) 237 } 238 239 if err := is.digestSet.Remove(id.Digest()); err != nil { 240 logrus.Errorf("error removing %s from digest set: %q", id, err) 241 } 242 delete(is.images, id) 243 is.fs.Delete(id.Digest()) 244 245 if imageMeta.layer != nil { 246 return is.ls.Release(imageMeta.layer) 247 } 248 return nil, nil 249} 250 251func (is *store) SetParent(id, parent ID) error { 252 is.Lock() 253 defer is.Unlock() 254 parentMeta := is.images[parent] 255 if parentMeta == nil { 256 return fmt.Errorf("unknown parent image ID %s", parent.String()) 257 } 258 if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil { 259 delete(is.images[parent].children, id) 260 } 261 parentMeta.children[id] = struct{}{} 262 return is.fs.SetMetadata(id.Digest(), "parent", []byte(parent)) 263} 264 265func (is *store) GetParent(id ID) (ID, error) { 266 d, err := is.fs.GetMetadata(id.Digest(), "parent") 267 if err != nil { 268 return "", err 269 } 270 return ID(d), nil // todo: validate? 271} 272 273// SetLastUpdated time for the image ID to the current time 274func (is *store) SetLastUpdated(id ID) error { 275 lastUpdated := []byte(time.Now().Format(time.RFC3339Nano)) 276 return is.fs.SetMetadata(id.Digest(), "lastUpdated", lastUpdated) 277} 278 279// GetLastUpdated time for the image ID 280func (is *store) GetLastUpdated(id ID) (time.Time, error) { 281 bytes, err := is.fs.GetMetadata(id.Digest(), "lastUpdated") 282 if err != nil || len(bytes) == 0 { 283 // No lastUpdated time 284 return time.Time{}, nil 285 } 286 return time.Parse(time.RFC3339Nano, string(bytes)) 287} 288 289func (is *store) Children(id ID) []ID { 290 is.RLock() 291 defer is.RUnlock() 292 293 return is.children(id) 294} 295 296func (is *store) children(id ID) []ID { 297 var ids []ID 298 if is.images[id] != nil { 299 for id := range is.images[id].children { 300 ids = append(ids, id) 301 } 302 } 303 return ids 304} 305 306func (is *store) Heads() map[ID]*Image { 307 return is.imagesMap(false) 308} 309 310func (is *store) Map() map[ID]*Image { 311 return is.imagesMap(true) 312} 313 314func (is *store) imagesMap(all bool) map[ID]*Image { 315 is.RLock() 316 defer is.RUnlock() 317 318 images := make(map[ID]*Image) 319 320 for id := range is.images { 321 if !all && len(is.children(id)) > 0 { 322 continue 323 } 324 img, err := is.Get(id) 325 if err != nil { 326 logrus.Errorf("invalid image access: %q, error: %q", id, err) 327 continue 328 } 329 images[id] = img 330 } 331 return images 332} 333