github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/cmd/umoci/raw-add-layer.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016, 2017, 2018 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  	"os"
    22  	"time"
    23  
    24  	"github.com/apex/log"
    25  	"github.com/openSUSE/umoci"
    26  	"github.com/openSUSE/umoci/mutate"
    27  	"github.com/openSUSE/umoci/oci/cas/dir"
    28  	"github.com/openSUSE/umoci/oci/casext"
    29  	igen "github.com/openSUSE/umoci/oci/config/generate"
    30  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    31  	"github.com/pkg/errors"
    32  	"github.com/urfave/cli"
    33  	"golang.org/x/net/context"
    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  	created := time.Now()
   128  	history := ispec.History{
   129  		Author:     imageMeta.Author,
   130  		Comment:    "",
   131  		Created:    &created,
   132  		CreatedBy:  "umoci raw add-layer", // XXX: Should we append argv to this?
   133  		EmptyLayer: false,
   134  	}
   135  
   136  	if val, ok := ctx.App.Metadata["--history.author"]; ok {
   137  		history.Author = val.(string)
   138  	}
   139  	if val, ok := ctx.App.Metadata["--history.comment"]; ok {
   140  		history.Comment = val.(string)
   141  	}
   142  	if val, ok := ctx.App.Metadata["--history.created"]; ok {
   143  		created, err := time.Parse(igen.ISO8601, val.(string))
   144  		if err != nil {
   145  			return errors.Wrap(err, "parsing --history.created")
   146  		}
   147  		history.Created = &created
   148  	}
   149  	if val, ok := ctx.App.Metadata["--history.created_by"]; ok {
   150  		history.CreatedBy = val.(string)
   151  	}
   152  
   153  	// TODO: We should add a flag to allow for a new layer to be made
   154  	//       non-distributable.
   155  	if err := mutator.Add(context.Background(), newLayer, history); err != nil {
   156  		return errors.Wrap(err, "add diff layer")
   157  	}
   158  
   159  	newDescriptorPath, err := mutator.Commit(context.Background())
   160  	if err != nil {
   161  		return errors.Wrap(err, "commit mutated image")
   162  	}
   163  
   164  	log.Infof("new image manifest created: %s->%s", newDescriptorPath.Root().Digest, newDescriptorPath.Descriptor().Digest)
   165  
   166  	if err := engineExt.UpdateReference(context.Background(), tagName, newDescriptorPath.Root()); err != nil {
   167  		return errors.Wrap(err, "add new tag")
   168  	}
   169  
   170  	log.Infof("created new tag for image manifest: %s", tagName)
   171  	return nil
   172  }