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