github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/raw-add-layer.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 main
    19  
    20  import (
    21  	"context"
    22  	"os"
    23  	"time"
    24  
    25  	"github.com/apex/log"
    26  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    27  	"github.com/opencontainers/umoci"
    28  	"github.com/opencontainers/umoci/mutate"
    29  	"github.com/opencontainers/umoci/oci/cas/dir"
    30  	"github.com/opencontainers/umoci/oci/casext"
    31  	igen "github.com/opencontainers/umoci/oci/config/generate"
    32  	"github.com/pkg/errors"
    33  	"github.com/urfave/cli"
    34  )
    35  
    36  var rawAddLayerCommand = uxHistory(uxTag(cli.Command{
    37  	Name:  "add-layer",
    38  	Usage: "add a layer archive verbatim to an image",
    39  	ArgsUsage: `--image <image-path>[:<tag>] <new-layer.tar>
    40  
    41  Where "<image-path>" is the path to the OCI image, "<tag>" is the name of the
    42  tagged image to modify (if not specified, defaults to "latest"),
    43  "<new-layer.tar>" is the new layer to add (it must be uncompressed).
    44  
    45  Note that using your own layer archives may result in strange behaviours (for
    46  instance, you may need to use --keep-dirlink with umoci-unpack(1) in order to
    47  avoid breaking certain entries).
    48  
    49  At the moment, umoci-raw-add-layer(1) will only *append* layers to an image and
    50  only supports uncompressed archives.`,
    51  
    52  	// unpack reads manifest information.
    53  	Category: "image",
    54  
    55  	Action: rawAddLayer,
    56  
    57  	Before: func(ctx *cli.Context) error {
    58  		if ctx.NArg() != 1 {
    59  			return errors.Errorf("invalid number of positional arguments: expected <newlayer.tar>")
    60  		}
    61  		if ctx.Args().First() == "" {
    62  			return errors.Errorf("<new-layer.tar> path cannot be empty")
    63  		}
    64  		ctx.App.Metadata["newlayer"] = ctx.Args().First()
    65  		return nil
    66  	},
    67  }))
    68  
    69  func rawAddLayer(ctx *cli.Context) error {
    70  	imagePath := ctx.App.Metadata["--image-path"].(string)
    71  	fromName := ctx.App.Metadata["--image-tag"].(string)
    72  	newLayerPath := ctx.App.Metadata["newlayer"].(string)
    73  
    74  	// Overide the from tag by default, otherwise use the one specified.
    75  	tagName := fromName
    76  	if overrideTagName, ok := ctx.App.Metadata["--tag"]; ok {
    77  		tagName = overrideTagName.(string)
    78  	}
    79  
    80  	var meta umoci.Meta
    81  	meta.Version = umoci.MetaVersion
    82  
    83  	// Get a reference to the CAS.
    84  	engine, err := dir.Open(imagePath)
    85  	if err != nil {
    86  		return errors.Wrap(err, "open CAS")
    87  	}
    88  	engineExt := casext.NewEngine(engine)
    89  	defer engine.Close()
    90  
    91  	fromDescriptorPaths, err := engineExt.ResolveReference(context.Background(), fromName)
    92  	if err != nil {
    93  		return errors.Wrap(err, "get descriptor")
    94  	}
    95  	if len(fromDescriptorPaths) == 0 {
    96  		return errors.Errorf("tag not found: %s", fromName)
    97  	}
    98  	if len(fromDescriptorPaths) != 1 {
    99  		// TODO: Handle this more nicely.
   100  		return errors.Errorf("tag is ambiguous: %s", fromName)
   101  	}
   102  	meta.From = fromDescriptorPaths[0]
   103  
   104  	// Create the mutator.
   105  	mutator, err := mutate.New(engine, meta.From)
   106  	if err != nil {
   107  		return errors.Wrap(err, "create mutator for base image")
   108  	}
   109  
   110  	newLayer, err := os.Open(newLayerPath)
   111  	if err != nil {
   112  		return errors.Wrap(err, "open new layer archive")
   113  	}
   114  	if fi, err := newLayer.Stat(); err != nil {
   115  		return errors.Wrap(err, "stat new layer archive")
   116  	} else if fi.IsDir() {
   117  		return errors.Errorf("new layer archive is a directory")
   118  	}
   119  	// TODO: Verify that the layer is actually uncompressed.
   120  	defer newLayer.Close()
   121  
   122  	imageMeta, err := mutator.Meta(context.Background())
   123  	if err != nil {
   124  		return errors.Wrap(err, "get image metadata")
   125  	}
   126  
   127  	var history *ispec.History
   128  	if !ctx.Bool("no-history") {
   129  		created := time.Now()
   130  		history = &ispec.History{
   131  			Author:     imageMeta.Author,
   132  			Comment:    "",
   133  			Created:    &created,
   134  			CreatedBy:  "umoci raw add-layer", // XXX: Should we append argv to this?
   135  			EmptyLayer: false,
   136  		}
   137  
   138  		if ctx.IsSet("history.author") {
   139  			history.Author = ctx.String("history.author")
   140  		}
   141  		if ctx.IsSet("history.comment") {
   142  			history.Comment = ctx.String("history.comment")
   143  		}
   144  		if ctx.IsSet("history.created") {
   145  			created, err := time.Parse(igen.ISO8601, ctx.String("history.created"))
   146  			if err != nil {
   147  				return errors.Wrap(err, "parsing --history.created")
   148  			}
   149  			history.Created = &created
   150  		}
   151  		if ctx.IsSet("history.created_by") {
   152  			history.CreatedBy = ctx.String("history.created_by")
   153  		}
   154  	}
   155  
   156  	// TODO: We should add a flag to allow for a new layer to be made
   157  	//       non-distributable.
   158  	if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, newLayer, history, mutate.GzipCompressor, nil); err != nil {
   159  		return errors.Wrap(err, "add diff layer")
   160  	}
   161  
   162  	newDescriptorPath, err := mutator.Commit(context.Background())
   163  	if err != nil {
   164  		return errors.Wrap(err, "commit mutated image")
   165  	}
   166  
   167  	log.Infof("new image manifest created: %s->%s", newDescriptorPath.Root().Digest, newDescriptorPath.Descriptor().Digest)
   168  
   169  	if err := engineExt.UpdateReference(context.Background(), tagName, newDescriptorPath.Root()); err != nil {
   170  		return errors.Wrap(err, "add new tag")
   171  	}
   172  
   173  	log.Infof("created new tag for image manifest: %s", tagName)
   174  	return nil
   175  }