github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/jujuc/ports.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package jujuc
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/juju/cmd"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/gnuflag"
    15  )
    16  
    17  const (
    18  	portFormat = "<port>[/<protocol>] or <from>-<to>[/<protocol>]"
    19  
    20  	portExp  = "(?:[0-9]+)"
    21  	protoExp = "(?:[a-z0-9]+)"
    22  )
    23  
    24  var validPortOrRange = regexp.MustCompile("^" + portExp + "(?:-" + portExp + ")?(/" + protoExp + ")?$")
    25  
    26  type port struct {
    27  	number   int
    28  	protocol string
    29  }
    30  
    31  func (p port) validate() error {
    32  	if p.number < 1 || p.number > 65535 {
    33  		return errors.Errorf(`port must be in the range [1, 65535]; got "%v"`, p.number)
    34  	}
    35  	proto := strings.ToLower(p.protocol)
    36  	if proto != "tcp" && proto != "udp" {
    37  		return errors.Errorf(`protocol must be "tcp" or "udp"; got %q`, p.protocol)
    38  	}
    39  	return nil
    40  }
    41  
    42  type portRange struct {
    43  	fromPort, toPort int
    44  	protocol         string
    45  }
    46  
    47  func (pr portRange) validate() error {
    48  	if pr.fromPort == pr.toPort {
    49  		return port{pr.fromPort, pr.protocol}.validate()
    50  	}
    51  	if pr.fromPort > pr.toPort {
    52  		return errors.Errorf(
    53  			"invalid port range %d-%d/%s; expected fromPort <= toPort",
    54  			pr.fromPort, pr.toPort, pr.protocol,
    55  		)
    56  	}
    57  	if pr.fromPort < 1 || pr.fromPort > 65535 {
    58  		return errors.Errorf(`fromPort must be in the range [1, 65535]; got "%v"`, pr.fromPort)
    59  	}
    60  	if pr.toPort < 1 || pr.toPort > 65535 {
    61  		return errors.Errorf(`toPort must be in the range [1, 65535]; got "%v"`, pr.toPort)
    62  	}
    63  	proto := strings.ToLower(pr.protocol)
    64  	if proto != "tcp" && proto != "udp" {
    65  		return errors.Errorf(`protocol must be "tcp" or "udp"; got %q`, pr.protocol)
    66  	}
    67  	return nil
    68  }
    69  
    70  func parseArguments(args []string) (portRange, error) {
    71  	arg := strings.ToLower(args[0])
    72  	if !validPortOrRange.MatchString(arg) {
    73  		return portRange{}, errors.Errorf("expected %s; got %q", portFormat, args[0])
    74  	}
    75  	portOrRange := validPortOrRange.FindString(arg)
    76  	parts := strings.SplitN(portOrRange, "/", 2)
    77  
    78  	protocol := "tcp"
    79  	if len(parts) > 1 {
    80  		protocol = parts[1]
    81  	}
    82  	ports := parts[0]
    83  	portParts := strings.SplitN(ports, "-", 2)
    84  	fromPort, toPort := 0, 0
    85  	if len(portParts) >= 1 {
    86  		port, err := strconv.Atoi(portParts[0])
    87  		if err != nil {
    88  			return portRange{}, errors.Annotatef(err, "expected port number; got %q", portParts[0])
    89  		}
    90  		fromPort = port
    91  	}
    92  	if len(portParts) == 2 {
    93  		port, err := strconv.Atoi(portParts[1])
    94  		if err != nil {
    95  			return portRange{}, errors.Annotatef(err, "expected port number; got %q", portParts[1])
    96  		}
    97  		toPort = port
    98  	} else {
    99  		toPort = fromPort
   100  	}
   101  	pr := portRange{fromPort, toPort, protocol}
   102  	return pr, pr.validate()
   103  }
   104  
   105  // portCommand implements the open-port and close-port commands.
   106  type portCommand struct {
   107  	cmd.CommandBase
   108  	info       *cmd.Info
   109  	action     func(*portCommand) error
   110  	Protocol   string
   111  	FromPort   int
   112  	ToPort     int
   113  	formatFlag string // deprecated
   114  }
   115  
   116  func (c *portCommand) Info() *cmd.Info {
   117  	return c.info
   118  }
   119  
   120  func (c *portCommand) SetFlags(f *gnuflag.FlagSet) {
   121  	f.StringVar(&c.formatFlag, "format", "", "deprecated format flag")
   122  }
   123  
   124  func (c *portCommand) Init(args []string) error {
   125  	if args == nil {
   126  		return errors.Errorf("no port or range specified")
   127  	}
   128  
   129  	portRange, err := parseArguments(args)
   130  	if err != nil {
   131  		return errors.Trace(err)
   132  	}
   133  
   134  	c.FromPort = portRange.fromPort
   135  	c.ToPort = portRange.toPort
   136  	c.Protocol = portRange.protocol
   137  	return cmd.CheckEmpty(args[1:])
   138  }
   139  
   140  func (c *portCommand) Run(ctx *cmd.Context) error {
   141  	if c.formatFlag != "" {
   142  		fmt.Fprintf(ctx.Stderr, "--format flag deprecated for command %q", c.Info().Name)
   143  	}
   144  	return c.action(c)
   145  }
   146  
   147  var openPortInfo = &cmd.Info{
   148  	Name:    "open-port",
   149  	Args:    portFormat,
   150  	Purpose: "register a port or range to open",
   151  	Doc:     "The port range will only be open while the service is exposed.",
   152  }
   153  
   154  func NewOpenPortCommand(ctx Context) (cmd.Command, error) {
   155  	return &portCommand{
   156  		info: openPortInfo,
   157  		action: func(c *portCommand) error {
   158  			return ctx.OpenPorts(c.Protocol, c.FromPort, c.ToPort)
   159  		},
   160  	}, nil
   161  }
   162  
   163  var closePortInfo = &cmd.Info{
   164  	Name:    "close-port",
   165  	Args:    portFormat,
   166  	Purpose: "ensure a port or range is always closed",
   167  }
   168  
   169  func NewClosePortCommand(ctx Context) (cmd.Command, error) {
   170  	return &portCommand{
   171  		info: closePortInfo,
   172  		action: func(c *portCommand) error {
   173  			return ctx.ClosePorts(c.Protocol, c.FromPort, c.ToPort)
   174  		},
   175  	}, nil
   176  }