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  }