123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- package services
- import (
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "git.linuxforward.com/byop/byop-engine/models"
- "github.com/sirupsen/logrus"
- "gopkg.in/yaml.v3"
- )
- // ComposeParser handles parsing and analyzing docker-compose.yml files
- type ComposeParser struct {
- entry *logrus.Entry
- }
- // NewComposeParser creates a new ComposeParser instance
- func NewComposeParser() *ComposeParser {
- return &ComposeParser{
- entry: logrus.WithField("service", "ComposeParser"),
- }
- }
- // ComposeFile represents the structure of a docker-compose.yml file
- type ComposeFile struct {
- Version string `yaml:"version,omitempty"`
- Services map[string]ComposeServiceYAML `yaml:"services"`
- }
- // ComposeService represents a service in docker-compose.yml
- type ComposeServiceYAML struct {
- Image string `yaml:"image,omitempty"`
- Build interface{} `yaml:"build,omitempty"` // Can be string or object
- Ports []interface{} `yaml:"ports,omitempty"` // Can be strings or objects
- Environment interface{} `yaml:"environment,omitempty"` // Can be array or map
- Volumes []interface{} `yaml:"volumes,omitempty"`
- Depends_On []string `yaml:"depends_on,omitempty"`
- }
- // ComposeServiceBuild represents the build configuration
- type ComposeServiceBuild struct {
- Context string `yaml:"context,omitempty"`
- Dockerfile string `yaml:"dockerfile,omitempty"`
- }
- // ParseComposeFile parses a docker-compose.yml file from a directory path
- func (cp *ComposeParser) ParseComposeFile(projectDir, composePath string) (*models.AppImportReview, error) {
- fullComposePath := filepath.Join(projectDir, composePath)
- cp.entry.Infof("Parsing compose file at: %s", fullComposePath)
- // Check if compose file exists
- if _, err := os.Stat(fullComposePath); os.IsNotExist(err) {
- return &models.AppImportReview{
- Valid: false,
- Error: fmt.Sprintf("docker-compose.yml file not found at %s", composePath),
- }, nil
- }
- // Read the compose file
- content, err := os.ReadFile(fullComposePath)
- if err != nil {
- return &models.AppImportReview{
- Valid: false,
- Error: fmt.Sprintf("Failed to read docker-compose.yml: %v", err),
- }, nil
- }
- return cp.ParseComposeContent(string(content), filepath.Base(projectDir))
- }
- // ParseComposeContent parses docker-compose content directly from string
- func (cp *ComposeParser) ParseComposeContent(content, projectName string) (*models.AppImportReview, error) {
- cp.entry.Info("Parsing compose content from string")
- var composeFile ComposeFile
- if err := yaml.Unmarshal([]byte(content), &composeFile); err != nil {
- cp.entry.Errorf("Failed to parse YAML: %v", err)
- return &models.AppImportReview{
- Valid: false,
- Error: fmt.Sprintf("Failed to parse docker-compose.yml: %v", err),
- }, nil
- }
- if len(composeFile.Services) == 0 {
- return &models.AppImportReview{
- Valid: false,
- Error: "No services found in docker-compose.yml",
- }, nil
- }
- // Convert compose services to our internal format
- services := make([]models.ComposeService, 0, len(composeFile.Services))
- for serviceName, serviceConfig := range composeFile.Services {
- composeService := models.ComposeService{
- Name: serviceName,
- }
- // Determine if this is a build or image service
- if serviceConfig.Build != nil {
- composeService.Source = "build"
- // Handle build configuration (can be string or object)
- switch build := serviceConfig.Build.(type) {
- case string:
- composeService.BuildContext = build
- composeService.Dockerfile = "Dockerfile" // default
- case map[string]interface{}:
- if context, ok := build["context"].(string); ok {
- composeService.BuildContext = context
- }
- if dockerfile, ok := build["dockerfile"].(string); ok {
- composeService.Dockerfile = dockerfile
- } else {
- composeService.Dockerfile = "Dockerfile" // default
- }
- }
- } else if serviceConfig.Image != "" {
- composeService.Source = "image"
- composeService.Image = serviceConfig.Image
- } else {
- cp.entry.Warnf("Service %s has neither build nor image configuration", serviceName)
- continue
- }
- // Extract ports
- for _, port := range serviceConfig.Ports {
- switch p := port.(type) {
- case string:
- composeService.Ports = append(composeService.Ports, p)
- case int:
- composeService.Ports = append(composeService.Ports, strconv.Itoa(p))
- case map[string]interface{}:
- if target, ok := p["target"].(int); ok {
- portStr := strconv.Itoa(target)
- if published, ok := p["published"].(int); ok {
- portStr = fmt.Sprintf("%d:%d", published, target)
- }
- composeService.Ports = append(composeService.Ports, portStr)
- }
- }
- }
- // Extract environment variables
- if serviceConfig.Environment != nil {
- composeService.Environment = make(map[string]string)
- switch env := serviceConfig.Environment.(type) {
- case []interface{}:
- for _, item := range env {
- if envStr, ok := item.(string); ok {
- parts := strings.SplitN(envStr, "=", 2)
- if len(parts) == 2 {
- composeService.Environment[parts[0]] = parts[1]
- }
- }
- }
- case map[string]interface{}:
- for key, value := range env {
- if valueStr, ok := value.(string); ok {
- composeService.Environment[key] = valueStr
- }
- }
- }
- }
- // Extract volumes
- for _, volume := range serviceConfig.Volumes {
- if volumeStr, ok := volume.(string); ok {
- composeService.Volumes = append(composeService.Volumes, volumeStr)
- }
- }
- services = append(services, composeService)
- }
- // Use provided project name or default
- appName := projectName
- if appName == "" {
- appName = "imported-app"
- }
- review := &models.AppImportReview{
- AppName: appName,
- Services: services,
- Valid: true,
- }
- cp.entry.Infof("Successfully parsed compose file with %d services", len(services))
- return review, nil
- }
|