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