github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/cmd/umoci/main.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 "os" 22 "runtime/pprof" 23 24 "github.com/apex/log" 25 logcli "github.com/apex/log/handlers/cli" 26 "github.com/opencontainers/umoci" 27 "github.com/pkg/errors" 28 "github.com/urfave/cli" 29 ) 30 31 const ( 32 usage = `umoci modifies Open Container images` 33 34 // Categories used to automatically monkey-patch flags to commands. 35 categoryLayout = "layout" 36 categoryImage = "image" 37 ) 38 39 // Main is the underlying main() implementation. You can call this directly as 40 // though it were the command-line arguments of the umoci binary (this is 41 // needed for umoci's integration test hacks you can find in main_test.go). 42 func Main(args []string) error { 43 app := cli.NewApp() 44 app.Name = "umoci" 45 app.Usage = usage 46 app.Authors = []cli.Author{ 47 { 48 Name: "Aleksa Sarai", 49 Email: "asarai@suse.com", 50 }, 51 } 52 app.Version = umoci.FullVersion() 53 54 app.Flags = []cli.Flag{ 55 cli.BoolFlag{ 56 Name: "verbose", 57 Usage: "alias for --log=info", 58 }, 59 cli.StringFlag{ 60 Name: "log", 61 Usage: "set the log level (debug, info, [warn], error, fatal)", 62 Value: "warn", 63 }, 64 cli.StringFlag{ 65 Name: "cpu-profile", 66 Usage: "profile umoci during execution and output it to a file", 67 Hidden: true, 68 }, 69 } 70 71 app.Before = func(ctx *cli.Context) error { 72 log.SetHandler(logcli.New(os.Stderr)) 73 74 if ctx.GlobalBool("verbose") { 75 if ctx.GlobalIsSet("log") { 76 return errors.New("--log=* and --verbose are mutually exclusive") 77 } 78 if err := ctx.GlobalSet("log", "info"); err != nil { 79 // Should _never_ be reached. 80 return errors.Wrap(err, "[internal error] failure auto-setting --log=info") 81 } 82 } 83 level, err := log.ParseLevel(ctx.GlobalString("log")) 84 if err != nil { 85 return errors.Wrap(err, "parsing log level") 86 } 87 log.SetLevel(level) 88 89 if path := ctx.GlobalString("cpu-profile"); path != "" { 90 fh, err := os.Create(path) 91 if err != nil { 92 return errors.Wrap(err, "opening cpu-profile path") 93 } 94 if err := pprof.StartCPUProfile(fh); err != nil { 95 return errors.Wrap(err, "start cpu-profile") 96 } 97 } 98 return nil 99 } 100 101 app.After = func(ctx *cli.Context) error { 102 pprof.StopCPUProfile() 103 return nil 104 } 105 106 app.Commands = []cli.Command{ 107 configCommand, 108 unpackCommand, 109 repackCommand, 110 gcCommand, 111 initCommand, 112 newCommand, 113 tagAddCommand, 114 tagRemoveCommand, 115 tagListCommand, 116 statCommand, 117 rawSubcommand, 118 insertCommand, 119 } 120 121 app.Metadata = map[string]interface{}{} 122 123 // In order to make the uxXyz wrappers not too cumbersome we automatically 124 // add them to images with categories set to categoryImage or 125 // categoryLayout. Monkey patching was never this neat. 126 for _, cmd := range flattenCommands(app.Commands) { 127 switch cmd.Category { 128 case categoryImage: 129 oldBefore := cmd.Before 130 cmd.Before = func(ctx *cli.Context) error { 131 if _, ok := ctx.App.Metadata["--image-path"]; !ok { 132 return errors.Errorf("missing mandatory argument: --image") 133 } 134 if _, ok := ctx.App.Metadata["--image-tag"]; !ok { 135 return errors.Errorf("missing mandatory argument: --image") 136 } 137 if oldBefore != nil { 138 return oldBefore(ctx) 139 } 140 return nil 141 } 142 *cmd = uxImage(*cmd) 143 case categoryLayout: 144 oldBefore := cmd.Before 145 cmd.Before = func(ctx *cli.Context) error { 146 if _, ok := ctx.App.Metadata["--image-path"]; !ok { 147 return errors.Errorf("missing mandatory argument: --layout") 148 } 149 if oldBefore != nil { 150 return oldBefore(ctx) 151 } 152 return nil 153 } 154 *cmd = uxLayout(*cmd) 155 } 156 } 157 158 err := app.Run(args) 159 if err != nil { 160 // If an error is a permission based error, give a hint to the user 161 // that --rootless might help. We probably should only be doing this if 162 // we're an unprivileged user. 163 if os.IsPermission(errors.Cause(err)) { 164 log.Warn("umoci encountered a permission error: maybe --rootless will help?") 165 } 166 log.Debugf("%+v", err) 167 } 168 return err 169 } 170 171 func main() { 172 if err := Main(os.Args); err != nil { 173 log.Fatalf("%v", err) 174 } 175 }