github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/volume_ls.go (about)

     1  package main
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  
     7  	"github.com/containers/buildah/pkg/formats"
     8  	"github.com/containers/libpod/cmd/podman/cliconfig"
     9  	"github.com/containers/libpod/pkg/adapter"
    10  	"github.com/pkg/errors"
    11  	"github.com/spf13/cobra"
    12  )
    13  
    14  // volumeOptions is the "ls" command options
    15  type volumeLsOptions struct {
    16  	Format string
    17  	Quiet  bool
    18  }
    19  
    20  // volumeLsTemplateParams is the template parameters to list the volumes
    21  type volumeLsTemplateParams struct {
    22  	Name       string
    23  	Labels     string
    24  	MountPoint string
    25  	Driver     string
    26  	Options    string
    27  	Scope      string
    28  }
    29  
    30  // volumeLsJSONParams is the JSON parameters to list the volumes
    31  type volumeLsJSONParams struct {
    32  	Name       string            `json:"name"`
    33  	Labels     map[string]string `json:"labels"`
    34  	MountPoint string            `json:"mountPoint"`
    35  	Driver     string            `json:"driver"`
    36  	Options    map[string]string `json:"options"`
    37  	Scope      string            `json:"scope"`
    38  }
    39  
    40  var (
    41  	volumeLsCommand cliconfig.VolumeLsValues
    42  
    43  	volumeLsDescription = `
    44  podman volume ls
    45  
    46  List all available volumes. The output of the volumes can be filtered
    47  and the output format can be changed to JSON or a user specified Go template.`
    48  	_volumeLsCommand = &cobra.Command{
    49  		Use:     "ls",
    50  		Aliases: []string{"list"},
    51  		Args:    noSubArgs,
    52  		Short:   "List volumes",
    53  		Long:    volumeLsDescription,
    54  		RunE: func(cmd *cobra.Command, args []string) error {
    55  			volumeLsCommand.InputArgs = args
    56  			volumeLsCommand.GlobalFlags = MainGlobalOpts
    57  			volumeLsCommand.Remote = remoteclient
    58  			return volumeLsCmd(&volumeLsCommand)
    59  		},
    60  	}
    61  )
    62  
    63  func init() {
    64  	volumeLsCommand.Command = _volumeLsCommand
    65  	volumeLsCommand.SetHelpTemplate(HelpTemplate())
    66  	volumeLsCommand.SetUsageTemplate(UsageTemplate())
    67  	flags := volumeLsCommand.Flags()
    68  
    69  	flags.StringVarP(&volumeLsCommand.Filter, "filter", "f", "", "Filter volume output")
    70  	flags.StringVar(&volumeLsCommand.Format, "format", "table {{.Driver}}\t{{.Name}}", "Format volume output using Go template")
    71  	flags.BoolVarP(&volumeLsCommand.Quiet, "quiet", "q", false, "Print volume output in quiet mode")
    72  }
    73  
    74  func volumeLsCmd(c *cliconfig.VolumeLsValues) error {
    75  	runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
    76  	if err != nil {
    77  		return errors.Wrapf(err, "error creating libpod runtime")
    78  	}
    79  	defer runtime.DeferredShutdown(false)
    80  
    81  	opts := volumeLsOptions{
    82  		Quiet: c.Quiet,
    83  	}
    84  	opts.Format = genVolLsFormat(c)
    85  
    86  	// Get the filter functions based on any filters set
    87  	var filterFuncs []adapter.VolumeFilter
    88  	if c.Filter != "" {
    89  		filters := strings.Split(c.Filter, ",")
    90  		for _, f := range filters {
    91  			filterSplit := strings.Split(f, "=")
    92  			if len(filterSplit) < 2 {
    93  				return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
    94  			}
    95  			generatedFunc, err := generateVolumeFilterFuncs(filterSplit[0], filterSplit[1])
    96  			if err != nil {
    97  				return errors.Wrapf(err, "invalid filter")
    98  			}
    99  			filterFuncs = append(filterFuncs, generatedFunc)
   100  		}
   101  	}
   102  
   103  	volumes, err := runtime.Volumes(getContext())
   104  	if err != nil {
   105  		return err
   106  	}
   107  	// Get the volumes that match the filter
   108  	volsFiltered := make([]*adapter.Volume, 0, len(volumes))
   109  	for _, vol := range volumes {
   110  		include := true
   111  		for _, filter := range filterFuncs {
   112  			include = include && filter(vol)
   113  		}
   114  
   115  		if include {
   116  			volsFiltered = append(volsFiltered, vol)
   117  		}
   118  	}
   119  	return generateVolLsOutput(volsFiltered, opts)
   120  }
   121  
   122  // generate the template based on conditions given
   123  func genVolLsFormat(c *cliconfig.VolumeLsValues) string {
   124  	var format string
   125  	if c.Format != "" {
   126  		// "\t" from the command line is not being recognized as a tab
   127  		// replacing the string "\t" to a tab character if the user passes in "\t"
   128  		format = strings.Replace(c.Format, `\t`, "\t", -1)
   129  	}
   130  	if c.Quiet {
   131  		format = "{{.Name}}"
   132  	}
   133  	return format
   134  }
   135  
   136  // Convert output to genericParams for printing
   137  func volLsToGeneric(templParams []volumeLsTemplateParams, jsonParams []volumeLsJSONParams) (genericParams []interface{}) {
   138  	if len(templParams) > 0 {
   139  		for _, v := range templParams {
   140  			genericParams = append(genericParams, interface{}(v))
   141  		}
   142  		return
   143  	}
   144  	for _, v := range jsonParams {
   145  		genericParams = append(genericParams, interface{}(v))
   146  	}
   147  	return
   148  }
   149  
   150  // generate the accurate header based on template given
   151  func (vol *volumeLsTemplateParams) volHeaderMap() map[string]string {
   152  	v := reflect.Indirect(reflect.ValueOf(vol))
   153  	values := make(map[string]string)
   154  
   155  	for i := 0; i < v.NumField(); i++ {
   156  		key := v.Type().Field(i).Name
   157  		value := key
   158  		if value == "Name" {
   159  			value = "Volume" + value
   160  		}
   161  		values[key] = strings.ToUpper(splitCamelCase(value))
   162  	}
   163  	return values
   164  }
   165  
   166  // getVolTemplateOutput returns all the volumes in the volumeLsTemplateParams format
   167  func getVolTemplateOutput(lsParams []volumeLsJSONParams, opts volumeLsOptions) ([]volumeLsTemplateParams, error) {
   168  	var lsOutput []volumeLsTemplateParams
   169  
   170  	for _, lsParam := range lsParams {
   171  		var (
   172  			labels  string
   173  			options string
   174  		)
   175  
   176  		for k, v := range lsParam.Labels {
   177  			label := k
   178  			if v != "" {
   179  				label += "=" + v
   180  			}
   181  			labels += label
   182  		}
   183  		for k, v := range lsParam.Options {
   184  			option := k
   185  			if v != "" {
   186  				option += "=" + v
   187  			}
   188  			options += option
   189  		}
   190  		params := volumeLsTemplateParams{
   191  			Name:       lsParam.Name,
   192  			Driver:     lsParam.Driver,
   193  			MountPoint: lsParam.MountPoint,
   194  			Scope:      lsParam.Scope,
   195  			Labels:     labels,
   196  			Options:    options,
   197  		}
   198  
   199  		lsOutput = append(lsOutput, params)
   200  	}
   201  	return lsOutput, nil
   202  }
   203  
   204  // getVolJSONParams returns the volumes in JSON format
   205  func getVolJSONParams(volumes []*adapter.Volume) []volumeLsJSONParams {
   206  	var lsOutput []volumeLsJSONParams
   207  
   208  	for _, volume := range volumes {
   209  		params := volumeLsJSONParams{
   210  			Name:       volume.Name(),
   211  			Labels:     volume.Labels(),
   212  			MountPoint: volume.MountPoint(),
   213  			Driver:     volume.Driver(),
   214  			Options:    volume.Options(),
   215  			Scope:      volume.Scope(),
   216  		}
   217  
   218  		lsOutput = append(lsOutput, params)
   219  	}
   220  	return lsOutput
   221  }
   222  
   223  // generateVolLsOutput generates the output based on the format, JSON or Go Template, and prints it out
   224  func generateVolLsOutput(volumes []*adapter.Volume, opts volumeLsOptions) error {
   225  	if len(volumes) == 0 && opts.Format != formats.JSONString {
   226  		return nil
   227  	}
   228  	lsOutput := getVolJSONParams(volumes)
   229  	var out formats.Writer
   230  
   231  	switch opts.Format {
   232  	case formats.JSONString:
   233  		out = formats.JSONStructArray{Output: volLsToGeneric([]volumeLsTemplateParams{}, lsOutput)}
   234  	default:
   235  		lsOutput, err := getVolTemplateOutput(lsOutput, opts)
   236  		if err != nil {
   237  			return errors.Wrapf(err, "unable to create volume output")
   238  		}
   239  		out = formats.StdoutTemplateArray{Output: volLsToGeneric(lsOutput, []volumeLsJSONParams{}), Template: opts.Format, Fields: lsOutput[0].volHeaderMap()}
   240  	}
   241  	return out.Out()
   242  }
   243  
   244  // generateVolumeFilterFuncs returns the true if the volume matches the filter set, otherwise it returns false.
   245  func generateVolumeFilterFuncs(filter, filterValue string) (func(volume *adapter.Volume) bool, error) {
   246  	switch filter {
   247  	case "name":
   248  		return func(v *adapter.Volume) bool {
   249  			return strings.Contains(v.Name(), filterValue)
   250  		}, nil
   251  	case "driver":
   252  		return func(v *adapter.Volume) bool {
   253  			return v.Driver() == filterValue
   254  		}, nil
   255  	case "scope":
   256  		return func(v *adapter.Volume) bool {
   257  			return v.Scope() == filterValue
   258  		}, nil
   259  	case "label":
   260  		filterArray := strings.SplitN(filterValue, "=", 2)
   261  		filterKey := filterArray[0]
   262  		if len(filterArray) > 1 {
   263  			filterValue = filterArray[1]
   264  		} else {
   265  			filterValue = ""
   266  		}
   267  		return func(v *adapter.Volume) bool {
   268  			for labelKey, labelValue := range v.Labels() {
   269  				if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
   270  					return true
   271  				}
   272  			}
   273  			return false
   274  		}, nil
   275  	case "opt":
   276  		filterArray := strings.SplitN(filterValue, "=", 2)
   277  		filterKey := filterArray[0]
   278  		if len(filterArray) > 1 {
   279  			filterValue = filterArray[1]
   280  		} else {
   281  			filterValue = ""
   282  		}
   283  		return func(v *adapter.Volume) bool {
   284  			for labelKey, labelValue := range v.Options() {
   285  				if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
   286  					return true
   287  				}
   288  			}
   289  			return false
   290  		}, nil
   291  	}
   292  	return nil, errors.Errorf("%s is an invalid filter", filter)
   293  }