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 }