github.com/uber/kraken@v0.1.4/proxy/proxyserver/preheat.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package proxyserver 15 16 import ( 17 "bytes" 18 "encoding/json" 19 "fmt" 20 "net/http" 21 _ "net/http/pprof" // Registers /debug/pprof endpoints in http.DefaultServeMux. 22 "regexp" 23 "time" 24 25 "github.com/docker/distribution" 26 "github.com/uber/kraken/core" 27 "github.com/uber/kraken/origin/blobclient" 28 "github.com/uber/kraken/utils/dockerutil" 29 "github.com/uber/kraken/utils/handler" 30 "github.com/uber/kraken/utils/httputil" 31 "github.com/uber/kraken/utils/log" 32 ) 33 34 var _manifestRegexp = regexp.MustCompile(`^application/vnd.docker.distribution.manifest.v\d\+(json|prettyjws)`) 35 36 // PreheatHandler defines the handler of preheat. 37 type PreheatHandler struct { 38 clusterClient blobclient.ClusterClient 39 } 40 41 // NewPreheatHandler creates a new preheat handler. 42 func NewPreheatHandler(client blobclient.ClusterClient) *PreheatHandler { 43 return &PreheatHandler{client} 44 } 45 46 // Handle notifies origins to cache the blob related to the image. 47 func (ph *PreheatHandler) Handle(w http.ResponseWriter, r *http.Request) error { 48 var notification Notification 49 if err := json.NewDecoder(r.Body).Decode(¬ification); err != nil { 50 return handler.Errorf("decode body: %s", err) 51 } 52 53 events := filterEvents(¬ification) 54 for _, event := range events { 55 repo := event.Target.Repository 56 digest := event.Target.Digest 57 58 log.With("repo", repo, "digest", digest).Infof("deal push image event") 59 err := ph.process(repo, digest) 60 if err != nil { 61 log.With("repo", repo, "digest", digest).Errorf("handle preheat: %s", err) 62 } 63 } 64 return nil 65 } 66 67 func (ph *PreheatHandler) process(repo, digest string) error { 68 manifest, err := ph.fetchManifest(repo, digest) 69 if err != nil { 70 return err 71 } 72 for _, desc := range manifest.References() { 73 d, err := core.ParseSHA256Digest(string(desc.Digest)) 74 if err != nil { 75 log.With("repo", repo, "digest", string(desc.Digest)).Errorf("parse digest: %s", err) 76 continue 77 } 78 go func() { 79 log.With("repo", repo).Debugf("trigger origin cache: %+v", d) 80 _, err = ph.clusterClient.GetMetaInfo(repo, d) 81 if err != nil && !httputil.IsAccepted(err) { 82 log.With("repo", repo, "digest", digest).Errorf("notify origin cache: %s", err) 83 } 84 }() 85 } 86 return nil 87 } 88 89 func (ph *PreheatHandler) fetchManifest(repo, digest string) (distribution.Manifest, error) { 90 d, err := core.ParseSHA256Digest(digest) 91 if err != nil { 92 return nil, fmt.Errorf("Error parse digest: %s ", err) 93 } 94 95 buf := &bytes.Buffer{} 96 // there may be a gap between registry finish uploading manifest and send notification. 97 // see https://github.com/docker/distribution/issues/2625. 98 interval := 100 * time.Millisecond 99 for i := 0; i < 4; i++ { 100 if i != 0 { 101 time.Sleep(interval) 102 interval = interval * 2 103 } 104 if err := ph.clusterClient.DownloadBlob(repo, d, buf); err == nil { 105 break 106 } else if err == blobclient.ErrBlobNotFound { 107 continue 108 } else { 109 return nil, fmt.Errorf("download manifest: %s", err) 110 } 111 } 112 if buf.Len() == 0 { 113 return nil, fmt.Errorf("manifest not found") 114 } 115 116 manifest, _, err := dockerutil.ParseManifestV2(buf) 117 if err != nil { 118 return nil, fmt.Errorf("parse manifest: %s", err) 119 } 120 return manifest, nil 121 } 122 123 // filterEvents pick out the push manifest events. 124 func filterEvents(notification *Notification) []*Event { 125 events := []*Event{} 126 127 for _, event := range notification.Events { 128 isManifest := _manifestRegexp.MatchString(event.Target.MediaType) 129 if !isManifest { 130 continue 131 } 132 133 if event.Action == "push" { 134 events = append(events, &event) 135 continue 136 } 137 } 138 return events 139 }