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