Files
tiny-forge/internal/deployer/subdomain.go
T
alexei.dolgolyov eef60a4302 feat(docker-watcher): phase 6 - webhook handler
Secret UUID-based webhook endpoint for CI image push notifications.
Project/stage matching via glob patterns, auto-creation of unknown
projects from image inspection. Fix JSON response injection.
2026-03-27 21:56:18 +03:00

85 lines
2.4 KiB
Go

package deployer
import (
"regexp"
"strings"
)
// maxSubdomainLen is the maximum length of a single DNS label (RFC 1035).
const maxSubdomainLen = 63
// invalidDNSChars matches characters not allowed in a DNS label.
var invalidDNSChars = regexp.MustCompile(`[^a-z0-9-]`)
// GenerateSubdomain builds a subdomain string from the given pattern and parameters.
// The pattern may contain {stage}, {project}, and {tag} placeholders.
// If the stage has a custom subdomain override, that value is used instead of the pattern.
func GenerateSubdomain(pattern, project, stage, tag, stageSubdomain string) string {
if stageSubdomain != "" {
return SanitizeDNSLabel(stageSubdomain)
}
result := pattern
result = strings.ReplaceAll(result, "{stage}", stage)
result = strings.ReplaceAll(result, "{project}", project)
result = strings.ReplaceAll(result, "{tag}", tag)
return SanitizeDNSLabel(result)
}
// GenerateTaggedSubdomain builds a subdomain that includes the tag for multi-instance support.
// It appends "-{sanitized_tag}" to the base subdomain.
func GenerateTaggedSubdomain(pattern, project, stage, tag, stageSubdomain string) string {
base := GenerateSubdomain(pattern, project, stage, "", stageSubdomain)
sanitizedTag := SanitizeDNSLabel(tag)
if sanitizedTag == "" {
return base
}
combined := base + "-" + sanitizedTag
return truncateDNSLabel(combined)
}
// SanitizeDNSLabel converts an arbitrary string into a valid DNS label.
// It lowercases, replaces dots and invalid characters with hyphens,
// collapses consecutive hyphens, trims leading/trailing hyphens, and truncates.
func SanitizeDNSLabel(s string) string {
s = strings.ToLower(s)
s = strings.ReplaceAll(s, ".", "-")
s = invalidDNSChars.ReplaceAllString(s, "-")
s = collapseHyphens(s)
s = strings.Trim(s, "-")
return truncateDNSLabel(s)
}
// collapseHyphens replaces consecutive hyphens with a single hyphen.
func collapseHyphens(s string) string {
prev := false
var b strings.Builder
b.Grow(len(s))
for _, r := range s {
if r == '-' {
if !prev {
b.WriteRune(r)
}
prev = true
} else {
b.WriteRune(r)
prev = false
}
}
return b.String()
}
// truncateDNSLabel truncates a label to maxSubdomainLen characters,
// ensuring it does not end with a hyphen after truncation.
func truncateDNSLabel(s string) string {
if len(s) <= maxSubdomainLen {
return s
}
s = s[:maxSubdomainLen]
return strings.TrimRight(s, "-")
}