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

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 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  
    23  	"github.com/apex/log"
    24  	"github.com/openSUSE/umoci"
    25  	"github.com/openSUSE/umoci/oci/cas/dir"
    26  	"github.com/openSUSE/umoci/oci/casext"
    27  	"github.com/openSUSE/umoci/oci/layer"
    28  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    29  	"github.com/pkg/errors"
    30  	"github.com/urfave/cli"
    31  	"golang.org/x/net/context"
    32  )
    33  
    34  var rawUnpackCommand = uxRemap(cli.Command{
    35  	Name:  "unpack",
    36  	Usage: "unpacks a reference into a rootfs",
    37  	ArgsUsage: `--image <image-path>[:<tag>] <rootfs>
    38  
    39  Where "<image-path>" is the path to the OCI image, "<tag>" is the name of the
    40  tagged image to unpack (if not specified, defaults to "latest") and "<rootfs>"
    41  is the destination to unpack the image to.`,
    42  
    43  	// unpack reads manifest information.
    44  	Category: "image",
    45  
    46  	Flags: []cli.Flag{
    47  		cli.BoolFlag{
    48  			Name:  "keep-dirlinks",
    49  			Usage: "don't clobber underlying symlinks to directories",
    50  		},
    51  	},
    52  
    53  	Action: rawUnpack,
    54  
    55  	Before: func(ctx *cli.Context) error {
    56  		if ctx.NArg() != 1 {
    57  			return errors.Errorf("invalid number of positional arguments: expected <rootfs>")
    58  		}
    59  		if ctx.Args().First() == "" {
    60  			return errors.Errorf("rootfs path cannot be empty")
    61  		}
    62  		ctx.App.Metadata["rootfs"] = ctx.Args().First()
    63  		return nil
    64  	},
    65  })
    66  
    67  func rawUnpack(ctx *cli.Context) error {
    68  	imagePath := ctx.App.Metadata["--image-path"].(string)
    69  	fromName := ctx.App.Metadata["--image-tag"].(string)
    70  	rootfsPath := ctx.App.Metadata["rootfs"].(string)
    71  
    72  	var meta umoci.Meta
    73  	meta.Version = umoci.MetaVersion
    74  
    75  	// Parse map options.
    76  	// We need to set mappings if we're in rootless mode.
    77  	err := umoci.ParseIdmapOptions(&meta, ctx)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	meta.MapOptions.KeepDirlinks = ctx.Bool("keep-dirlinks")
    83  
    84  	// Get a reference to the CAS.
    85  	engine, err := dir.Open(imagePath)
    86  	if err != nil {
    87  		return errors.Wrap(err, "open CAS")
    88  	}
    89  	engineExt := casext.NewEngine(engine)
    90  	defer engine.Close()
    91  
    92  	fromDescriptorPaths, err := engineExt.ResolveReference(context.Background(), fromName)
    93  	if err != nil {
    94  		return errors.Wrap(err, "get descriptor")
    95  	}
    96  	if len(fromDescriptorPaths) == 0 {
    97  		return errors.Errorf("tag is not found: %s", fromName)
    98  	}
    99  	if len(fromDescriptorPaths) != 1 {
   100  		// TODO: Handle this more nicely.
   101  		return errors.Errorf("tag is ambiguous: %s", fromName)
   102  	}
   103  	meta.From = fromDescriptorPaths[0]
   104  
   105  	manifestBlob, err := engineExt.FromDescriptor(context.Background(), meta.From.Descriptor())
   106  	if err != nil {
   107  		return errors.Wrap(err, "get manifest")
   108  	}
   109  	defer manifestBlob.Close()
   110  
   111  	if manifestBlob.MediaType != ispec.MediaTypeImageManifest {
   112  		return errors.Wrap(fmt.Errorf("descriptor does not point to ispec.MediaTypeImageManifest: not implemented: %s", manifestBlob.MediaType), "invalid --image tag")
   113  	}
   114  
   115  	log.WithFields(log.Fields{
   116  		"image":  imagePath,
   117  		"rootfs": rootfsPath,
   118  		"ref":    fromName,
   119  	}).Debugf("umoci: unpacking OCI image")
   120  
   121  	// Get the manifest.
   122  	manifest, ok := manifestBlob.Data.(ispec.Manifest)
   123  	if !ok {
   124  		// Should _never_ be reached.
   125  		return errors.Errorf("[internal error] unknown manifest blob type: %s", manifestBlob.MediaType)
   126  	}
   127  
   128  	// FIXME: Currently we only support OCI layouts, not tar archives. This
   129  	//        should be fixed once the CAS engine PR is merged into
   130  	//        image-tools. https://github.com/opencontainers/image-tools/pull/5
   131  	log.Warnf("unpacking rootfs ...")
   132  	if err := layer.UnpackRootfs(context.Background(), engineExt, rootfsPath, manifest, &meta.MapOptions); err != nil {
   133  		return errors.Wrap(err, "create rootfs")
   134  	}
   135  	log.Warnf("... done")
   136  
   137  	log.Warnf("unpacked image rootfs: %s", rootfsPath)
   138  	return nil
   139  }