github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/builder/builder-next/exporter/writer.go (about) 1 package containerimage 2 3 import ( 4 "context" 5 "encoding/json" 6 "runtime" 7 "time" 8 9 "github.com/moby/buildkit/cache" 10 "github.com/moby/buildkit/util/progress" 11 "github.com/moby/buildkit/util/system" 12 digest "github.com/opencontainers/go-digest" 13 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 ) 17 18 // const ( 19 // emptyGZLayer = digest.Digest("sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1") 20 // ) 21 22 func emptyImageConfig() ([]byte, error) { 23 img := ocispec.Image{ 24 Architecture: runtime.GOARCH, 25 OS: runtime.GOOS, 26 } 27 img.RootFS.Type = "layers" 28 img.Config.WorkingDir = "/" 29 img.Config.Env = []string{"PATH=" + system.DefaultPathEnv} 30 dt, err := json.Marshal(img) 31 return dt, errors.Wrap(err, "failed to create empty image config") 32 } 33 34 func parseHistoryFromConfig(dt []byte) ([]ocispec.History, error) { 35 var config struct { 36 History []ocispec.History 37 } 38 if err := json.Unmarshal(dt, &config); err != nil { 39 return nil, errors.Wrap(err, "failed to unmarshal history from config") 40 } 41 return config.History, nil 42 } 43 44 func patchImageConfig(dt []byte, dps []digest.Digest, history []ocispec.History, cache []byte) ([]byte, error) { 45 m := map[string]json.RawMessage{} 46 if err := json.Unmarshal(dt, &m); err != nil { 47 return nil, errors.Wrap(err, "failed to parse image config for patch") 48 } 49 50 var rootFS ocispec.RootFS 51 rootFS.Type = "layers" 52 rootFS.DiffIDs = append(rootFS.DiffIDs, dps...) 53 54 dt, err := json.Marshal(rootFS) 55 if err != nil { 56 return nil, errors.Wrap(err, "failed to marshal rootfs") 57 } 58 m["rootfs"] = dt 59 60 dt, err = json.Marshal(history) 61 if err != nil { 62 return nil, errors.Wrap(err, "failed to marshal history") 63 } 64 m["history"] = dt 65 66 if _, ok := m["created"]; !ok { 67 var tm *time.Time 68 for _, h := range history { 69 if h.Created != nil { 70 tm = h.Created 71 } 72 } 73 dt, err = json.Marshal(&tm) 74 if err != nil { 75 return nil, errors.Wrap(err, "failed to marshal creation time") 76 } 77 m["created"] = dt 78 } 79 80 if cache != nil { 81 dt, err = json.Marshal(cache) 82 if err != nil { 83 return nil, errors.Wrap(err, "failed to marshal cache") 84 } 85 m["moby.buildkit.cache.v0"] = dt 86 } 87 88 dt, err = json.Marshal(m) 89 return dt, errors.Wrap(err, "failed to marshal config after patch") 90 } 91 92 func normalizeLayersAndHistory(diffs []digest.Digest, history []ocispec.History, ref cache.ImmutableRef) ([]digest.Digest, []ocispec.History) { 93 refMeta := getRefMetadata(ref, len(diffs)) 94 var historyLayers int 95 for _, h := range history { 96 if !h.EmptyLayer { 97 historyLayers++ 98 } 99 } 100 if historyLayers > len(diffs) { 101 // this case shouldn't happen but if it does force set history layers empty 102 // from the bottom 103 logrus.Warn("invalid image config with unaccounted layers") 104 historyCopy := make([]ocispec.History, 0, len(history)) 105 var l int 106 for _, h := range history { 107 if l >= len(diffs) { 108 h.EmptyLayer = true 109 } 110 if !h.EmptyLayer { 111 l++ 112 } 113 historyCopy = append(historyCopy, h) 114 } 115 history = historyCopy 116 } 117 118 if len(diffs) > historyLayers { 119 // some history items are missing. add them based on the ref metadata 120 for _, md := range refMeta[historyLayers:] { 121 history = append(history, ocispec.History{ 122 Created: &md.createdAt, 123 CreatedBy: md.description, 124 Comment: "buildkit.exporter.image.v0", 125 }) 126 } 127 } 128 129 var layerIndex int 130 for i, h := range history { 131 if !h.EmptyLayer { 132 if h.Created == nil { 133 h.Created = &refMeta[layerIndex].createdAt 134 } 135 layerIndex++ 136 } 137 history[i] = h 138 } 139 140 // Find the first new layer time. Otherwise, the history item for a first 141 // metadata command would be the creation time of a base image layer. 142 // If there is no such then the last layer with timestamp. 143 var created *time.Time 144 var noCreatedTime bool 145 for _, h := range history { 146 if h.Created != nil { 147 created = h.Created 148 if noCreatedTime { 149 break 150 } 151 } else { 152 noCreatedTime = true 153 } 154 } 155 156 // Fill in created times for all history items to be either the first new 157 // layer time or the previous layer. 158 noCreatedTime = false 159 for i, h := range history { 160 if h.Created != nil { 161 if noCreatedTime { 162 created = h.Created 163 } 164 } else { 165 noCreatedTime = true 166 h.Created = created 167 } 168 history[i] = h 169 } 170 171 return diffs, history 172 } 173 174 type refMetadata struct { 175 description string 176 createdAt time.Time 177 } 178 179 func getRefMetadata(ref cache.ImmutableRef, limit int) []refMetadata { 180 if limit <= 0 { 181 return nil 182 } 183 meta := refMetadata{ 184 description: "created by buildkit", // shouldn't be shown but don't fail build 185 createdAt: time.Now(), 186 } 187 if ref == nil { 188 return append(getRefMetadata(nil, limit-1), meta) 189 } 190 if descr := cache.GetDescription(ref.Metadata()); descr != "" { 191 meta.description = descr 192 } 193 meta.createdAt = cache.GetCreatedAt(ref.Metadata()) 194 p := ref.Parent() 195 if p != nil { 196 defer p.Release(context.TODO()) 197 } 198 return append(getRefMetadata(p, limit-1), meta) 199 } 200 201 func oneOffProgress(ctx context.Context, id string) func(err error) error { 202 pw, _, _ := progress.FromContext(ctx) 203 now := time.Now() 204 st := progress.Status{ 205 Started: &now, 206 } 207 _ = pw.Write(id, st) 208 return func(err error) error { 209 // TODO: set error on status 210 now := time.Now() 211 st.Completed = &now 212 _ = pw.Write(id, st) 213 _ = pw.Close() 214 return err 215 } 216 }