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