github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/insert.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 SUSE LLC
     4   * Copyright (C) 2018 Cisco Systems
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package main
    20  
    21  import (
    22  	"context"
    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/opencontainers/umoci/oci/layer"
    33  	"github.com/pkg/errors"
    34  	"github.com/urfave/cli"
    35  )
    36  
    37  var insertCommand = uxRemap(uxHistory(uxTag(cli.Command{
    38  	Name:  "insert",
    39  	Usage: "insert content into an OCI image",
    40  	ArgsUsage: `--image <image-path>[:<tag>] [--opaque] <source> <target>
    41                                    --image <image-path>[:<tag>] [--whiteout] <target>
    42  
    43  Where "<image-path>" is the path to the OCI image, and "<tag>" is the name of
    44  the tag that the content wil be inserted into (if not specified, defaults to
    45  "latest").
    46  
    47  The path at "<source>" is added to the image with the given "<target>" name.
    48  If "--whiteout" is specified, rather than inserting content into the image, a
    49  removal entry for "<target>" is inserted instead.
    50  
    51  If "--opaque" is specified then any paths below "<target>" (assuming it is a
    52  directory) from previous layers will no longer be present. Only the contents
    53  inserted by this command will be visible. This can be used to replace an entire
    54  directory, while the default behaviour merges the old contents with the new.
    55  
    56  Note that this command works by creating a new layer, so this should not be
    57  used to remove (or replace) secrets from an already-built image. See
    58  umoci-config(1) and --config.volume for how to achieve this correctly.
    59  
    60  Some examples:
    61  	umoci insert --image oci:foo mybinary /usr/bin/mybinary
    62  	umoci insert --image oci:foo myconfigdir /etc/myconfigdir
    63  	umoci insert --image oci:foo --opaque myoptdir /opt
    64  	umoci insert --image oci:foo --whiteout /some/old/dir
    65  `,
    66  
    67  	Category: "image",
    68  
    69  	Action: insert,
    70  
    71  	Flags: []cli.Flag{
    72  		cli.BoolFlag{
    73  			Name:  "whiteout",
    74  			Usage: "insert a 'removal entry' for the given path",
    75  		},
    76  		cli.BoolFlag{
    77  			Name:  "opaque",
    78  			Usage: "mask any previous entries in the target directory",
    79  		},
    80  	},
    81  
    82  	Before: func(ctx *cli.Context) error {
    83  		// This command is quite weird because we need to support two different
    84  		// positional-argument numbers. Awesome.
    85  		numArgs := 2
    86  		if ctx.IsSet("whiteout") {
    87  			numArgs = 1
    88  		}
    89  		if ctx.NArg() != numArgs {
    90  			return errors.Errorf("invalid number of positional arguments: expected %d", numArgs)
    91  		}
    92  		for idx, args := range ctx.Args() {
    93  			if args == "" {
    94  				return errors.Errorf("invalid positional argument %d: arguments cannot be empty", idx)
    95  			}
    96  		}
    97  
    98  		// Figure out the arguments.
    99  		var sourcePath, targetPath string
   100  		targetPath = ctx.Args()[0]
   101  		if !ctx.IsSet("whiteout") {
   102  			sourcePath = targetPath
   103  			targetPath = ctx.Args()[1]
   104  		}
   105  
   106  		ctx.App.Metadata["--source-path"] = sourcePath
   107  		ctx.App.Metadata["--target-path"] = targetPath
   108  		return nil
   109  	},
   110  })))
   111  
   112  func insert(ctx *cli.Context) error {
   113  	imagePath := ctx.App.Metadata["--image-path"].(string)
   114  	fromName := ctx.App.Metadata["--image-tag"].(string)
   115  	sourcePath := ctx.App.Metadata["--source-path"].(string)
   116  	targetPath := ctx.App.Metadata["--target-path"].(string)
   117  
   118  	// By default we clobber the old tag.
   119  	tagName := fromName
   120  	if val, ok := ctx.App.Metadata["--tag"]; ok {
   121  		tagName = val.(string)
   122  	}
   123  
   124  	// Get a reference to the CAS.
   125  	engine, err := dir.Open(imagePath)
   126  	if err != nil {
   127  		return errors.Wrap(err, "open CAS")
   128  	}
   129  	engineExt := casext.NewEngine(engine)
   130  	defer engine.Close()
   131  
   132  	descriptorPaths, err := engineExt.ResolveReference(context.Background(), fromName)
   133  	if err != nil {
   134  		return errors.Wrap(err, "get descriptor")
   135  	}
   136  	if len(descriptorPaths) == 0 {
   137  		return errors.Errorf("tag not found: %s", fromName)
   138  	}
   139  	if len(descriptorPaths) != 1 {
   140  		// TODO: Handle this more nicely.
   141  		return errors.Errorf("tag is ambiguous: %s", fromName)
   142  	}
   143  
   144  	// Create the mutator.
   145  	mutator, err := mutate.New(engine, descriptorPaths[0])
   146  	if err != nil {
   147  		return errors.Wrap(err, "create mutator for base image")
   148  	}
   149  
   150  	var meta umoci.Meta
   151  	meta.Version = umoci.MetaVersion
   152  
   153  	// Parse and set up the mapping options.
   154  	err = umoci.ParseIdmapOptions(&meta, ctx)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	packOptions := layer.RepackOptions{MapOptions: meta.MapOptions}
   160  	reader := layer.GenerateInsertLayer(sourcePath, targetPath, ctx.IsSet("opaque"), &packOptions)
   161  	defer reader.Close()
   162  
   163  	var history *ispec.History
   164  	if !ctx.Bool("no-history") {
   165  		created := time.Now()
   166  		history = &ispec.History{
   167  			Comment:    "",
   168  			Created:    &created,
   169  			CreatedBy:  "umoci insert", // XXX: Should we append argv to this?
   170  			EmptyLayer: false,
   171  		}
   172  
   173  		if ctx.IsSet("history.author") {
   174  			history.Author = ctx.String("history.author")
   175  		}
   176  		if ctx.IsSet("history.comment") {
   177  			history.Comment = ctx.String("history.comment")
   178  		}
   179  		if ctx.IsSet("history.created") {
   180  			created, err := time.Parse(igen.ISO8601, ctx.String("history.created"))
   181  			if err != nil {
   182  				return errors.Wrap(err, "parsing --history.created")
   183  			}
   184  			history.Created = &created
   185  		}
   186  		if ctx.IsSet("history.created_by") {
   187  			history.CreatedBy = ctx.String("history.created_by")
   188  		}
   189  	}
   190  
   191  	// TODO: We should add a flag to allow for a new layer to be made
   192  	//       non-distributable.
   193  	if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, reader, history, mutate.GzipCompressor, nil); err != nil {
   194  		return errors.Wrap(err, "add diff layer")
   195  	}
   196  
   197  	newDescriptorPath, err := mutator.Commit(context.Background())
   198  	if err != nil {
   199  		return errors.Wrap(err, "commit mutated image")
   200  	}
   201  
   202  	log.Infof("new image manifest created: %s->%s", newDescriptorPath.Root().Digest, newDescriptorPath.Descriptor().Digest)
   203  
   204  	if err := engineExt.UpdateReference(context.Background(), tagName, newDescriptorPath.Root()); err != nil {
   205  		return errors.Wrap(err, "add new tag")
   206  	}
   207  	log.Infof("updated tag for image manifest: %s", tagName)
   208  	return nil
   209  }