github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/tag.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 "fmt" 23 24 "github.com/apex/log" 25 "github.com/opencontainers/umoci/oci/cas/dir" 26 "github.com/opencontainers/umoci/oci/casext" 27 "github.com/pkg/errors" 28 "github.com/urfave/cli" 29 ) 30 31 var tagAddCommand = cli.Command{ 32 Name: "tag", 33 Usage: "creates a new tag in an OCI image", 34 ArgsUsage: `--image <image-path>[:<tag>] <new-tag> 35 36 Where "<image-path>" is the path to the OCI image, "<tag>" is the old name of 37 the tag and "<new-tag>" is the new name of the tag.`, 38 39 // tag modifies an image layout. 40 Category: "image", 41 42 Action: tagAdd, 43 44 Before: func(ctx *cli.Context) error { 45 if ctx.NArg() != 1 { 46 return errors.Errorf("invalid number of positional arguments: expected <new-tag>") 47 } 48 if ctx.Args().First() == "" { 49 return errors.Errorf("new tag cannot be empty") 50 } 51 if !casext.IsValidReferenceName(ctx.Args().First()) { 52 return errors.Errorf("new tag is an invalid reference") 53 } 54 ctx.App.Metadata["new-tag"] = ctx.Args().First() 55 return nil 56 }, 57 } 58 59 func tagAdd(ctx *cli.Context) error { 60 imagePath := ctx.App.Metadata["--image-path"].(string) 61 fromName := ctx.App.Metadata["--image-tag"].(string) 62 tagName := ctx.App.Metadata["new-tag"].(string) 63 64 // Get a reference to the CAS. 65 engine, err := dir.Open(imagePath) 66 if err != nil { 67 return errors.Wrap(err, "open CAS") 68 } 69 engineExt := casext.NewEngine(engine) 70 defer engine.Close() 71 72 // Get original descriptor. 73 descriptorPaths, err := engineExt.ResolveReference(context.Background(), fromName) 74 if err != nil { 75 return errors.Wrap(err, "get descriptor") 76 } 77 if len(descriptorPaths) == 0 { 78 return errors.Errorf("tag not found: %s", fromName) 79 } 80 if len(descriptorPaths) != 1 { 81 // TODO: Handle this more nicely. 82 return errors.Errorf("tag is ambiguous: %s", fromName) 83 } 84 descriptor := descriptorPaths[0].Descriptor() 85 86 // Add it. 87 if err := engineExt.UpdateReference(context.Background(), tagName, descriptor); err != nil { 88 return errors.Wrap(err, "put reference") 89 } 90 91 log.Infof("created new tag: %q -> %q", tagName, fromName) 92 return nil 93 } 94 95 var tagRemoveCommand = cli.Command{ 96 Name: "remove", 97 Aliases: []string{"rm"}, 98 Usage: "removes a tag from an OCI image", 99 ArgsUsage: `--image <image-path>[:<tag>] 100 101 102 Where "<image-path>" is the path to the OCI image, "<tag>" is the name of the 103 tag to remove.`, 104 105 // tag modifies an image layout. 106 Category: "image", 107 108 Before: func(ctx *cli.Context) error { 109 if ctx.NArg() != 0 { 110 return errors.Errorf("invalid number of positional arguments: expected none") 111 } 112 return nil 113 }, 114 115 Action: tagRemove, 116 } 117 118 func tagRemove(ctx *cli.Context) error { 119 imagePath := ctx.App.Metadata["--image-path"].(string) 120 tagName := ctx.App.Metadata["--image-tag"].(string) 121 122 // Get a reference to the CAS. 123 engine, err := dir.Open(imagePath) 124 if err != nil { 125 return errors.Wrap(err, "open CAS") 126 } 127 engineExt := casext.NewEngine(engine) 128 defer engine.Close() 129 130 // Remove it. 131 if err := engineExt.DeleteReference(context.Background(), tagName); err != nil { 132 return errors.Wrap(err, "delete reference") 133 } 134 135 log.Infof("removed tag: %s", tagName) 136 return nil 137 } 138 139 var tagListCommand = cli.Command{ 140 Name: "list", 141 Aliases: []string{"ls"}, 142 Usage: "lists the set of tags in an OCI layout", 143 ArgsUsage: `--layout <image-path> 144 145 Where "<image-path>" is the path to the OCI layout. 146 147 Gives the full list of tags in an OCI layout, with each tag name on a single 148 line. See umoci-stat(1) to get more information about each tagged image.`, 149 150 // tag modifies an image layout. 151 Category: "layout", 152 153 Before: func(ctx *cli.Context) error { 154 if ctx.NArg() != 0 { 155 return errors.Errorf("invalid number of positional arguments: expected none") 156 } 157 return nil 158 }, 159 160 Action: tagList, 161 } 162 163 func tagList(ctx *cli.Context) error { 164 imagePath := ctx.App.Metadata["--image-path"].(string) 165 166 // Get a reference to the CAS. 167 engine, err := dir.Open(imagePath) 168 if err != nil { 169 return errors.Wrap(err, "open CAS") 170 } 171 engineExt := casext.NewEngine(engine) 172 defer engine.Close() 173 174 names, err := engineExt.ListReferences(context.Background()) 175 if err != nil { 176 return errors.Wrap(err, "list references") 177 } 178 179 for _, name := range names { 180 fmt.Println(name) 181 } 182 return nil 183 }