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 }