github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/resource/list.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resource 5 6 import ( 7 "sort" 8 9 "github.com/juju/cmd" 10 "github.com/juju/errors" 11 "github.com/juju/gnuflag" 12 "gopkg.in/juju/names.v2" 13 14 jujucmd "github.com/juju/juju/cmd" 15 "github.com/juju/juju/cmd/modelcmd" 16 "github.com/juju/juju/resource" 17 ) 18 19 // ListClient has the API client methods needed by ListCommand. 20 type ListClient interface { 21 // ListResources returns info about resources for applications in the model. 22 ListResources(applications []string) ([]resource.ApplicationResources, error) 23 // Close closes the connection. 24 Close() error 25 } 26 27 // ListDeps is a type that contains external functions that List needs. 28 type ListDeps struct { 29 // NewClient returns the value that wraps the API for showing 30 // resources from the server. 31 NewClient func(*ListCommand) (ListClient, error) 32 } 33 34 // ListCommand discovers and lists application or unit resources. 35 type ListCommand struct { 36 modelcmd.ModelCommandBase 37 38 details bool 39 deps ListDeps 40 out cmd.Output 41 target string 42 } 43 44 // NewListCommand returns a new command that lists resources defined 45 // by a charm. 46 func NewListCommand(deps ListDeps) modelcmd.ModelCommand { 47 return modelcmd.Wrap(&ListCommand{deps: deps}) 48 } 49 50 // Info implements cmd.Command.Info. 51 func (c *ListCommand) Info() *cmd.Info { 52 return jujucmd.Info(&cmd.Info{ 53 Name: "resources", 54 Aliases: []string{"list-resources"}, 55 Args: "<application or unit>", 56 Purpose: "Show the resources for an application or unit.", 57 Doc: ` 58 This command shows the resources required by and those in use by an existing 59 application or unit in your model. When run for an application, it will also show any 60 updates available for resources from the charmstore. 61 `, 62 }) 63 } 64 65 // SetFlags implements cmd.Command.SetFlags. 66 func (c *ListCommand) SetFlags(f *gnuflag.FlagSet) { 67 c.ModelCommandBase.SetFlags(f) 68 const defaultFormat = "tabular" 69 c.out.AddFlags(f, defaultFormat, map[string]cmd.Formatter{ 70 defaultFormat: FormatAppTabular, 71 "yaml": cmd.FormatYaml, 72 "json": cmd.FormatJson, 73 }) 74 75 f.BoolVar(&c.details, "details", false, "show detailed information about resources used by each unit.") 76 } 77 78 // Init implements cmd.Command.Init. It will return an error satisfying 79 // errors.BadRequest if you give it an incorrect number of arguments. 80 func (c *ListCommand) Init(args []string) error { 81 if len(args) == 0 { 82 return errors.NewBadRequest(nil, "missing application or unit name") 83 } 84 c.target = args[0] 85 if err := cmd.CheckEmpty(args[1:]); err != nil { 86 return errors.NewBadRequest(err, "") 87 } 88 return nil 89 } 90 91 // Run implements cmd.Command.Run. 92 func (c *ListCommand) Run(ctx *cmd.Context) error { 93 apiclient, err := c.deps.NewClient(c) 94 if err != nil { 95 return errors.Trace(err) 96 } 97 defer apiclient.Close() 98 99 var unit string 100 var application string 101 if names.IsValidApplication(c.target) { 102 application = c.target 103 } else { 104 application, err = names.UnitApplication(c.target) 105 if err != nil { 106 return errors.Errorf("%q is neither an application nor a unit", c.target) 107 } 108 unit = c.target 109 } 110 111 vals, err := apiclient.ListResources([]string{application}) 112 if err != nil { 113 return errors.Trace(err) 114 } 115 if len(vals) != 1 { 116 return errors.Errorf("bad data returned from server") 117 } 118 v := vals[0] 119 120 // It's a lot easier to read and to digest a list of resources 121 // when they are ordered. 122 sort.Sort(charmResourceList(v.CharmStoreResources)) 123 sort.Sort(resourceList(v.Resources)) 124 for _, u := range v.UnitResources { 125 sort.Sort(resourceList(u.Resources)) 126 } 127 128 if unit == "" { 129 return c.formatApplicationResources(ctx, v) 130 } 131 return c.formatUnitResources(ctx, unit, application, v) 132 } 133 134 const noResources = "No resources to display." 135 136 func (c *ListCommand) formatApplicationResources(ctx *cmd.Context, sr resource.ApplicationResources) error { 137 if c.details { 138 formatted, err := FormatApplicationDetails(sr) 139 if err != nil { 140 return errors.Trace(err) 141 } 142 if len(formatted.Resources) == 0 && len(formatted.Updates) == 0 { 143 ctx.Infof(noResources) 144 return nil 145 } 146 147 return c.out.Write(ctx, formatted) 148 } 149 150 formatted, err := formatApplicationResources(sr) 151 if err != nil { 152 return errors.Trace(err) 153 } 154 if len(formatted.Resources) == 0 && len(formatted.Updates) == 0 { 155 ctx.Infof(noResources) 156 return nil 157 } 158 return c.out.Write(ctx, formatted) 159 } 160 161 func (c *ListCommand) formatUnitResources(ctx *cmd.Context, unit, application string, sr resource.ApplicationResources) error { 162 if len(sr.Resources) == 0 && len(sr.UnitResources) == 0 { 163 ctx.Infof(noResources) 164 return nil 165 } 166 167 if c.details { 168 formatted, err := detailedResources(unit, sr) 169 if err != nil { 170 return errors.Trace(err) 171 } 172 return c.out.Write(ctx, FormattedUnitDetails(formatted)) 173 } 174 175 resources := unitResources(unit, application, sr) 176 res := make([]FormattedAppResource, len(sr.Resources)) 177 for i, r := range sr.Resources { 178 if unitResource, ok := resources[r.ID]; ok { 179 // Unit has this application resource, 180 // so use unit's version. 181 r = unitResource 182 } else { 183 // Unit does not have this application resource. 184 // Have to set it to -1 since revision 0 is still a valid revision. 185 // All other information is inherited from application resource. 186 r.Revision = -1 187 } 188 res[i] = FormatAppResource(r) 189 } 190 191 return c.out.Write(ctx, res) 192 193 } 194 195 func unitResources(unit, application string, sr resource.ApplicationResources) map[string]resource.Resource { 196 var resources []resource.Resource 197 for _, res := range sr.UnitResources { 198 if res.Tag.Id() == unit { 199 resources = res.Resources 200 } 201 } 202 if len(resources) == 0 { 203 return nil 204 } 205 unitResourcesById := make(map[string]resource.Resource) 206 for _, r := range resources { 207 unitResourcesById[r.ID] = r 208 } 209 return unitResourcesById 210 }