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