github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/cmd/umoci/unpack.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  	"fmt"
    22  	"os"
    23  	"strings"
    24  
    25  	"github.com/apex/log"
    26  	"github.com/openSUSE/umoci"
    27  	"github.com/openSUSE/umoci/oci/cas/dir"
    28  	"github.com/openSUSE/umoci/oci/casext"
    29  	"github.com/openSUSE/umoci/oci/layer"
    30  	"github.com/openSUSE/umoci/pkg/fseval"
    31  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    32  	"github.com/pkg/errors"
    33  	"github.com/urfave/cli"
    34  	"golang.org/x/net/context"
    35  )
    36  
    37  var unpackCommand = uxRemap(cli.Command{
    38  	Name:  "unpack",
    39  	Usage: "unpacks a reference into an OCI runtime bundle",
    40  	ArgsUsage: `--image <image-path>[:<tag>] <bundle>
    41  
    42  Where "<image-path>" is the path to the OCI image, "<tag>" is the name of the
    43  tagged image to unpack (if not specified, defaults to "latest") and "<bundle>"
    44  is the destination to unpack the image to.
    45  
    46  It should be noted that this is not the same as oci-create-runtime-bundle,
    47  because this command also will create an mtree specification to allow for layer
    48  creation with umoci-repack(1).`,
    49  
    50  	// unpack reads manifest information.
    51  	Category: "image",
    52  
    53  	Flags: []cli.Flag{
    54  		cli.BoolFlag{
    55  			Name:  "keep-dirlinks",
    56  			Usage: "don't clobber underlying symlinks to directories",
    57  		},
    58  	},
    59  
    60  	Action: unpack,
    61  
    62  	Before: func(ctx *cli.Context) error {
    63  		if ctx.NArg() != 1 {
    64  			return errors.Errorf("invalid number of positional arguments: expected <bundle>")
    65  		}
    66  		if ctx.Args().First() == "" {
    67  			return errors.Errorf("bundle path cannot be empty")
    68  		}
    69  		ctx.App.Metadata["bundle"] = ctx.Args().First()
    70  		return nil
    71  	},
    72  })
    73  
    74  func unpack(ctx *cli.Context) error {
    75  	imagePath := ctx.App.Metadata["--image-path"].(string)
    76  	fromName := ctx.App.Metadata["--image-tag"].(string)
    77  	bundlePath := ctx.App.Metadata["bundle"].(string)
    78  
    79  	var meta umoci.Meta
    80  	meta.Version = umoci.MetaVersion
    81  
    82  	// Parse and set up the mapping options.
    83  	err := umoci.ParseIdmapOptions(&meta, ctx)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	meta.MapOptions.KeepDirlinks = ctx.Bool("keep-dirlinks")
    89  
    90  	// Get a reference to the CAS.
    91  	engine, err := dir.Open(imagePath)
    92  	if err != nil {
    93  		return errors.Wrap(err, "open CAS")
    94  	}
    95  	engineExt := casext.NewEngine(engine)
    96  	defer engine.Close()
    97  
    98  	fromDescriptorPaths, err := engineExt.ResolveReference(context.Background(), fromName)
    99  	if err != nil {
   100  		return errors.Wrap(err, "get descriptor")
   101  	}
   102  	if len(fromDescriptorPaths) == 0 {
   103  		return errors.Errorf("tag is not found: %s", fromName)
   104  	}
   105  	if len(fromDescriptorPaths) != 1 {
   106  		// TODO: Handle this more nicely.
   107  		return errors.Errorf("tag is ambiguous: %s", fromName)
   108  	}
   109  	meta.From = fromDescriptorPaths[0]
   110  
   111  	manifestBlob, err := engineExt.FromDescriptor(context.Background(), meta.From.Descriptor())
   112  	if err != nil {
   113  		return errors.Wrap(err, "get manifest")
   114  	}
   115  	defer manifestBlob.Close()
   116  
   117  	if manifestBlob.MediaType != ispec.MediaTypeImageManifest {
   118  		return errors.Wrap(fmt.Errorf("descriptor does not point to ispec.MediaTypeImageManifest: not implemented: %s", manifestBlob.MediaType), "invalid --image tag")
   119  	}
   120  
   121  	mtreeName := strings.Replace(meta.From.Descriptor().Digest.String(), ":", "_", 1)
   122  	log.WithFields(log.Fields{
   123  		"image":  imagePath,
   124  		"bundle": bundlePath,
   125  		"ref":    fromName,
   126  		"rootfs": layer.RootfsName,
   127  	}).Debugf("umoci: unpacking OCI image")
   128  
   129  	// Get the manifest.
   130  	manifest, ok := manifestBlob.Data.(ispec.Manifest)
   131  	if !ok {
   132  		// Should _never_ be reached.
   133  		return errors.Errorf("[internal error] unknown manifest blob type: %s", manifestBlob.MediaType)
   134  	}
   135  
   136  	// Unpack the runtime bundle.
   137  	if err := os.MkdirAll(bundlePath, 0755); err != nil {
   138  		return errors.Wrap(err, "create bundle path")
   139  	}
   140  	// XXX: We should probably defer os.RemoveAll(bundlePath).
   141  
   142  	// FIXME: Currently we only support OCI layouts, not tar archives. This
   143  	//        should be fixed once the CAS engine PR is merged into
   144  	//        image-tools. https://github.com/opencontainers/image-tools/pull/5
   145  	log.Info("unpacking bundle ...")
   146  	if err := layer.UnpackManifest(context.Background(), engineExt, bundlePath, manifest, &meta.MapOptions); err != nil {
   147  		return errors.Wrap(err, "create runtime bundle")
   148  	}
   149  	log.Info("... done")
   150  
   151  	fsEval := fseval.DefaultFsEval
   152  	if meta.MapOptions.Rootless {
   153  		fsEval = fseval.RootlessFsEval
   154  	}
   155  
   156  	if err := umoci.GenerateBundleManifest(mtreeName, bundlePath, fsEval); err != nil {
   157  		return errors.Wrap(err, "write mtree")
   158  	}
   159  
   160  	log.WithFields(log.Fields{
   161  		"version":     meta.Version,
   162  		"from":        meta.From,
   163  		"map_options": meta.MapOptions,
   164  	}).Debugf("umoci: saving Meta metadata")
   165  
   166  	if err := umoci.WriteBundleMeta(bundlePath, meta); err != nil {
   167  		return errors.Wrap(err, "write umoci.json metadata")
   168  	}
   169  
   170  	log.Infof("unpacked image bundle: %s", bundlePath)
   171  	return nil
   172  }