mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
feat(cli): classify every provisioning failure with stable codes
This commit is contained in:
+69
-1
@@ -1,6 +1,10 @@
|
||||
package cli
|
||||
|
||||
import "regexp"
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validHostname reports whether hostname is a valid hostname.
|
||||
// A valid hostname contains 3 -> 64 characters and conform to RFC1123.
|
||||
@@ -16,3 +20,67 @@ func validHostname(hostname string) bool {
|
||||
validHostnameRfc1123 := regexp.MustCompile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
|
||||
return validHostnameRfc1123.MatchString(hostname)
|
||||
}
|
||||
|
||||
// isHostnameChar reports whether r is part of validHostname's accepted
|
||||
// charset (letters, digits, hyphen, dot). It does not check position, so a
|
||||
// hostname can fail validHostname on structure (length, leading/trailing
|
||||
// hyphen) while every one of its characters passes here.
|
||||
func isHostnameChar(r rune) bool {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
|
||||
return true
|
||||
case r == '-' || r == '.':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// offendingHostnameChars returns the distinct characters in hostname that
|
||||
// validHostname's charset does not accept, in first-seen order. Empty when
|
||||
// every character is accepted - a rejection can still come from structure
|
||||
// alone (too short, too long, a leading or trailing hyphen).
|
||||
func offendingHostnameChars(hostname string) string {
|
||||
seen := make(map[rune]bool)
|
||||
var bad []rune
|
||||
for _, r := range hostname {
|
||||
if isHostnameChar(r) || seen[r] {
|
||||
continue
|
||||
}
|
||||
seen[r] = true
|
||||
bad = append(bad, r)
|
||||
}
|
||||
return string(bad)
|
||||
}
|
||||
|
||||
// serverFoldedHostnameChars are the characters ControlD's
|
||||
// DevicesTableModel.formatDeviceName folds to '-' (or strips) when it
|
||||
// registers a device name server-side. Only '.' can actually reach
|
||||
// hostnameMayBeFoldedByServer through validateCustomHostnameFlag's guarded
|
||||
// path: validHostname runs first and already rejects any hostname
|
||||
// containing a space or a '+' as CUSTOM_HOSTNAME_INVALID, so those two never
|
||||
// get here from an explicit --custom-hostname value. They stay in this set
|
||||
// for completeness: a mobile caller can set CustomHostname to an
|
||||
// OS-derived default directly, without going through
|
||||
// validateCustomHostnameFlag at all, so a space or '+' can still reach the
|
||||
// API unvalidated by this client.
|
||||
const serverFoldedHostnameChars = ". +"
|
||||
|
||||
// hostnameMayBeFoldedByServer reports whether hostname contains a character
|
||||
// ControlD may fold or strip when it registers the device, so the name ctrld
|
||||
// accepted may not be the name the dashboard ends up showing.
|
||||
func hostnameMayBeFoldedByServer(hostname string) bool {
|
||||
return strings.ContainsAny(hostname, serverFoldedHostnameChars)
|
||||
}
|
||||
|
||||
// customHostnameFailureMessage names the field, the offending character(s)
|
||||
// when there are any, and the allowed format for CUSTOM_HOSTNAME_INVALID.
|
||||
// ctrld's accept/reject rule (validHostname) is unchanged - this only
|
||||
// explains a rejection that used to be a bare fatal exit.
|
||||
func customHostnameFailureMessage(hostname string) string {
|
||||
const allowedFormat = "3-64 characters of letters, digits, hyphens, and dots (RFC1123 hostname format)"
|
||||
reason := "is not a valid hostname"
|
||||
if bad := offendingHostnameChars(hostname); bad != "" {
|
||||
reason = fmt.Sprintf("contains characters a hostname cannot use: %q", bad)
|
||||
}
|
||||
return fmt.Sprintf("--custom-hostname (CustomHostname) %q %s; allowed format: %s", hostname, reason, allowedFormat)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user