github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/utils_ux.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 "fmt" 22 "strings" 23 24 "github.com/opencontainers/umoci/oci/casext" 25 "github.com/pkg/errors" 26 "github.com/urfave/cli" 27 ) 28 29 func flattenCommands(cmds []cli.Command) []*cli.Command { 30 var flatten []*cli.Command 31 for idx, cmd := range cmds { 32 flatten = append(flatten, &cmds[idx]) 33 flatten = append(flatten, flattenCommands(cmd.Subcommands)...) 34 } 35 return flatten 36 } 37 38 // uxHistory adds the full set of --history.* flags to the given cli.Command as 39 // well as adding relevant validation logic to the .Before of the command. The 40 // values will be stored in ctx.Metadata with the keys "--history.author", 41 // "--history.created", "--history.created_by", "--history.comment", with 42 // string values. If they are not set the value will be nil. 43 func uxHistory(cmd cli.Command) cli.Command { 44 historyFlags := []cli.Flag{ 45 cli.BoolFlag{ 46 Name: "no-history", 47 Usage: "do not create a history entry", 48 }, 49 cli.StringFlag{ 50 Name: "history.author", 51 Usage: "author value for the history entry", 52 }, 53 cli.StringFlag{ 54 Name: "history.comment", 55 Usage: "comment for the history entry", 56 }, 57 cli.StringFlag{ 58 Name: "history.created", 59 Usage: "created value for the history entry", 60 }, 61 cli.StringFlag{ 62 Name: "history.created_by", 63 Usage: "created_by value for the history entry", 64 }, 65 } 66 cmd.Flags = append(cmd.Flags, historyFlags...) 67 68 oldBefore := cmd.Before 69 cmd.Before = func(ctx *cli.Context) error { 70 // --no-history is incompatible with other --history.* options. 71 if ctx.Bool("no-history") { 72 for _, flag := range historyFlags { 73 if name := flag.GetName(); name == "no-history" { 74 continue 75 } else if ctx.IsSet(name) { 76 return errors.Errorf("--no-history and --%s may not be specified together", name) 77 } 78 } 79 } 80 81 // Include any old befores set. 82 if oldBefore != nil { 83 return oldBefore(ctx) 84 } 85 return nil 86 } 87 88 return cmd 89 } 90 91 // uxTag adds a --tag flag to the given cli.Command as well as adding relevant 92 // validation logic to the .Before of the command. The value will be stored in 93 // ctx.Metadata["--tag"] as a string (or nil if --tag was not specified). 94 func uxTag(cmd cli.Command) cli.Command { 95 cmd.Flags = append(cmd.Flags, cli.StringFlag{ 96 Name: "tag", 97 Usage: "new tag name (if empty, overwrite --image tag)", 98 }) 99 100 oldBefore := cmd.Before 101 cmd.Before = func(ctx *cli.Context) error { 102 // Verify tag value. 103 if ctx.IsSet("tag") { 104 tag := ctx.String("tag") 105 if !casext.IsValidReferenceName(tag) { 106 return errors.Wrap(fmt.Errorf("tag contains invalid characters: '%s'", tag), "invalid --tag") 107 } 108 if tag == "" { 109 return errors.Wrap(fmt.Errorf("tag is empty"), "invalid --tag") 110 } 111 ctx.App.Metadata["--tag"] = tag 112 } 113 114 // Include any old befores set. 115 if oldBefore != nil { 116 return oldBefore(ctx) 117 } 118 return nil 119 } 120 121 return cmd 122 } 123 124 // uxImage adds an --image flag to the given cli.Command as well as adding 125 // relevant validation logic to the .Before of the command. The values (image, 126 // tag) will be stored in ctx.Metadata["--image-path"] and 127 // ctx.Metadata["--image-tag"] as strings (both will be nil if --image is not 128 // specified). 129 func uxImage(cmd cli.Command) cli.Command { 130 cmd.Flags = append(cmd.Flags, cli.StringFlag{ 131 Name: "image", 132 Usage: "OCI image URI of the form 'path[:tag]'", 133 }) 134 135 oldBefore := cmd.Before 136 cmd.Before = func(ctx *cli.Context) error { 137 // Verify and parse --image. 138 if ctx.IsSet("image") { 139 image := ctx.String("image") 140 141 var dir, tag string 142 sep := strings.Index(image, ":") 143 if sep == -1 { 144 dir = image 145 tag = "latest" 146 } else { 147 dir = image[:sep] 148 tag = image[sep+1:] 149 } 150 151 // Verify directory value. 152 if dir == "" { 153 return errors.Wrap(fmt.Errorf("path is empty"), "invalid --image") 154 } 155 156 // Verify tag value. 157 if !casext.IsValidReferenceName(tag) { 158 return errors.Wrap(fmt.Errorf("tag contains invalid characters: '%s'", tag), "invalid --image") 159 } 160 if tag == "" { 161 return errors.Wrap(fmt.Errorf("tag is empty"), "invalid --image") 162 } 163 164 ctx.App.Metadata["--image-path"] = dir 165 ctx.App.Metadata["--image-tag"] = tag 166 } 167 168 if oldBefore != nil { 169 return oldBefore(ctx) 170 } 171 return nil 172 } 173 174 return cmd 175 } 176 177 // uxLayout adds an --layout flag to the given cli.Command as well as adding 178 // relevant validation logic to the .Before of the command. The value is stored 179 // in ctx.App.Metadata["--image-path"] as a string (or nil --layout was not set). 180 func uxLayout(cmd cli.Command) cli.Command { 181 cmd.Flags = append(cmd.Flags, cli.StringFlag{ 182 Name: "layout", 183 Usage: "path to an OCI image layout", 184 }) 185 186 oldBefore := cmd.Before 187 cmd.Before = func(ctx *cli.Context) error { 188 // Verify and parse --layout. 189 if ctx.IsSet("layout") { 190 layout := ctx.String("layout") 191 192 // Verify directory value. 193 if strings.Contains(layout, ":") { 194 return errors.Wrap(fmt.Errorf("path contains ':' character: '%s'", layout), "invalid --layout") 195 } 196 if layout == "" { 197 return errors.Wrap(fmt.Errorf("path is empty"), "invalid --layout") 198 } 199 200 ctx.App.Metadata["--image-path"] = layout 201 } 202 203 if oldBefore != nil { 204 return oldBefore(ctx) 205 } 206 return nil 207 } 208 209 return cmd 210 } 211 212 func uxRemap(cmd cli.Command) cli.Command { 213 cmd.Flags = append(cmd.Flags, []cli.Flag{ 214 cli.StringSliceFlag{ 215 Name: "uid-map", 216 Usage: "specifies a uid mapping to use (container:host:size)", 217 }, 218 cli.StringSliceFlag{ 219 Name: "gid-map", 220 Usage: "specifies a gid mapping to use (container:host:size)", 221 }, 222 cli.BoolFlag{ 223 Name: "rootless", 224 Usage: "enable rootless command support", 225 }, 226 }...) 227 228 return cmd 229 }