github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/cmd/umoci/insert.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016, 2017, 2018 SUSE LLC. 4 * Copyright (C) 2018 Cisco 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 "github.com/openSUSE/umoci" 27 "github.com/openSUSE/umoci/mutate" 28 "github.com/openSUSE/umoci/oci/cas/dir" 29 "github.com/openSUSE/umoci/oci/casext" 30 igen "github.com/openSUSE/umoci/oci/config/generate" 31 "github.com/openSUSE/umoci/oci/layer" 32 ispec "github.com/opencontainers/image-spec/specs-go/v1" 33 "github.com/pkg/errors" 34 "github.com/urfave/cli" 35 ) 36 37 var insertCommand = uxRemap(uxHistory(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 tagName := ctx.App.Metadata["--image-tag"].(string) 115 sourcePath := ctx.App.Metadata["--source-path"].(string) 116 targetPath := ctx.App.Metadata["--target-path"].(string) 117 118 // Get a reference to the CAS. 119 engine, err := dir.Open(imagePath) 120 if err != nil { 121 return errors.Wrap(err, "open CAS") 122 } 123 engineExt := casext.NewEngine(engine) 124 defer engine.Close() 125 126 descriptorPaths, err := engineExt.ResolveReference(context.Background(), tagName) 127 if err != nil { 128 return errors.Wrap(err, "get descriptor") 129 } 130 if len(descriptorPaths) == 0 { 131 return errors.Errorf("tag not found: %s", tagName) 132 } 133 if len(descriptorPaths) != 1 { 134 // TODO: Handle this more nicely. 135 return errors.Errorf("tag is ambiguous: %s", tagName) 136 } 137 138 // Create the mutator. 139 mutator, err := mutate.New(engine, descriptorPaths[0]) 140 if err != nil { 141 return errors.Wrap(err, "create mutator for base image") 142 } 143 144 var meta umoci.Meta 145 meta.Version = umoci.MetaVersion 146 147 // Parse and set up the mapping options. 148 err = umoci.ParseIdmapOptions(&meta, ctx) 149 if err != nil { 150 return err 151 } 152 153 reader := layer.GenerateInsertLayer(sourcePath, targetPath, ctx.IsSet("opaque"), &meta.MapOptions) 154 defer reader.Close() 155 156 created := time.Now() 157 history := ispec.History{ 158 Comment: "", 159 Created: &created, 160 CreatedBy: "umoci insert", // XXX: Should we append argv to this? 161 EmptyLayer: false, 162 } 163 164 if val, ok := ctx.App.Metadata["--history.author"]; ok { 165 history.Author = val.(string) 166 } 167 if val, ok := ctx.App.Metadata["--history.comment"]; ok { 168 history.Comment = val.(string) 169 } 170 if val, ok := ctx.App.Metadata["--history.created"]; ok { 171 created, err := time.Parse(igen.ISO8601, val.(string)) 172 if err != nil { 173 return errors.Wrap(err, "parsing --history.created") 174 } 175 history.Created = &created 176 } 177 if val, ok := ctx.App.Metadata["--history.created_by"]; ok { 178 history.CreatedBy = val.(string) 179 } 180 181 // TODO: We should add a flag to allow for a new layer to be made 182 // non-distributable. 183 if err := mutator.Add(context.Background(), reader, history); err != nil { 184 return errors.Wrap(err, "add diff layer") 185 } 186 187 newDescriptorPath, err := mutator.Commit(context.Background()) 188 if err != nil { 189 return errors.Wrap(err, "commit mutated image") 190 } 191 192 log.Infof("new image manifest created: %s->%s", newDescriptorPath.Root().Digest, newDescriptorPath.Descriptor().Digest) 193 194 if err := engineExt.UpdateReference(context.Background(), tagName, newDescriptorPath.Root()); err != nil { 195 return errors.Wrap(err, "add new tag") 196 } 197 log.Infof("updated tag for image manifest: %s", tagName) 198 return nil 199 }