github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/raw-add-layer.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016-2020 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 "context" 22 "os" 23 "time" 24 25 "github.com/apex/log" 26 ispec "github.com/opencontainers/image-spec/specs-go/v1" 27 "github.com/opencontainers/umoci" 28 "github.com/opencontainers/umoci/mutate" 29 "github.com/opencontainers/umoci/oci/cas/dir" 30 "github.com/opencontainers/umoci/oci/casext" 31 igen "github.com/opencontainers/umoci/oci/config/generate" 32 "github.com/pkg/errors" 33 "github.com/urfave/cli" 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 var history *ispec.History 128 if !ctx.Bool("no-history") { 129 created := time.Now() 130 history = &ispec.History{ 131 Author: imageMeta.Author, 132 Comment: "", 133 Created: &created, 134 CreatedBy: "umoci raw add-layer", // XXX: Should we append argv to this? 135 EmptyLayer: false, 136 } 137 138 if ctx.IsSet("history.author") { 139 history.Author = ctx.String("history.author") 140 } 141 if ctx.IsSet("history.comment") { 142 history.Comment = ctx.String("history.comment") 143 } 144 if ctx.IsSet("history.created") { 145 created, err := time.Parse(igen.ISO8601, ctx.String("history.created")) 146 if err != nil { 147 return errors.Wrap(err, "parsing --history.created") 148 } 149 history.Created = &created 150 } 151 if ctx.IsSet("history.created_by") { 152 history.CreatedBy = ctx.String("history.created_by") 153 } 154 } 155 156 // TODO: We should add a flag to allow for a new layer to be made 157 // non-distributable. 158 if _, err := mutator.Add(context.Background(), ispec.MediaTypeImageLayer, newLayer, history, mutate.GzipCompressor, nil); err != nil { 159 return errors.Wrap(err, "add diff layer") 160 } 161 162 newDescriptorPath, err := mutator.Commit(context.Background()) 163 if err != nil { 164 return errors.Wrap(err, "commit mutated image") 165 } 166 167 log.Infof("new image manifest created: %s->%s", newDescriptorPath.Root().Digest, newDescriptorPath.Descriptor().Digest) 168 169 if err := engineExt.UpdateReference(context.Background(), tagName, newDescriptorPath.Root()); err != nil { 170 return errors.Wrap(err, "add new tag") 171 } 172 173 log.Infof("created new tag for image manifest: %s", tagName) 174 return nil 175 }