github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/space/list.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package space 5 6 import ( 7 "fmt" 8 "io" 9 "net" 10 "sort" 11 "strings" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/gnuflag" 16 17 "github.com/juju/juju/apiserver/params" 18 jujucmd "github.com/juju/juju/cmd" 19 "github.com/juju/juju/cmd/modelcmd" 20 "github.com/juju/juju/cmd/output" 21 ) 22 23 // NewListCommand returns a command used to list spaces. 24 func NewListCommand() modelcmd.ModelCommand { 25 return modelcmd.Wrap(&ListCommand{}) 26 } 27 28 // listCommand displays a list of all spaces known to Juju. 29 type ListCommand struct { 30 SpaceCommandBase 31 Short bool 32 out cmd.Output 33 } 34 35 const listCommandDoc = ` 36 Displays all defined spaces. If --short is not given both spaces and 37 their subnets are displayed, otherwise just a list of spaces. The 38 --format argument has the same semantics as in other CLI commands - 39 "yaml" is the default. The --output argument allows the command 40 output to be redirected to a file. ` 41 42 // Info is defined on the cmd.Command interface. 43 func (c *ListCommand) Info() *cmd.Info { 44 return jujucmd.Info(&cmd.Info{ 45 Name: "spaces", 46 Args: "[--short] [--format yaml|json] [--output <path>]", 47 Purpose: "List known spaces, including associated subnets.", 48 Doc: strings.TrimSpace(listCommandDoc), 49 Aliases: []string{"list-spaces"}, 50 }) 51 } 52 53 // SetFlags is defined on the cmd.Command interface. 54 func (c *ListCommand) SetFlags(f *gnuflag.FlagSet) { 55 c.SpaceCommandBase.SetFlags(f) 56 c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{ 57 "yaml": cmd.FormatYaml, 58 "json": cmd.FormatJson, 59 "tabular": c.printTabular, 60 }) 61 f.BoolVar(&c.Short, "short", false, "only display spaces.") 62 } 63 64 // Init is defined on the cmd.Command interface. It checks the 65 // arguments for sanity and sets up the command to run. 66 func (c *ListCommand) Init(args []string) error { 67 // No arguments are accepted, just flags. 68 if err := cmd.CheckEmpty(args); err != nil { 69 return errors.Trace(err) 70 } 71 72 return nil 73 } 74 75 // Run implements Command.Run. 76 func (c *ListCommand) Run(ctx *cmd.Context) error { 77 return c.RunWithAPI(ctx, func(api SpaceAPI, ctx *cmd.Context) error { 78 spaces, err := api.ListSpaces() 79 if err != nil { 80 if errors.IsNotSupported(err) { 81 ctx.Infof("cannot list spaces: %v", err) 82 } 83 return errors.Annotate(err, "cannot list spaces") 84 } 85 if len(spaces) == 0 { 86 ctx.Infof("no spaces to display") 87 if c.out.Name() == "tabular" { 88 return nil 89 } 90 } 91 92 if c.Short { 93 result := formattedShortList{} 94 for _, space := range spaces { 95 result.Spaces = append(result.Spaces, space.Name) 96 } 97 return c.out.Write(ctx, result) 98 } 99 // Construct the output list for displaying with the chosen 100 // format. 101 result := formattedList{ 102 Spaces: make(map[string]map[string]formattedSubnet), 103 } 104 105 for _, space := range spaces { 106 result.Spaces[space.Name] = make(map[string]formattedSubnet) 107 for _, subnet := range space.Subnets { 108 subResult := formattedSubnet{ 109 Type: typeUnknown, 110 ProviderId: subnet.ProviderId, 111 Zones: subnet.Zones, 112 } 113 // Display correct status according to the life cycle value. 114 // 115 // TODO(dimitern): Do this on the apiserver side, also 116 // do the same for params.Space, so in case of an 117 // error it can be displayed. 118 switch subnet.Life { 119 case params.Alive: 120 subResult.Status = statusInUse 121 case params.Dying, params.Dead: 122 subResult.Status = statusTerminating 123 } 124 125 // Use the CIDR to determine the subnet type. 126 // TODO(dimitern): Do this on the apiserver side. 127 if ip, _, err := net.ParseCIDR(subnet.CIDR); err != nil { 128 // This should never happen as subnets will be 129 // validated before saving in state. 130 msg := fmt.Sprintf("error: invalid subnet CIDR: %s", subnet.CIDR) 131 subResult.Status = msg 132 } else if ip.To4() != nil { 133 subResult.Type = typeIPv4 134 } else if ip.To16() != nil { 135 subResult.Type = typeIPv6 136 } 137 result.Spaces[space.Name][subnet.CIDR] = subResult 138 } 139 } 140 return c.out.Write(ctx, result) 141 }) 142 } 143 144 // printTabular prints the list of spaces in tabular format 145 func (c *ListCommand) printTabular(writer io.Writer, value interface{}) error { 146 tw := output.TabWriter(writer) 147 if c.Short { 148 list, ok := value.(formattedShortList) 149 if !ok { 150 return errors.New("unexpected value") 151 } 152 fmt.Fprintln(tw, "Space") 153 spaces := list.Spaces 154 sort.Strings(spaces) 155 for _, space := range spaces { 156 fmt.Fprintf(tw, "%v\n", space) 157 } 158 } else { 159 list, ok := value.(formattedList) 160 if !ok { 161 return errors.New("unexpected value") 162 } 163 164 fmt.Fprintf(tw, "%s\t%s\n", "Space", "Subnets") 165 spaces := []string{} 166 for name := range list.Spaces { 167 spaces = append(spaces, name) 168 } 169 sort.Strings(spaces) 170 for _, name := range spaces { 171 subnets := list.Spaces[name] 172 fmt.Fprintf(tw, "%s", name) 173 if len(subnets) == 0 { 174 fmt.Fprintf(tw, "\n") 175 continue 176 } 177 cidrs := []string{} 178 for subnet := range subnets { 179 cidrs = append(cidrs, subnet) 180 } 181 sort.Strings(cidrs) 182 for _, cidr := range cidrs { 183 fmt.Fprintf(tw, "\t%v\n", cidr) 184 } 185 } 186 } 187 tw.Flush() 188 return nil 189 } 190 191 const ( 192 typeUnknown = "unknown" 193 typeIPv4 = "ipv4" 194 typeIPv6 = "ipv6" 195 196 statusInUse = "in-use" 197 statusTerminating = "terminating" 198 ) 199 200 // TODO(dimitern): Display space attributes along with subnets (state 201 // or error,public,?) 202 203 type formattedList struct { 204 Spaces map[string]map[string]formattedSubnet `json:"spaces" yaml:"spaces"` 205 } 206 207 type formattedShortList struct { 208 Spaces []string `json:"spaces" yaml:"spaces"` 209 } 210 211 type formattedSubnet struct { 212 Type string `json:"type" yaml:"type"` 213 ProviderId string `json:"provider-id,omitempty" yaml:"provider-id,omitempty"` 214 Status string `json:"status,omitempty" yaml:"status,omitempty"` 215 Zones []string `json:"zones" yaml:"zones"` 216 }