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