Files
tiny-forge/internal/webhook/autocreate.go
T
alexei.dolgolyov 791cd4d6af
Build / build (push) Successful in 12m20s
feat: rename Docker Watcher to Tinyforge
Rebrand the project as Tinyforge to reflect its evolution from a Docker
container watcher into a self-hosted mini CI/deployment platform.

Rename covers: Go module path, Docker labels, DB/config filenames,
JWT issuer, Dockerfile binary, docker-compose, CI workflows, frontend
i18n, README with static sites docs, and all code comments.
2026-04-12 21:30:39 +03:00

92 lines
2.4 KiB
Go

package webhook
import (
"context"
"fmt"
"log/slog"
"strings"
"github.com/alexei/tinyforge/internal/docker"
"github.com/alexei/tinyforge/internal/store"
)
// AutoCreateProject creates a new project and a default "dev" stage from an
// unknown image. It inspects the Docker image to extract defaults (EXPOSE port,
// healthcheck, labels).
//
// The auto-created project uses:
// - Name: derived from image name (e.g. "web-app-launcher")
// - Image: full owner/name path
// - Port: first EXPOSE port from the image, or 0 if none
// - Healthcheck: from image HEALTHCHECK instruction, if present
// - A single "dev" stage with auto_deploy=true and tag_pattern="*"
func AutoCreateProject(
ctx context.Context,
st *store.Store,
inspector ImageInspector,
parsed ParsedImage,
) (store.Project, store.Stage, error) {
// Build the full image ref for inspection (registry/owner/name:tag).
imageRef := buildImageRef(parsed)
var port int
var healthcheck string
// Attempt to inspect the image for metadata. If inspection fails (image
// not pulled locally), proceed with zero defaults.
if inspector != nil {
info, err := inspector.InspectImage(ctx, imageRef)
if err != nil {
slog.Warn("webhook: image inspection failed, using defaults", "image", imageRef, "error", err)
} else {
port = docker.ExtractPort(info.ExposedPorts)
healthcheck = info.Healthcheck
}
}
project, err := st.CreateProject(store.Project{
Name: parsed.Name,
Registry: parsed.Registry,
Image: parsed.FullName(),
Port: port,
Healthcheck: healthcheck,
Env: "{}",
Volumes: "{}",
})
if err != nil {
return store.Project{}, store.Stage{}, fmt.Errorf("create project: %w", err)
}
stage, err := st.CreateStage(store.Stage{
ProjectID: project.ID,
Name: "dev",
TagPattern: "*",
AutoDeploy: true,
MaxInstances: 1,
})
if err != nil {
return store.Project{}, store.Stage{}, fmt.Errorf("create default stage: %w", err)
}
return project, stage, nil
}
// buildImageRef reconstructs a pullable image reference from parsed components.
func buildImageRef(parsed ParsedImage) string {
var parts []string
if parsed.Registry != "" {
parts = append(parts, parsed.Registry)
}
if parsed.Owner != "" {
parts = append(parts, parsed.Owner)
}
parts = append(parts, parsed.Name)
ref := strings.Join(parts, "/")
if parsed.Tag != "" {
ref += ":" + parsed.Tag
}
return ref
}