Files
tiny-forge/internal/workload/plugin/source/static/naming.go
T
alexei.dolgolyov bd7a11d4e7 refactor(source): dedup shared helpers across static + dockerfile plugins
Extract the verbatim-duplicated helpers into shared homes:
- buildEnv -> plugin.BuildWorkloadEnv (base plugin pkg; a sourceName param
  preserves each plugin's slog prefix / log-scraper text)
- idShort -> plugin.IDShort
- commitStatusReporter -> staticsite.CommitStatusReporter, re-parameterized
  on primitives (owner/repo/sha/targetURL/enabled) so staticsite needs no
  dependency on the plugin package; reporter tests ported to staticsite
  (plus a new nil-provider case)

containerNameFor/imageTagFor are intentionally left per-plugin: their
prefixes differ (dw-site- vs tf-build-) and name real Docker resources,
so merging them would risk mis-routing. Behavior-preserving; the
static/dockerfile test suites pass unchanged.

Reviewed: go APPROVE (0 CRITICAL/HIGH).
2026-05-29 14:57:30 +03:00

67 lines
2.2 KiB
Go

package static
import (
"fmt"
"strings"
"github.com/alexei/tinyforge/internal/workload/plugin"
)
// containerNameFor is the deterministic container name. Includes
// w.Name for visual continuity in `docker ps` plus the ID short for
// uniqueness.
func containerNameFor(w plugin.Workload) string {
return fmt.Sprintf("dw-site-%s-%s", w.Name, plugin.IDShort(w))
}
// imageTagFor is the deterministic image tag — same shape as the
// container name so the linkage between an image and the workload
// that owns it stays obvious from `docker images`.
func imageTagFor(w plugin.Workload) string {
return fmt.Sprintf("dw-site-%s-%s:latest", w.Name, plugin.IDShort(w))
}
// siteVolumeKey is the input to docker.SiteVolumeName / EnsureSiteVolume
// / RemoveSiteVolume. Composing it here (instead of building the full
// name ourselves) keeps the naming concern in one place — those docker
// helpers wrap the value with their own `tinyforge-site-...-data`
// envelope. Including idShort prevents two workloads sharing a name
// from sharing one persistent volume.
func siteVolumeKey(w plugin.Workload) string {
return fmt.Sprintf("%s-%s", w.Name, plugin.IDShort(w))
}
// sanitizeError clamps an error string so persisting it (in
// containers.extra_json's last_error) or echoing it (via the
// outbound notification webhook) cannot leak a multi-line response
// body, an HTTP header echoing the access token, or a stack trace.
//
// Strategy:
// - Reduce to a single line (replace any newline / tab with space).
// - Cap to a short maxLen so a very long Gitea/GitHub error body
// never round-trips into operator-visible state.
// - Redact the access token verbatim if it appears in the message
// (defense in depth — providers shouldn't echo tokens but a
// misbehaving one could).
func sanitizeError(msg, accessToken string) string {
if msg == "" {
return ""
}
if accessToken != "" {
msg = strings.ReplaceAll(msg, accessToken, "[REDACTED]")
}
// Collapse whitespace runs onto one line.
msg = strings.Map(func(r rune) rune {
switch r {
case '\n', '\r', '\t':
return ' '
}
return r
}, msg)
const maxLen = 240
if len(msg) > maxLen {
msg = msg[:maxLen] + "…"
}
return msg
}