github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/repack.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016-2020 SUSE LLC 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package umoci 19 20 import ( 21 "context" 22 "os" 23 "path/filepath" 24 "strings" 25 26 "github.com/apex/log" 27 ispec "github.com/opencontainers/image-spec/specs-go/v1" 28 "github.com/opencontainers/umoci/mutate" 29 "github.com/opencontainers/umoci/oci/casext" 30 "github.com/opencontainers/umoci/oci/layer" 31 "github.com/opencontainers/umoci/pkg/fseval" 32 "github.com/opencontainers/umoci/pkg/mtreefilter" 33 "github.com/pkg/errors" 34 "github.com/vbatts/go-mtree" 35 ) 36 37 // Repack repacks a bundle into an image adding a new layer for the changed 38 // data in the bundle. 39 func Repack(engineExt casext.Engine, tagName string, bundlePath string, meta Meta, history *ispec.History, filters []mtreefilter.FilterFunc, refreshBundle bool, mutator *mutate.Mutator) error { 40 mtreeName := strings.Replace(meta.From.Descriptor().Digest.String(), ":", "_", 1) 41 mtreePath := filepath.Join(bundlePath, mtreeName+".mtree") 42 fullRootfsPath := filepath.Join(bundlePath, layer.RootfsName) 43 44 log.WithFields(log.Fields{ 45 "bundle": bundlePath, 46 "rootfs": layer.RootfsName, 47 "mtree": mtreePath, 48 }).Debugf("umoci: repacking OCI image") 49 50 mfh, err := os.Open(mtreePath) 51 if err != nil { 52 return errors.Wrap(err, "open mtree") 53 } 54 defer mfh.Close() 55 56 spec, err := mtree.ParseSpec(mfh) 57 if err != nil { 58 return errors.Wrap(err, "parse mtree") 59 } 60 61 log.WithFields(log.Fields{ 62 "keywords": MtreeKeywords, 63 }).Debugf("umoci: parsed mtree spec") 64 65 fsEval := fseval.Default 66 if meta.MapOptions.Rootless { 67 fsEval = fseval.Rootless 68 } 69 70 log.Info("computing filesystem diff ...") 71 diffs, err := mtree.Check(fullRootfsPath, spec, MtreeKeywords, fsEval) 72 if err != nil { 73 return errors.Wrap(err, "check mtree") 74 } 75 log.Info("... done") 76 77 log.WithFields(log.Fields{ 78 "ndiff": len(diffs), 79 }).Debugf("umoci: checked mtree spec") 80 81 allFilters := append(filters, mtreefilter.SimplifyFilter(diffs)) 82 diffs = mtreefilter.FilterDeltas(diffs, allFilters...) 83 84 if len(diffs) == 0 { 85 config, err := mutator.Config(context.Background()) 86 if err != nil { 87 return err 88 } 89 90 imageMeta, err := mutator.Meta(context.Background()) 91 if err != nil { 92 return err 93 } 94 95 annotations, err := mutator.Annotations(context.Background()) 96 if err != nil { 97 return err 98 } 99 100 err = mutator.Set(context.Background(), config.Config, imageMeta, annotations, history) 101 if err != nil { 102 return err 103 } 104 } else { 105 packOptions := layer.RepackOptions{MapOptions: meta.MapOptions} 106 if meta.WhiteoutMode == layer.OverlayFSWhiteout { 107 packOptions.TranslateOverlayWhiteouts = true 108 } 109 reader, err := layer.GenerateLayer(fullRootfsPath, diffs, &packOptions) 110 if err != nil { 111 return errors.Wrap(err, "generate diff layer") 112 } 113 defer reader.Close() 114 115 // TODO: We should add a flag to allow for a new layer to be made 116 // non-distributable. 117 if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, reader, history, mutate.GzipCompressor, nil); err != nil { 118 return errors.Wrap(err, "add diff layer") 119 } 120 } 121 122 newDescriptorPath, err := mutator.Commit(context.Background()) 123 if err != nil { 124 return errors.Wrap(err, "commit mutated image") 125 } 126 127 log.Infof("new image manifest created: %s->%s", newDescriptorPath.Root().Digest, newDescriptorPath.Descriptor().Digest) 128 129 if err := engineExt.UpdateReference(context.Background(), tagName, newDescriptorPath.Root()); err != nil { 130 return errors.Wrap(err, "add new tag") 131 } 132 133 log.Infof("created new tag for image manifest: %s", tagName) 134 135 if refreshBundle { 136 newMtreeName := strings.Replace(newDescriptorPath.Descriptor().Digest.String(), ":", "_", 1) 137 if err := GenerateBundleManifest(newMtreeName, bundlePath, fsEval); err != nil { 138 return errors.Wrap(err, "write mtree metadata") 139 } 140 if err := os.Remove(mtreePath); err != nil { 141 return errors.Wrap(err, "remove old mtree metadata") 142 } 143 meta.From = newDescriptorPath 144 if err := WriteBundleMeta(bundlePath, meta); err != nil { 145 return errors.Wrap(err, "write umoci.json metadata") 146 } 147 } 148 return nil 149 }