Files
tiny-forge/internal/stack/validate.go
T
alexei.dolgolyov 75424a5f25
Build / build (push) Successful in 10m42s
feat: docker-compose stacks with Forge-themed UI
Adds a new Stacks feature: upload/edit docker-compose YAML,
deploy as atomic units, browse revisions, roll back, and
stream logs. Backend in internal/stack + internal/api/stacks.go,
persistent storage in internal/store/stacks.go.

Stacks pages (list, new, detail) use a modern Forge aesthetic —
Instrument Serif display type, JetBrains Mono for meta/code,
indigo ember accents, dot-grid hero, registration marks on
hover, terminal panel for logs. Palette is sourced from the
app's existing design tokens so the feature remains consistent
with the rest of Tinyforge.

Fonts self-hosted via @fontsource/instrument-serif and
@fontsource/jetbrains-mono to satisfy the strict CSP.
2026-04-16 03:48:37 +03:00

53 lines
1.7 KiB
Go

package stack
import (
"fmt"
"gopkg.in/yaml.v3"
)
// ComposeSpec is a minimal, lenient representation of a compose file.
// We only decode fields we need for validation + label-based proxy routing;
// everything else is preserved as-is and passed to `docker compose`.
type ComposeSpec struct {
Version string `yaml:"version,omitempty"`
Services map[string]ServiceSpec `yaml:"services"`
}
// ServiceSpec captures the subset of compose service fields we inspect.
type ServiceSpec struct {
Image string `yaml:"image,omitempty"`
Ports []any `yaml:"ports,omitempty"`
Labels map[string]string `yaml:"labels,omitempty"`
Privileged bool `yaml:"privileged,omitempty"`
}
// Parse decodes YAML into a ComposeSpec. Returns a descriptive error on failure.
func Parse(yamlText string) (ComposeSpec, error) {
var spec ComposeSpec
if err := yaml.Unmarshal([]byte(yamlText), &spec); err != nil {
return ComposeSpec{}, fmt.Errorf("invalid yaml: %w", err)
}
if len(spec.Services) == 0 {
return ComposeSpec{}, fmt.Errorf("compose file has no services")
}
return spec, nil
}
// Validate enforces Tinyforge-level constraints beyond compose schema validity.
// Current rules:
// - No service may set `privileged: true`.
// - Every service must declare an image (compose supports build: too, but
// Tinyforge v1 disallows building from context to avoid arbitrary-code exec).
func Validate(spec ComposeSpec) error {
for name, svc := range spec.Services {
if svc.Privileged {
return fmt.Errorf("service %q: privileged mode is not allowed", name)
}
if svc.Image == "" {
return fmt.Errorf("service %q: image is required (build contexts not supported)", name)
}
}
return nil
}