chore: Clean up build scripts and remove unused Docker agent entry point

This commit is contained in:
rcourtman
2025-12-29 23:37:16 +00:00
parent e42cbe38f0
commit 6ac5e3ebfe
5 changed files with 19 additions and 843 deletions
+15 -50
View File
@@ -67,42 +67,7 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
-trimpath \
-o pulse-linux-arm64 ./cmd/pulse
# Build docker-agent binaries (optional cross-arch builds controlled by BUILD_AGENT)
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
--mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \
VERSION="v$(cat VERSION | tr -d '\n')" && \
if [ "${BUILD_AGENT:-1}" = "1" ]; then \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=${VERSION}" \
-trimpath \
-o pulse-docker-agent-linux-amd64 ./cmd/pulse-docker-agent && \
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=${VERSION}" \
-trimpath \
-o pulse-docker-agent-linux-arm64 ./cmd/pulse-docker-agent && \
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=${VERSION}" \
-trimpath \
-o pulse-docker-agent-linux-armv7 ./cmd/pulse-docker-agent && \
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=${VERSION}" \
-trimpath \
-o pulse-docker-agent-linux-armv6 ./cmd/pulse-docker-agent && \
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=${VERSION}" \
-trimpath \
-o pulse-docker-agent-linux-386 ./cmd/pulse-docker-agent; \
else \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=${VERSION}" \
-trimpath \
-o pulse-docker-agent-linux-amd64 ./cmd/pulse-docker-agent && \
cp pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-arm64 && \
cp pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-armv7 && \
cp pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-armv6 && \
cp pulse-docker-agent-linux-amd64 pulse-docker-agent-linux-386; \
fi && \
cp pulse-docker-agent-linux-amd64 pulse-docker-agent
# Build host-agent binaries for all platforms (for download endpoint)
RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \
@@ -233,20 +198,25 @@ RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
# Copy all agent binaries first
COPY --from=backend-builder /app/pulse-docker-agent-linux-* /tmp/
# Copy all unified agent binaries first
COPY --from=backend-builder /app/pulse-agent-linux-* /tmp/
# Select the appropriate architecture binary
# Docker buildx automatically sets TARGETARCH (amd64, arm64, arm) and TARGETVARIANT (v7)
RUN if [ "$TARGETARCH" = "arm64" ]; then \
cp /tmp/pulse-docker-agent-linux-arm64 /usr/local/bin/pulse-docker-agent; \
cp /tmp/pulse-agent-linux-arm64 /usr/local/bin/pulse-agent; \
elif [ "$TARGETARCH" = "arm" ]; then \
cp /tmp/pulse-docker-agent-linux-armv7 /usr/local/bin/pulse-docker-agent; \
cp /tmp/pulse-agent-linux-armv7 /usr/local/bin/pulse-agent; \
else \
cp /tmp/pulse-docker-agent-linux-amd64 /usr/local/bin/pulse-docker-agent; \
cp /tmp/pulse-agent-linux-amd64 /usr/local/bin/pulse-agent; \
fi && \
chmod +x /usr/local/bin/pulse-docker-agent && \
rm -rf /tmp/pulse-docker-agent-*
chmod +x /usr/local/bin/pulse-agent && \
rm -rf /tmp/pulse-agent-*
# Create shim for pulse-docker-agent to maintain backward compatibility
RUN echo '#!/bin/sh' > /usr/local/bin/pulse-docker-agent && \
echo 'exec /usr/local/bin/pulse-agent --enable-docker "$@"' >> /usr/local/bin/pulse-docker-agent && \
chmod +x /usr/local/bin/pulse-docker-agent
COPY --from=backend-builder /app/VERSION /VERSION
@@ -274,7 +244,7 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then \
chmod +x ./pulse && \
rm -rf /tmp/pulse-linux-*
COPY --from=backend-builder /app/pulse-docker-agent .
# Copy VERSION file
COPY --from=backend-builder /app/VERSION .
@@ -309,12 +279,7 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then \
fi
# Docker agent binaries (all architectures)
COPY --from=backend-builder /app/pulse-docker-agent-linux-amd64 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-docker-agent-linux-arm64 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-docker-agent-linux-armv7 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-docker-agent-linux-armv6 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-docker-agent-linux-386 /opt/pulse/bin/
COPY --from=backend-builder /app/pulse-docker-agent /opt/pulse/bin/pulse-docker-agent
# Host agent binaries (all platforms and architectures)
COPY --from=backend-builder /app/pulse-host-agent-linux-amd64 /opt/pulse/bin/
+1 -4
View File
@@ -87,10 +87,7 @@ build-agents:
@echo "Building agent binaries for all platforms..."
@mkdir -p bin
@VERSION=$$(cat VERSION | tr -d '\n') && \
echo "Building docker agent binaries (version: v$$VERSION)..." && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=v$$VERSION" -trimpath -o bin/pulse-docker-agent-linux-amd64 ./cmd/pulse-docker-agent && \
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=v$$VERSION" -trimpath -o bin/pulse-docker-agent-linux-arm64 ./cmd/pulse-docker-agent && \
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=v$$VERSION" -trimpath -o bin/pulse-docker-agent-linux-armv7 ./cmd/pulse-docker-agent && \
echo "Building host agent binaries..." && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-amd64 ./cmd/pulse-host-agent && \
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-arm64 ./cmd/pulse-host-agent && \
-365
View File
@@ -1,365 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/dockeragent"
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
"github.com/rs/zerolog"
)
type stringFlagList []string
func (l *stringFlagList) String() string {
return strings.Join(*l, ",")
}
func (l *stringFlagList) Set(value string) error {
*l = append(*l, value)
return nil
}
func (l stringFlagList) Values() []string {
if len(l) == 0 {
return nil
}
return append([]string(nil), l...)
}
func main() {
// Handle --version flag early before other config parsing
versionFlag := false
for _, arg := range os.Args[1:] {
if arg == "--version" || arg == "-version" || arg == "version" {
versionFlag = true
break
}
}
if versionFlag {
fmt.Printf("pulse-docker-agent version %s\n", dockeragent.Version)
os.Exit(0)
}
cfg := loadConfig()
zerolog.SetGlobalLevel(cfg.LogLevel)
logger := zerolog.New(os.Stdout).Level(cfg.LogLevel).With().Timestamp().Logger()
cfg.Logger = &logger
// Deprecation warning
logger.Warn().Msg("pulse-docker-agent is DEPRECATED and will be removed in a future release")
logger.Warn().Msg("Please migrate to the unified 'pulse-agent' with --enable-docker flag")
logger.Warn().Msg("Example: pulse-agent --url <URL> --token <TOKEN> --enable-docker")
logger.Warn().Msg("")
agent, err := dockeragent.New(cfg)
if err != nil {
logger.Fatal().Err(err).Msg("Failed to create docker agent")
}
defer agent.Close()
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
logger.Info().Str("pulse_url", cfg.PulseURL).Dur("interval", cfg.Interval).Msg("Starting Pulse Docker agent")
if err := agent.Run(ctx); err != nil && err != context.Canceled {
logger.Fatal().Err(err).Msg("Agent terminated with error")
}
logger.Info().Msg("Agent stopped")
}
func loadConfig() dockeragent.Config {
envURL := utils.GetenvTrim("PULSE_URL")
envToken := utils.GetenvTrim("PULSE_TOKEN")
envInterval := utils.GetenvTrim("PULSE_INTERVAL")
envHostname := utils.GetenvTrim("PULSE_HOSTNAME")
envAgentID := utils.GetenvTrim("PULSE_AGENT_ID")
envInsecure := utils.GetenvTrim("PULSE_INSECURE_SKIP_VERIFY")
envNoAutoUpdate := utils.GetenvTrim("PULSE_NO_AUTO_UPDATE")
envTargets := utils.GetenvTrim("PULSE_TARGETS")
envRuntime := utils.GetenvTrim("PULSE_RUNTIME")
envContainerStates := utils.GetenvTrim("PULSE_CONTAINER_STATES")
envSwarmScope := utils.GetenvTrim("PULSE_SWARM_SCOPE")
envSwarmServices := utils.GetenvTrim("PULSE_SWARM_SERVICES")
envSwarmTasks := utils.GetenvTrim("PULSE_SWARM_TASKS")
envIncludeContainers := utils.GetenvTrim("PULSE_INCLUDE_CONTAINERS")
envCollectDisk := utils.GetenvTrim("PULSE_COLLECT_DISK")
envLogLevel := utils.GetenvTrim("LOG_LEVEL")
defaultInterval := 30 * time.Second
if envInterval != "" {
if parsed, err := time.ParseDuration(envInterval); err == nil {
defaultInterval = parsed
}
}
swarmScopeDefault := envSwarmScope
if swarmScopeDefault == "" {
swarmScopeDefault = "node"
}
includeServicesDefault := true
if envSwarmServices != "" {
includeServicesDefault = utils.ParseBool(envSwarmServices)
}
includeTasksDefault := true
if envSwarmTasks != "" {
includeTasksDefault = utils.ParseBool(envSwarmTasks)
}
includeContainersDefault := true
if envIncludeContainers != "" {
includeContainersDefault = utils.ParseBool(envIncludeContainers)
}
collectDiskDefault := true
if envCollectDisk != "" {
collectDiskDefault = utils.ParseBool(envCollectDisk)
}
logLevelDefault := "info"
if envLogLevel != "" {
logLevelDefault = envLogLevel
}
urlFlag := flag.String("url", envURL, "Pulse server URL (e.g. http://pulse:7655)")
tokenFlag := flag.String("token", envToken, "Pulse API token (required)")
intervalFlag := flag.Duration("interval", defaultInterval, "Reporting interval (e.g. 30s)")
hostnameFlag := flag.String("hostname", envHostname, "Override hostname reported to Pulse")
agentIDFlag := flag.String("agent-id", envAgentID, "Override agent identifier")
insecureFlag := flag.Bool("insecure", utils.ParseBool(envInsecure), "Skip TLS certificate verification")
noAutoUpdateFlag := flag.Bool("no-auto-update", utils.ParseBool(envNoAutoUpdate), "Disable automatic agent updates")
runtimeFlag := flag.String("runtime", envRuntime, "Container runtime to expect (auto, docker, podman)")
logLevelFlag := flag.String("log-level", logLevelDefault, "Log level: debug, info, warn, error")
var targetFlags stringFlagList
flag.Var(&targetFlags, "target", "Pulse target in url|token[|insecure] format. Repeat to send to multiple Pulse instances")
var containerStateFlags stringFlagList
flag.Var(&containerStateFlags, "container-state", "Only include containers whose status matches this value (repeat to allow multiple). Allowed values: created,running,restarting,removing,paused,exited,dead.")
swarmScopeFlag := flag.String("swarm-scope", strings.ToLower(strings.TrimSpace(swarmScopeDefault)), "Swarm data scope to collect: node, cluster, or auto")
includeServicesFlag := flag.Bool("swarm-services", includeServicesDefault, "Include Swarm service summaries in reports")
includeTasksFlag := flag.Bool("swarm-tasks", includeTasksDefault, "Include Swarm tasks in reports")
includeContainersFlag := flag.Bool("include-containers", includeContainersDefault, "Include per-container metrics in reports")
collectDiskFlag := flag.Bool("collect-disk", collectDiskDefault, "Collect per-container disk usage, block IO, and mount details in reports")
flag.Parse()
// Check for common mistakes with unparsed arguments
unparsedArgs := flag.Args()
if len(unparsedArgs) > 0 {
// Check if any look like flags with single dash
for _, arg := range unparsedArgs {
if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
fmt.Fprintf(os.Stderr, "error: unrecognized argument %q\n", arg)
fmt.Fprintln(os.Stderr, "note: flags must use double dashes (e.g., --token, not -token)")
fmt.Fprintln(os.Stderr, "\nUsage:")
flag.Usage()
os.Exit(1)
}
}
fmt.Fprintf(os.Stderr, "error: unexpected arguments: %v\n", unparsedArgs)
flag.Usage()
os.Exit(1)
}
pulseURL := *urlFlag
urlFromEnvOrFlag := envURL != "" || *urlFlag != envURL
if pulseURL == "" {
pulseURL = "http://localhost:7655"
}
targets := make([]dockeragent.TargetConfig, 0)
if len(targetFlags) > 0 {
parsedTargets, err := parseTargetSpecs(targetFlags.Values())
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
targets = append(targets, parsedTargets...)
}
if envTargets != "" {
envTargetSpecs := splitTargetSpecs(envTargets)
if len(envTargetSpecs) > 0 {
parsedTargets, err := parseTargetSpecs(envTargetSpecs)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
targets = append(targets, parsedTargets...)
}
}
token := strings.TrimSpace(*tokenFlag)
if token == "" && len(targets) == 0 {
fmt.Fprintln(os.Stderr, "error: PULSE_TOKEN, --token, or at least one --target/PULSE_TARGETS entry must be provided")
fmt.Fprintln(os.Stderr, "\nExample usage:")
fmt.Fprintln(os.Stderr, " pulse-docker-agent --url http://pulse.example.com:7655 --token <your-token>")
fmt.Fprintln(os.Stderr, "\nOr set environment variables:")
fmt.Fprintln(os.Stderr, " export PULSE_URL=http://pulse.example.com:7655")
fmt.Fprintln(os.Stderr, " export PULSE_TOKEN=<your-token>")
fmt.Fprintln(os.Stderr, " pulse-docker-agent")
os.Exit(1)
}
// Warn if using default localhost URL without explicit configuration
if !urlFromEnvOrFlag && len(targets) == 0 && token != "" {
fmt.Fprintln(os.Stderr, "warning: no --url or PULSE_URL provided, defaulting to http://localhost:7655")
fmt.Fprintln(os.Stderr, "note: if your Pulse server is not on localhost, specify --url http://your-pulse-server:7655")
fmt.Fprintln(os.Stderr, "")
}
interval := *intervalFlag
if interval <= 0 {
interval = 30 * time.Second
}
logLevel, err := parseLogLevel(*logLevelFlag)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
containerStates := make([]string, 0)
if len(containerStateFlags) > 0 {
containerStates = append(containerStates, containerStateFlags.Values()...)
}
if envContainerStates != "" {
containerStates = append(containerStates, splitStringList(envContainerStates)...)
}
return dockeragent.Config{
PulseURL: pulseURL,
APIToken: token,
Interval: interval,
HostnameOverride: strings.TrimSpace(*hostnameFlag),
AgentID: strings.TrimSpace(*agentIDFlag),
InsecureSkipVerify: *insecureFlag,
DisableAutoUpdate: *noAutoUpdateFlag,
Targets: targets,
ContainerStates: containerStates,
SwarmScope: strings.ToLower(strings.TrimSpace(*swarmScopeFlag)),
Runtime: strings.ToLower(strings.TrimSpace(*runtimeFlag)),
IncludeServices: *includeServicesFlag,
IncludeTasks: *includeTasksFlag,
IncludeContainers: *includeContainersFlag,
CollectDiskMetrics: *collectDiskFlag,
LogLevel: logLevel,
}
}
func parseLogLevel(value string) (zerolog.Level, error) {
normalized := strings.ToLower(strings.TrimSpace(value))
if normalized == "" {
return zerolog.InfoLevel, nil
}
level, err := zerolog.ParseLevel(normalized)
if err != nil {
return zerolog.InfoLevel, fmt.Errorf("invalid log level %q: must be debug, info, warn, or error", value)
}
return level, nil
}
func parseTargetSpecs(specs []string) ([]dockeragent.TargetConfig, error) {
targets := make([]dockeragent.TargetConfig, 0, len(specs))
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if spec == "" {
continue
}
target, err := parseTargetSpec(spec)
if err != nil {
return nil, err
}
targets = append(targets, target)
}
return targets, nil
}
func parseTargetSpec(spec string) (dockeragent.TargetConfig, error) {
parts := strings.Split(spec, "|")
if len(parts) < 2 {
return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: expected format url|token[|insecure]", spec)
}
url := strings.TrimSpace(parts[0])
token := strings.TrimSpace(parts[1])
if url == "" {
return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: URL is required", spec)
}
if token == "" {
return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: token is required", spec)
}
insecure := false
if len(parts) >= 3 {
switch strings.ToLower(strings.TrimSpace(parts[2])) {
case "1", "true", "yes", "y", "on":
insecure = true
case "", "0", "false", "no", "n", "off":
insecure = false
default:
return dockeragent.TargetConfig{}, fmt.Errorf("invalid target %q: insecure flag must be true/false", spec)
}
}
return dockeragent.TargetConfig{
URL: url,
Token: token,
InsecureSkipVerify: insecure,
}, nil
}
func splitTargetSpecs(value string) []string {
if value == "" {
return nil
}
normalized := strings.ReplaceAll(value, "\n", ";")
raw := strings.Split(normalized, ";")
result := make([]string, 0, len(raw))
for _, item := range raw {
if trimmed := strings.TrimSpace(item); trimmed != "" {
result = append(result, trimmed)
}
}
return result
}
func splitStringList(value string) []string {
if value == "" {
return nil
}
items := strings.FieldsFunc(value, func(r rune) bool {
switch r {
case ',', ';', '\n', '\r':
return true
default:
return false
}
})
result := make([]string, 0, len(items))
for _, item := range items {
if trimmed := strings.TrimSpace(item); trimmed != "" {
result = append(result, trimmed)
}
}
return result
}
-394
View File
@@ -1,394 +0,0 @@
package main
import (
"reflect"
"strings"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/dockeragent"
"github.com/rs/zerolog"
)
func TestParseTargetSpec(t *testing.T) {
target, err := parseTargetSpec("https://pulse.example.com|abc123|true")
if err != nil {
t.Fatalf("parseTargetSpec returned error: %v", err)
}
if target.URL != "https://pulse.example.com" {
t.Fatalf("expected URL https://pulse.example.com, got %q", target.URL)
}
if target.Token != "abc123" {
t.Fatalf("expected token abc123, got %q", target.Token)
}
if !target.InsecureSkipVerify {
t.Fatalf("expected insecure flag true")
}
}
func TestParseTargetSpecDefaults(t *testing.T) {
target, err := parseTargetSpec(" https://pulse.example.com | token456 ")
if err != nil {
t.Fatalf("parseTargetSpec returned error: %v", err)
}
if target.URL != "https://pulse.example.com" {
t.Fatalf("expected URL https://pulse.example.com, got %q", target.URL)
}
if target.Token != "token456" {
t.Fatalf("expected token token456, got %q", target.Token)
}
if target.InsecureSkipVerify {
t.Fatalf("expected insecure flag false")
}
}
func TestParseTargetSpecInvalid(t *testing.T) {
if _, err := parseTargetSpec("https://pulse.example.com"); err == nil {
t.Fatalf("expected error for missing token")
}
if _, err := parseTargetSpec("https://pulse.example.com|token|maybe"); err == nil {
t.Fatalf("expected error for invalid insecure flag")
}
}
func TestParseTargetSpecsSkipsBlanks(t *testing.T) {
specs, err := parseTargetSpecs([]string{"https://a|tokenA", " ", "\n", "https://b|tokenB|true"})
if err != nil {
t.Fatalf("parseTargetSpecs returned error: %v", err)
}
if len(specs) != 2 {
t.Fatalf("expected 2 targets, got %d", len(specs))
}
expected := []dockeragent.TargetConfig{
{URL: "https://a", Token: "tokenA", InsecureSkipVerify: false},
{URL: "https://b", Token: "tokenB", InsecureSkipVerify: true},
}
for i, target := range specs {
if target != expected[i] {
t.Fatalf("target %d mismatch: expected %+v, got %+v", i, expected[i], target)
}
}
}
func TestSplitTargetSpecs(t *testing.T) {
values := splitTargetSpecs("https://a|tokenA;https://b|tokenB\nhttps://c|tokenC")
expected := []string{"https://a|tokenA", "https://b|tokenB", "https://c|tokenC"}
if len(values) != len(expected) {
t.Fatalf("expected %d values, got %d", len(expected), len(values))
}
for i, v := range values {
if v != expected[i] {
t.Fatalf("value %d mismatch: expected %q, got %q", i, expected[i], v)
}
}
}
func TestParseLogLevel(t *testing.T) {
tests := []struct {
name string
input string
wantLevel zerolog.Level
wantErr bool
errSubstr string
}{
// Valid levels
{
name: "debug level",
input: "debug",
wantLevel: zerolog.DebugLevel,
},
{
name: "info level",
input: "info",
wantLevel: zerolog.InfoLevel,
},
{
name: "warn level",
input: "warn",
wantLevel: zerolog.WarnLevel,
},
{
name: "error level",
input: "error",
wantLevel: zerolog.ErrorLevel,
},
{
name: "trace level",
input: "trace",
wantLevel: zerolog.TraceLevel,
},
// Case insensitivity
{
name: "uppercase DEBUG",
input: "DEBUG",
wantLevel: zerolog.DebugLevel,
},
{
name: "mixed case Info",
input: "Info",
wantLevel: zerolog.InfoLevel,
},
{
name: "uppercase WARN",
input: "WARN",
wantLevel: zerolog.WarnLevel,
},
// Whitespace handling
{
name: "leading whitespace",
input: " debug",
wantLevel: zerolog.DebugLevel,
},
{
name: "trailing whitespace",
input: "warn ",
wantLevel: zerolog.WarnLevel,
},
{
name: "both whitespace",
input: " error ",
wantLevel: zerolog.ErrorLevel,
},
{
name: "tabs",
input: "\tinfo\t",
wantLevel: zerolog.InfoLevel,
},
// Empty string defaults to info
{
name: "empty string defaults to info",
input: "",
wantLevel: zerolog.InfoLevel,
},
{
name: "whitespace only defaults to info",
input: " ",
wantLevel: zerolog.InfoLevel,
},
{
name: "tabs only defaults to info",
input: "\t\t",
wantLevel: zerolog.InfoLevel,
},
// Invalid levels
{
name: "invalid level returns error",
input: "invalid",
wantLevel: zerolog.InfoLevel,
wantErr: true,
errSubstr: "invalid log level",
},
{
name: "typo returns error",
input: "debuf",
wantLevel: zerolog.InfoLevel,
wantErr: true,
errSubstr: "must be debug, info, warn, or error",
},
{
name: "numeric 1 maps to info level",
input: "1",
wantLevel: zerolog.InfoLevel,
},
{
name: "numeric 0 maps to debug level",
input: "0",
wantLevel: zerolog.DebugLevel,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
level, err := parseLogLevel(tt.input)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
t.Fatalf("expected error containing %q, got %q", tt.errSubstr, err.Error())
}
} else {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
if level != tt.wantLevel {
t.Fatalf("expected level %v, got %v", tt.wantLevel, level)
}
})
}
}
func TestSplitStringList(t *testing.T) {
tests := []struct {
name string
input string
want []string
}{
// Empty input
{
name: "empty string returns nil",
input: "",
want: nil,
},
// Single item
{
name: "single item",
input: "foo",
want: []string{"foo"},
},
{
name: "single item with whitespace",
input: " foo ",
want: []string{"foo"},
},
// Comma delimiter
{
name: "comma separated",
input: "foo,bar,baz",
want: []string{"foo", "bar", "baz"},
},
{
name: "comma with spaces",
input: "foo, bar, baz",
want: []string{"foo", "bar", "baz"},
},
{
name: "comma with extra spaces",
input: " foo , bar , baz ",
want: []string{"foo", "bar", "baz"},
},
// Semicolon delimiter
{
name: "semicolon separated",
input: "foo;bar;baz",
want: []string{"foo", "bar", "baz"},
},
{
name: "semicolon with spaces",
input: "foo; bar; baz",
want: []string{"foo", "bar", "baz"},
},
// Newline delimiter
{
name: "newline separated",
input: "foo\nbar\nbaz",
want: []string{"foo", "bar", "baz"},
},
{
name: "newline with spaces",
input: "foo \n bar \n baz",
want: []string{"foo", "bar", "baz"},
},
// Carriage return delimiter
{
name: "carriage return separated",
input: "foo\rbar\rbaz",
want: []string{"foo", "bar", "baz"},
},
{
name: "CRLF (Windows line ending)",
input: "foo\r\nbar\r\nbaz",
want: []string{"foo", "bar", "baz"},
},
// Mixed delimiters
{
name: "mixed comma and semicolon",
input: "foo,bar;baz",
want: []string{"foo", "bar", "baz"},
},
{
name: "mixed all delimiters",
input: "a,b;c\nd\re",
want: []string{"a", "b", "c", "d", "e"},
},
{
name: "mixed with spaces",
input: "a , b ; c \n d \r e",
want: []string{"a", "b", "c", "d", "e"},
},
// Consecutive delimiters (should be filtered)
{
name: "double comma",
input: "foo,,bar",
want: []string{"foo", "bar"},
},
{
name: "multiple consecutive delimiters",
input: "foo,,,bar;;;baz",
want: []string{"foo", "bar", "baz"},
},
{
name: "trailing delimiter",
input: "foo,bar,",
want: []string{"foo", "bar"},
},
{
name: "leading delimiter",
input: ",foo,bar",
want: []string{"foo", "bar"},
},
{
name: "only delimiters returns empty slice",
input: ",;,;",
want: []string{},
},
// Whitespace-only items filtered
{
name: "whitespace between delimiters filtered",
input: "foo, ,bar",
want: []string{"foo", "bar"},
},
{
name: "tabs between delimiters filtered",
input: "foo,\t\t,bar",
want: []string{"foo", "bar"},
},
// Real-world examples
{
name: "network names list",
input: "bridge, host, none",
want: []string{"bridge", "host", "none"},
},
{
name: "container IDs",
input: "abc123;def456;ghi789",
want: []string{"abc123", "def456", "ghi789"},
},
{
name: "multiline config",
input: "web\napi\nworker",
want: []string{"web", "api", "worker"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := splitStringList(tt.input)
if !reflect.DeepEqual(got, tt.want) {
t.Fatalf("expected %v, got %v", tt.want, got)
}
})
}
}
+3 -30
View File
@@ -113,12 +113,7 @@ for build_name in "${build_order[@]}"; do
-o "$BUILD_DIR/pulse-$build_name" \
./cmd/pulse
# Build docker agent binary
env $build_env go build \
-ldflags="-s -w -X github.com/rcourtman/pulse-go-rewrite/internal/dockeragent.Version=v${VERSION}" \
-trimpath \
-o "$BUILD_DIR/pulse-docker-agent-$build_name" \
./cmd/pulse-docker-agent
# Build temperature proxy binary
env $build_env go build \
@@ -140,7 +135,7 @@ for build_name in "${build_order[@]}"; do
# Copy architecture-specific runtime binaries
cp "$BUILD_DIR/pulse-$build_name" "$staging_dir/bin/pulse"
cp "$BUILD_DIR/pulse-docker-agent-$build_name" "$staging_dir/bin/pulse-docker-agent"
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$staging_dir/bin/pulse-host-agent"
cp "$BUILD_DIR/pulse-sensor-proxy-$build_name" "$staging_dir/bin/pulse-sensor-proxy"
@@ -201,7 +196,7 @@ mkdir -p "$universal_dir/scripts"
# Copy all binaries to bin/ directory to maintain consistent structure
for build_name in "${build_order[@]}"; do
cp "$BUILD_DIR/pulse-$build_name" "$universal_dir/bin/pulse-${build_name}"
cp "$BUILD_DIR/pulse-docker-agent-$build_name" "$universal_dir/bin/pulse-docker-agent-${build_name}"
cp "$BUILD_DIR/pulse-host-agent-$build_name" "$universal_dir/bin/pulse-host-agent-${build_name}"
cp "$BUILD_DIR/pulse-agent-$build_name" "$universal_dir/bin/pulse-agent-${build_name}"
cp "$BUILD_DIR/pulse-sensor-proxy-$build_name" "$universal_dir/bin/pulse-sensor-proxy-${build_name}"
@@ -243,28 +238,6 @@ esac
EOF
chmod +x "$universal_dir/bin/pulse"
cat > "$universal_dir/bin/pulse-docker-agent" << 'EOF'
#!/bin/sh
# Auto-detect architecture and run appropriate pulse-docker-agent binary
ARCH=$(uname -m)
case "$ARCH" in
x86_64|amd64)
exec "$(dirname "$0")/pulse-docker-agent-linux-amd64" "$@"
;;
aarch64|arm64)
exec "$(dirname "$0")/pulse-docker-agent-linux-arm64" "$@"
;;
armv7l|armhf)
exec "$(dirname "$0")/pulse-docker-agent-linux-armv7" "$@"
;;
*)
echo "Unsupported architecture: $ARCH" >&2
exit 1
;;
esac
EOF
chmod +x "$universal_dir/bin/pulse-docker-agent"
cat > "$universal_dir/bin/pulse-sensor-proxy" << 'EOF'
#!/bin/sh