github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/cmd/umoci/main.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  	"fmt"
    22  	"os"
    23  
    24  	"github.com/apex/log"
    25  	logcli "github.com/apex/log/handlers/cli"
    26  	"github.com/pkg/errors"
    27  	"github.com/urfave/cli"
    28  )
    29  
    30  // version is version ID for the source, read from VERSION in the source and
    31  // populated on build by make.
    32  var version = ""
    33  
    34  // gitCommit is the commit hash that the binary was built from and will be
    35  // populated on build by make.
    36  var gitCommit = ""
    37  
    38  const (
    39  	usage = `umoci modifies Open Container images`
    40  
    41  	// Categories used to automatically monkey-patch flags to commands.
    42  	categoryLayout = "layout"
    43  	categoryImage  = "image"
    44  )
    45  
    46  func main() {
    47  	app := cli.NewApp()
    48  	app.Name = "umoci"
    49  	app.Usage = usage
    50  	app.Authors = []cli.Author{
    51  		{
    52  			Name:  "Aleksa Sarai",
    53  			Email: "asarai@suse.com",
    54  		},
    55  	}
    56  
    57  	// Fill the version.
    58  	v := "unknown"
    59  	if version != "" {
    60  		v = version
    61  	}
    62  	if gitCommit != "" {
    63  		v = fmt.Sprintf("%s~git%s", v, gitCommit)
    64  	}
    65  	app.Version = v
    66  
    67  	app.Flags = []cli.Flag{
    68  		cli.BoolFlag{
    69  			Name:  "verbose",
    70  			Usage: "alias for --log=info",
    71  		},
    72  		cli.StringFlag{
    73  			Name:  "log",
    74  			Usage: "set the log level (debug, info, [warn], error, fatal)",
    75  			Value: "warn",
    76  		},
    77  	}
    78  
    79  	app.Before = func(ctx *cli.Context) error {
    80  		log.SetHandler(logcli.New(os.Stderr))
    81  
    82  		if ctx.GlobalBool("verbose") {
    83  			if ctx.GlobalIsSet("log") {
    84  				return errors.New("--log=* and --verbose are mutually exclusive")
    85  			}
    86  			ctx.GlobalSet("log", "info")
    87  		}
    88  
    89  		level, err := log.ParseLevel(ctx.GlobalString("log"))
    90  		if err != nil {
    91  			return errors.Wrap(err, "parsing log level")
    92  		}
    93  		log.SetLevel(level)
    94  		return nil
    95  	}
    96  
    97  	app.Commands = []cli.Command{
    98  		configCommand,
    99  		unpackCommand,
   100  		repackCommand,
   101  		gcCommand,
   102  		initCommand,
   103  		newCommand,
   104  		tagAddCommand,
   105  		tagRemoveCommand,
   106  		tagListCommand,
   107  		statCommand,
   108  		rawSubcommand,
   109  		insertCommand,
   110  	}
   111  
   112  	app.Metadata = map[string]interface{}{}
   113  
   114  	// In order to make the uxXyz wrappers not too cumbersome we automatically
   115  	// add them to images with categories set to categoryImage or
   116  	// categoryLayout. Monkey patching was never this neat.
   117  	for _, cmd := range flattenCommands(app.Commands) {
   118  		switch cmd.Category {
   119  		case categoryImage:
   120  			oldBefore := cmd.Before
   121  			cmd.Before = func(ctx *cli.Context) error {
   122  				if _, ok := ctx.App.Metadata["--image-path"]; !ok {
   123  					return errors.Errorf("missing mandatory argument: --image")
   124  				}
   125  				if _, ok := ctx.App.Metadata["--image-tag"]; !ok {
   126  					return errors.Errorf("missing mandatory argument: --image")
   127  				}
   128  				if oldBefore != nil {
   129  					return oldBefore(ctx)
   130  				}
   131  				return nil
   132  			}
   133  			*cmd = uxImage(*cmd)
   134  		case categoryLayout:
   135  			oldBefore := cmd.Before
   136  			cmd.Before = func(ctx *cli.Context) error {
   137  				if _, ok := ctx.App.Metadata["--image-path"]; !ok {
   138  					return errors.Errorf("missing mandatory argument: --layout")
   139  				}
   140  				if oldBefore != nil {
   141  					return oldBefore(ctx)
   142  				}
   143  				return nil
   144  			}
   145  			*cmd = uxLayout(*cmd)
   146  		}
   147  	}
   148  
   149  	// Actually run umoci.
   150  	if err := app.Run(os.Args); err != nil {
   151  		// If an error is a permission based error, give a hint to the user
   152  		// that --rootless might help. We probably should only be doing this if
   153  		// we're an unprivileged user.
   154  		if os.IsPermission(errors.Cause(err)) {
   155  			log.Info("umoci encountered a permission error: maybe --rootless will help?")
   156  		}
   157  		log.Fatalf("%v", err)
   158  		log.Debugf("%+v", err)
   159  	}
   160  }