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  }