mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-06-04 12:32:58 +00:00
overhaul
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"containr/internal/docker"
|
||||
"containr/internal/types"
|
||||
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
)
|
||||
|
||||
type BuildManager struct {
|
||||
railpackBuilder *RailpackBuilder
|
||||
nixpacksBuilder *NixpacksBuilder
|
||||
dockerfileBuilder *DockerfileBuilder
|
||||
dockerClient *docker.Client
|
||||
cacheManager *CacheManager
|
||||
parallelBuilder *ParallelBuilder
|
||||
workDir string
|
||||
}
|
||||
|
||||
type BuildType string
|
||||
|
||||
const (
|
||||
BuildTypeRailpack BuildType = "railpack"
|
||||
BuildTypeNixpacks BuildType = "nixpacks"
|
||||
BuildTypeDockerfile BuildType = "dockerfile"
|
||||
BuildTypePrebuilt BuildType = "prebuilt"
|
||||
)
|
||||
|
||||
func NewBuildManager(workDir string, dockerClient *docker.Client) *BuildManager {
|
||||
cacheManager := NewCacheManager(filepath.Join(workDir, "cache"), dockerClient)
|
||||
parallelBuilder := NewParallelBuilder(nil, cacheManager, 4) // Default 4 workers
|
||||
|
||||
return &BuildManager{
|
||||
railpackBuilder: NewRailpackBuilder(workDir, dockerClient),
|
||||
nixpacksBuilder: NewNixpacksBuilder(workDir, dockerClient),
|
||||
dockerfileBuilder: NewDockerfileBuilder(workDir, dockerClient),
|
||||
dockerClient: dockerClient,
|
||||
cacheManager: cacheManager,
|
||||
parallelBuilder: parallelBuilder,
|
||||
workDir: workDir,
|
||||
}
|
||||
}
|
||||
|
||||
// DetectBuildType automatically detects the build type based on repository contents
|
||||
func (bm *BuildManager) DetectBuildType(ctx context.Context, repoPath string) (BuildType, error) {
|
||||
// Check for Dockerfile first
|
||||
if _, err := bm.dockerfileBuilder.DetectDockerfile(ctx, repoPath); err == nil {
|
||||
return BuildTypeDockerfile, nil
|
||||
}
|
||||
|
||||
// Check if Railpack can build this project (primary choice)
|
||||
if err := bm.railpackBuilder.DetectRailpack(ctx, repoPath); err == nil {
|
||||
return BuildTypeRailpack, nil
|
||||
}
|
||||
|
||||
// Default to Nixpacks as fallback
|
||||
return BuildTypeNixpacks, nil
|
||||
}
|
||||
|
||||
// Build executes the build process using the appropriate builder
|
||||
func (bm *BuildManager) Build(ctx context.Context, req *types.BuildRequest) (*types.BuildResponse, error) {
|
||||
// Detect build type if not specified
|
||||
if req.BuildType == "" {
|
||||
detectedType, err := bm.DetectBuildType(ctx, req.SourcePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to detect build type: %w", err)
|
||||
}
|
||||
req.BuildType = string(detectedType)
|
||||
}
|
||||
|
||||
switch BuildType(req.BuildType) {
|
||||
case BuildTypeRailpack:
|
||||
return bm.railpackBuilder.Build(ctx, req)
|
||||
case BuildTypeNixpacks:
|
||||
return bm.nixpacksBuilder.Build(ctx, req)
|
||||
case BuildTypeDockerfile:
|
||||
return bm.dockerfileBuilder.Build(ctx, req)
|
||||
case BuildTypePrebuilt:
|
||||
return bm.buildPrebuilt(ctx, req)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported build type: %s", req.BuildType)
|
||||
}
|
||||
}
|
||||
|
||||
// buildPrebuilt handles prebuilt image deployments
|
||||
func (bm *BuildManager) buildPrebuilt(ctx context.Context, req *types.BuildRequest) (*types.BuildResponse, error) {
|
||||
if req.PrebuiltImage == "" {
|
||||
return nil, fmt.Errorf("prebuilt image not specified")
|
||||
}
|
||||
|
||||
// Pull the prebuilt image
|
||||
auth := registry.AuthConfig{}
|
||||
_, err := bm.dockerClient.PullImage(ctx, req.PrebuiltImage, auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pull prebuilt image: %w", err)
|
||||
}
|
||||
|
||||
// Tag with our desired name and tag if different
|
||||
if req.ImageName != "" && req.ImageTag != "" {
|
||||
targetImage := fmt.Sprintf("%s:%s", req.ImageName, req.ImageTag)
|
||||
if targetImage != req.PrebuiltImage {
|
||||
err = bm.dockerClient.TagImage(ctx, req.PrebuiltImage, targetImage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to tag image: %w", err)
|
||||
}
|
||||
req.PrebuiltImage = targetImage
|
||||
}
|
||||
}
|
||||
|
||||
// Push to registry if specified
|
||||
if req.RegistryURL != "" {
|
||||
err = bm.dockerClient.PushImage(ctx, req.PrebuiltImage, req.RegistryURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to push image: %w", err)
|
||||
}
|
||||
req.PrebuiltImage = fmt.Sprintf("%s/%s", req.RegistryURL, req.PrebuiltImage)
|
||||
}
|
||||
|
||||
// Get image info
|
||||
imageInfo, err := bm.dockerClient.GetImageInfo(ctx, req.PrebuiltImage)
|
||||
if err != nil {
|
||||
// Continue even if we can't get image info
|
||||
imageInfo = &docker.ImageInfo{Size: 0}
|
||||
}
|
||||
|
||||
return &types.BuildResponse{
|
||||
ImageName: req.PrebuiltImage,
|
||||
ImageTag: req.ImageTag,
|
||||
Size: imageInfo.Size,
|
||||
Digest: imageInfo.Digest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ValidateBuildRequest validates the build request parameters
|
||||
func (bm *BuildManager) ValidateBuildRequest(ctx context.Context, req *types.BuildRequest) error {
|
||||
if req.SourcePath == "" && req.PrebuiltImage == "" {
|
||||
return fmt.Errorf("either source path or prebuilt image must be specified")
|
||||
}
|
||||
|
||||
if req.ImageName == "" {
|
||||
return fmt.Errorf("image name is required")
|
||||
}
|
||||
|
||||
if req.ImageTag == "" {
|
||||
return fmt.Errorf("image tag is required")
|
||||
}
|
||||
|
||||
// Validate source path exists if specified
|
||||
if req.SourcePath != "" {
|
||||
if _, err := filepath.Abs(req.SourcePath); err != nil {
|
||||
return fmt.Errorf("invalid source path: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBuildPlan returns the build plan for inspection without building
|
||||
func (bm *BuildManager) GetBuildPlan(ctx context.Context, req *types.BuildRequest) (interface{}, error) {
|
||||
switch BuildType(req.BuildType) {
|
||||
case BuildTypeRailpack:
|
||||
config := &RailpackConfig{
|
||||
BuildCmd: req.BuildCommand,
|
||||
StartCmd: req.StartCommand,
|
||||
Env: req.Environment,
|
||||
}
|
||||
return bm.railpackBuilder.GeneratePlan(ctx, req.SourcePath, config)
|
||||
case BuildTypeNixpacks:
|
||||
config := &NixpacksConfig{
|
||||
BuildCmd: req.BuildCommand,
|
||||
StartCmd: req.StartCommand,
|
||||
Env: req.Environment,
|
||||
}
|
||||
return bm.nixpacksBuilder.GeneratePlan(ctx, req.SourcePath, config)
|
||||
case BuildTypeDockerfile:
|
||||
dockerfile, err := bm.dockerfileBuilder.DetectDockerfile(ctx, req.SourcePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]string{
|
||||
"dockerfile": dockerfile,
|
||||
"context": req.SourcePath,
|
||||
}, nil
|
||||
case BuildTypePrebuilt:
|
||||
return map[string]string{
|
||||
"image": req.PrebuiltImage,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported build type: %s", req.BuildType)
|
||||
}
|
||||
}
|
||||
|
||||
// BuildWithCache attempts to build using cache first
|
||||
func (bm *BuildManager) BuildWithCache(ctx context.Context, req *types.BuildRequest) (*types.BuildResponse, error) {
|
||||
bm.parallelBuilder.buildManager = bm
|
||||
result, err := bm.parallelBuilder.BuildWithCache(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Response, nil
|
||||
}
|
||||
|
||||
// BuildParallel executes multiple builds in parallel
|
||||
func (bm *BuildManager) BuildParallel(ctx context.Context, requests []*types.BuildRequest) ([]*types.BuildResponse, error) {
|
||||
bm.parallelBuilder.buildManager = bm
|
||||
results, err := bm.parallelBuilder.BuildParallel(ctx, requests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responses := make([]*types.BuildResponse, len(results))
|
||||
for i, result := range results {
|
||||
if result.Success {
|
||||
responses[i] = result.Response
|
||||
} else {
|
||||
return nil, result.Error
|
||||
}
|
||||
}
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// SetRemoteCache configures remote cache settings
|
||||
func (bm *BuildManager) SetRemoteCache(config *RemoteCacheConfig) {
|
||||
bm.cacheManager.SetRemoteCache(config)
|
||||
}
|
||||
|
||||
// GetCacheStats returns cache statistics
|
||||
func (bm *BuildManager) GetCacheStats(ctx context.Context) (map[string]interface{}, error) {
|
||||
return bm.cacheManager.GetCacheStats(ctx)
|
||||
}
|
||||
|
||||
// CleanupCache removes old cache entries
|
||||
func (bm *BuildManager) CleanupCache(ctx context.Context, maxAge time.Duration) error {
|
||||
return bm.cacheManager.CleanupCache(ctx, maxAge)
|
||||
}
|
||||
Reference in New Issue
Block a user