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 }