To allow some flexibilities in how user specifies the --api-port argument. In case the 'host' is an string, We will try to convert it into an IP address for port mapping. IP address is also allowed.
123 lines
3.1 KiB
Go
123 lines
3.1 KiB
Go
package run
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type apiPort struct {
|
|
Host string
|
|
HostIp string
|
|
Port string
|
|
}
|
|
|
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
const (
|
|
letterIdxBits = 6 // 6 bits to represent a letter index
|
|
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
|
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
|
)
|
|
|
|
var src = rand.NewSource(time.Now().UnixNano())
|
|
|
|
// GenerateRandomString thanks to https://stackoverflow.com/a/31832326/6450189
|
|
// GenerateRandomString is used to generate a random string that is used as a cluster secret
|
|
func GenerateRandomString(n int) string {
|
|
|
|
sb := strings.Builder{}
|
|
sb.Grow(n)
|
|
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
|
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
|
if remain == 0 {
|
|
cache, remain = src.Int63(), letterIdxMax
|
|
}
|
|
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
|
sb.WriteByte(letterBytes[idx])
|
|
i--
|
|
}
|
|
cache >>= letterIdxBits
|
|
remain--
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
/*** Cluster Name Validation ***/
|
|
const clusterNameMaxSize int = 35
|
|
|
|
// CheckClusterName ensures that a cluster name is also a valid host name according to RFC 1123.
|
|
// We further restrict the length of the cluster name to maximum 'clusterNameMaxSize'
|
|
// so that we can construct the host names based on the cluster name, and still stay
|
|
// within the 64 characters limit.
|
|
func CheckClusterName(name string) error {
|
|
if err := ValidateHostname(name); err != nil {
|
|
return fmt.Errorf("[ERROR] Invalid cluster name\n%+v", ValidateHostname(name))
|
|
}
|
|
if len(name) > clusterNameMaxSize {
|
|
return fmt.Errorf("[ERROR] Cluster name is too long (%d > %d)", len(name), clusterNameMaxSize)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateHostname ensures that a cluster name is also a valid host name according to RFC 1123.
|
|
func ValidateHostname(name string) error {
|
|
|
|
if len(name) == 0 {
|
|
return fmt.Errorf("[ERROR] no name provided")
|
|
}
|
|
|
|
if name[0] == '-' || name[len(name)-1] == '-' {
|
|
return fmt.Errorf("[ERROR] Hostname [%s] must not start or end with - (dash)", name)
|
|
}
|
|
|
|
for _, c := range name {
|
|
switch {
|
|
case '0' <= c && c <= '9':
|
|
case 'a' <= c && c <= 'z':
|
|
case 'A' <= c && c <= 'Z':
|
|
case c == '-':
|
|
break
|
|
default:
|
|
return fmt.Errorf("[ERROR] Hostname [%s] contains characters other than 'Aa-Zz', '0-9' or '-'", name)
|
|
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseApiPort(portSpec string) (*apiPort, error) {
|
|
var port *apiPort
|
|
split := strings.Split(portSpec, ":")
|
|
if len(split) > 2 {
|
|
return nil, fmt.Errorf("api-port format error")
|
|
}
|
|
|
|
if len(split) == 1 {
|
|
port = &apiPort{Port: split[0]}
|
|
} else {
|
|
// Make sure 'host' can be resolved to an IP address
|
|
addrs, err := net.LookupHost(split[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
port = &apiPort{Host: split[0], HostIp: addrs[0], Port: split[1]}
|
|
}
|
|
|
|
// Verify 'port' is an integer and within port ranges
|
|
p, err := strconv.Atoi(port.Port)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p < 0 || p > 65535 {
|
|
return nil, fmt.Errorf("ERROR: --api-port port value out of range")
|
|
}
|
|
|
|
return port, nil
|
|
}
|