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