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  }