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 }