github.com/dnephin/dobi@v0.15.0/cmd/list.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"strings"
     8  
     9  	"github.com/dnephin/dobi/config"
    10  	"github.com/dnephin/dobi/logging"
    11  	"github.com/spf13/cobra"
    12  )
    13  
    14  type listOptions struct {
    15  	all     bool
    16  	grouped bool
    17  	tags    []string
    18  }
    19  
    20  func (o listOptions) tagMatch(tags []string) bool {
    21  	for _, otag := range o.tags {
    22  		for _, tag := range tags {
    23  			if tag == otag {
    24  				return true
    25  			}
    26  		}
    27  	}
    28  	return false
    29  }
    30  
    31  func newListCommand(opts *dobiOptions) *cobra.Command {
    32  	var listOpts listOptions
    33  	cmd := &cobra.Command{
    34  		Use:   "list",
    35  		Short: "List resources",
    36  		RunE: func(cmd *cobra.Command, args []string) error {
    37  			return runList(opts, listOpts)
    38  		},
    39  	}
    40  	flags := cmd.Flags()
    41  	flags.BoolVarP(
    42  		&listOpts.all, "all", "a", false,
    43  		"List all resources, including those without descriptions")
    44  	flags.BoolVarP(
    45  		&listOpts.grouped, "grouped", "g", false,
    46  		"List resources grouped by tag. Only resources "+
    47  			"with configured tags will be listed.")
    48  	flags.StringSliceVarP(
    49  		&listOpts.tags, "tags", "t", nil,
    50  		"List tasks matching the tag")
    51  	return cmd
    52  }
    53  
    54  func runList(opts *dobiOptions, listOpts listOptions) error {
    55  	conf, err := config.Load(opts.filename)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	tags := getTags(conf.Resources)
    61  	var descriptions []string
    62  	if listOpts.grouped {
    63  		resources := filterResourcesTags(conf, listOpts)
    64  		descriptions = getDescriptionsByTag(resources)
    65  	} else {
    66  		resources := filterResources(conf, listOpts)
    67  		descriptions = getDescriptions(resources)
    68  	}
    69  
    70  	if len(descriptions) == 0 {
    71  		logging.Log.Warn("No resources found. Try --all or --tags.")
    72  		return nil
    73  	}
    74  	fmt.Print(format(descriptions, tags))
    75  	return nil
    76  }
    77  
    78  func filterResourcesTags(conf *config.Config, listOpts listOptions) []resourceGroup {
    79  	tags := []resourceGroup{}
    80  	if listOpts.all {
    81  		// Add 'none' tag group accessible via [0] for resources with no tags configured
    82  		tags = append(tags, resourceGroup{tag: "none"})
    83  	}
    84  	for _, name := range conf.Sorted() {
    85  		res := conf.Resources[name]
    86  		if len(res.CategoryTags()) > 0 {
    87  			for _, tagname := range res.CategoryTags() {
    88  				currentGroupIndex := 0
    89  				if i, found := findGroup(tags, tagname); found {
    90  					currentGroupIndex = i
    91  				} else {
    92  					tags = append(tags, resourceGroup{
    93  						tag: tagname,
    94  					})
    95  					currentGroupIndex = len(tags) - 1
    96  				}
    97  				tags[currentGroupIndex].resources = append(tags[currentGroupIndex].resources,
    98  					namedResource{
    99  						name:     name,
   100  						resource: res,
   101  					})
   102  			}
   103  		} else {
   104  			if listOpts.all {
   105  				tags[0].resources = append(tags[0].resources,
   106  					namedResource{
   107  						name:     name,
   108  						resource: res,
   109  					})
   110  			}
   111  		}
   112  	}
   113  
   114  	return tags
   115  }
   116  
   117  func filterResources(conf *config.Config, listOpts listOptions) []namedResource {
   118  	resources := []namedResource{}
   119  	for _, name := range conf.Sorted() {
   120  		res := conf.Resources[name]
   121  		if include(res, listOpts) {
   122  			resources = append(resources, namedResource{name: name, resource: res})
   123  		}
   124  	}
   125  	return resources
   126  }
   127  
   128  type resourceGroup struct {
   129  	tag       string
   130  	resources []namedResource
   131  }
   132  
   133  type namedResource struct {
   134  	name     string
   135  	resource config.Resource
   136  }
   137  
   138  func (n namedResource) Describe() string {
   139  	desc := n.resource.Describe()
   140  	if desc == "" {
   141  		return n.resource.String()
   142  	}
   143  	return desc
   144  }
   145  
   146  func include(res config.Resource, listOpts listOptions) bool {
   147  	if listOpts.all || listOpts.tagMatch(res.CategoryTags()) {
   148  		return true
   149  	}
   150  	return len(listOpts.tags) == 0 && res.Describe() != ""
   151  }
   152  
   153  func getDescriptions(resources []namedResource) []string {
   154  	lines := []string{}
   155  	for _, named := range resources {
   156  		line := fmt.Sprintf("%-20s %s", named.name, named.Describe())
   157  		lines = append(lines, line)
   158  	}
   159  	return lines
   160  }
   161  
   162  func getDescriptionsByTag(resources []resourceGroup) []string {
   163  	lines := []string{}
   164  	for _, tag := range resources {
   165  		descriptions := getDescriptions(tag.resources)
   166  		lines = append(lines, formatTags(tag.tag, descriptions))
   167  	}
   168  	return lines
   169  }
   170  
   171  func getTags(resources map[string]config.Resource) []string {
   172  	mapped := make(map[string]struct{})
   173  	for _, res := range resources {
   174  		for _, tag := range res.CategoryTags() {
   175  			mapped[tag] = struct{}{}
   176  		}
   177  	}
   178  	tags := []string{}
   179  	for tag := range mapped {
   180  		tags = append(tags, tag)
   181  	}
   182  	sort.Strings(tags)
   183  	return tags
   184  }
   185  
   186  func findGroup(slice []resourceGroup, tag string) (int, bool) {
   187  	for i, item := range slice {
   188  		if item.tag == tag {
   189  			return i, true
   190  		}
   191  	}
   192  	return -1, false
   193  }
   194  
   195  func format(descriptions []string, tags []string) string {
   196  	resources := strings.Join(descriptions, "\n  ")
   197  
   198  	msg := fmt.Sprintf("Resources:\n  %s\n", resources)
   199  	if len(tags) > 0 {
   200  		msg += fmt.Sprintf("\nTags:\n  %s\n", strings.Join(tags, ", "))
   201  	}
   202  	return msg
   203  }
   204  
   205  func formatTags(tag string, descriptions []string) string {
   206  	msg := fmt.Sprintf("Tag: %s\n", tag)
   207  	resources := strings.Join(descriptions, "\n  ")
   208  	msg += fmt.Sprintf("  %s\n", resources)
   209  	return msg
   210  }