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.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
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, "-")
|
||||
}
|
||||
Reference in New Issue
Block a user