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  }