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