github.com/tigera/api@v0.0.0-20240320170621-278e89a8c5fb/pkg/lib/numorstring/port.go (about) 1 // Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package numorstring 16 17 import ( 18 "encoding/json" 19 "errors" 20 "fmt" 21 "regexp" 22 "strconv" 23 ) 24 25 // Port represents either a range of numeric ports or a named port. 26 // 27 // - For a named port, set the PortName, leaving MinPort and MaxPort as 0. 28 // - For a port range, set MinPort and MaxPort to the (inclusive) port numbers. Set 29 // PortName to "". 30 // - For a single port, set MinPort = MaxPort and PortName = "". 31 type Port struct { 32 MinPort uint16 `json:"minPort,omitempty"` 33 MaxPort uint16 `json:"maxPort,omitempty"` 34 PortName string `json:"portName" validate:"omitempty,portName"` 35 } 36 37 // SinglePort creates a Port struct representing a single port. 38 func SinglePort(port uint16) Port { 39 return Port{MinPort: port, MaxPort: port} 40 } 41 42 func NamedPort(name string) Port { 43 return Port{PortName: name} 44 } 45 46 // PortFromRange creates a Port struct representing a range of ports. 47 func PortFromRange(minPort, maxPort uint16) (Port, error) { 48 port := Port{MinPort: minPort, MaxPort: maxPort} 49 if minPort > maxPort { 50 msg := fmt.Sprintf("minimum port number (%d) is greater than maximum port number (%d) in port range", minPort, maxPort) 51 return port, errors.New(msg) 52 } 53 return port, nil 54 } 55 56 var ( 57 allDigits = regexp.MustCompile(`^\d+$`) 58 portRange = regexp.MustCompile(`^(\d+):(\d+)$`) 59 nameRegex = regexp.MustCompile("^[a-zA-Z0-9_.-]{1,128}$") 60 ) 61 62 // PortFromString creates a Port struct from its string representation. A port 63 // may either be single value "1234", a range of values "100:200" or a named port: "name". 64 func PortFromString(s string) (Port, error) { 65 if allDigits.MatchString(s) { 66 // Port is all digits, it should parse as a single port. 67 num, err := strconv.ParseUint(s, 10, 16) 68 if err != nil { 69 msg := fmt.Sprintf("invalid port format (%s)", s) 70 return Port{}, errors.New(msg) 71 } 72 return SinglePort(uint16(num)), nil 73 } 74 75 if groups := portRange.FindStringSubmatch(s); len(groups) > 0 { 76 // Port matches <digits>:<digits>, it should parse as a range of ports. 77 if pmin, err := strconv.ParseUint(groups[1], 10, 16); err != nil { 78 msg := fmt.Sprintf("invalid minimum port number in range (%s)", s) 79 return Port{}, errors.New(msg) 80 } else if pmax, err := strconv.ParseUint(groups[2], 10, 16); err != nil { 81 msg := fmt.Sprintf("invalid maximum port number in range (%s)", s) 82 return Port{}, errors.New(msg) 83 } else { 84 return PortFromRange(uint16(pmin), uint16(pmax)) 85 } 86 } 87 88 if !nameRegex.MatchString(s) { 89 msg := fmt.Sprintf("invalid name for named port (%s)", s) 90 return Port{}, errors.New(msg) 91 } 92 93 return NamedPort(s), nil 94 } 95 96 // UnmarshalJSON implements the json.Unmarshaller interface. 97 func (p *Port) UnmarshalJSON(b []byte) error { 98 if b[0] == '"' { 99 var s string 100 if err := json.Unmarshal(b, &s); err != nil { 101 return err 102 } 103 104 if v, err := PortFromString(s); err != nil { 105 return err 106 } else { 107 *p = v 108 return nil 109 } 110 } 111 112 // It's not a string, it must be a single int. 113 var i uint16 114 if err := json.Unmarshal(b, &i); err != nil { 115 return err 116 } 117 v := SinglePort(i) 118 *p = v 119 return nil 120 } 121 122 // MarshalJSON implements the json.Marshaller interface. 123 func (p Port) MarshalJSON() ([]byte, error) { 124 if p.PortName != "" { 125 return json.Marshal(p.PortName) 126 } else if p.MinPort == p.MaxPort { 127 return json.Marshal(p.MinPort) 128 } else { 129 return json.Marshal(p.String()) 130 } 131 } 132 133 // String returns the string value. If the min and max port are the same 134 // this returns a single string representation of the port number, otherwise 135 // if returns a colon separated range of ports. 136 func (p Port) String() string { 137 if p.PortName != "" { 138 return p.PortName 139 } else if p.MinPort == p.MaxPort { 140 return strconv.FormatUint(uint64(p.MinPort), 10) 141 } else { 142 return fmt.Sprintf("%d:%d", p.MinPort, p.MaxPort) 143 } 144 } 145 146 // OpenAPISchemaType is used by the kube-openapi generator when constructing 147 // the OpenAPI spec of this type. 148 // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators 149 func (_ Port) OpenAPISchemaType() []string { return []string{"string"} } 150 151 // OpenAPISchemaFormat is used by the kube-openapi generator when constructing 152 // the OpenAPI spec of this type. 153 // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators 154 func (_ Port) OpenAPISchemaFormat() string { return "int-or-string" }