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