diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index fb0088428f..3bcef505c8 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -41,6 +41,8 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" + + "golang.org/x/exp/maps" ) var stdioToStdout bool @@ -166,7 +168,7 @@ func getContainerNameWithoutProject(c moby.Container) string { } // projectFromName builds a types.Project based on actual resources with compose labels set -func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) { +func (s *composeService) projectFromName(containers Containers, projectName string, generateMode bool, services ...string) (*types.Project, error) { project := &types.Project{ Name: projectName, Services: types.Services{}, @@ -175,6 +177,11 @@ func (s *composeService) projectFromName(containers Containers, projectName stri return project, fmt.Errorf("no container found for project %q: %w", projectName, api.ErrNotFound) } set := types.Services{} + networks := types.Networks{} + volumes := types.Volumes{} + secrets := types.Secrets{} + configs := types.Configs{} + for _, c := range containers { serviceLabel, ok := c.Labels[api.ServiceLabel] if !ok { @@ -190,6 +197,13 @@ func (s *composeService) projectFromName(containers Containers, projectName stri } service.Scale = increment(service.Scale) + if generateMode { + inspect, err := s.apiClient().ContainerInspect(context.Background(), c.ID) + if err != nil { + continue + } + s.extractComposeConfiguration(service, inspect, volumes, secrets) + } set[serviceLabel] = service } for name, service := range set { @@ -217,6 +231,10 @@ func (s *composeService) projectFromName(containers Containers, projectName stri } } project.Services = set + project.Networks = networks + project.Volumes = volumes + project.Secrets = secrets + project.Configs = configs SERVICES: for _, qs := range services { @@ -235,6 +253,21 @@ SERVICES: return project, nil } +func (s *composeService) extractComposeConfiguration(service types.ServiceConfig, inspect moby.ContainerJSON, volumes types.Volumes, secrets types.Secrets) { + service.Environment = types.NewMappingWithEquals(inspect.Config.Env) + if inspect.Config.Healthcheck != nil { + healthConfig := inspect.Config.Healthcheck + service.HealthCheck = s.toComposeHealthCheck(healthConfig) + } + if len(inspect.Mounts) > 0 { + detectedVolumes, volumeConfigs, detectedSecrets, secretsConfigs := s.toComposeVolumes(inspect.Mounts) + service.Volumes = append(service.Volumes, volumeConfigs...) + service.Secrets = append(service.Secrets, secretsConfigs...) + maps.Copy(volumes, detectedVolumes) + maps.Copy(secrets, detectedSecrets) + } +} + func increment(scale *int) *int { i := 1 if scale != nil { diff --git a/pkg/compose/convert.go b/pkg/compose/convert.go index e9ff7838b9..3016e37820 100644 --- a/pkg/compose/convert.go +++ b/pkg/compose/convert.go @@ -20,10 +20,13 @@ import ( "context" "errors" "fmt" + "strings" "time" compose "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/versions" ) @@ -97,3 +100,73 @@ func ToSeconds(d *compose.Duration) *int { s := int(time.Duration(*d).Seconds()) return &s } + +func (s *composeService) toComposeHealthCheck(healthConfig *container.HealthConfig) *compose.HealthCheckConfig { + var healthCheck compose.HealthCheckConfig + healthCheck.Test = healthConfig.Test + if healthConfig.Timeout != 0 { + timeout := compose.Duration(healthConfig.Timeout) + healthCheck.Timeout = &timeout + } + if healthConfig.Interval != 0 { + interval := compose.Duration(healthConfig.Interval) + healthCheck.Interval = &interval + } + if healthConfig.StartPeriod != 0 { + startPeriod := compose.Duration(healthConfig.StartPeriod) + healthCheck.StartPeriod = &startPeriod + } + if healthConfig.StartInterval != 0 { + startInterval := compose.Duration(healthConfig.StartInterval) + healthCheck.StartInterval = &startInterval + } + if healthConfig.Retries != 0 { + retries := uint64(healthConfig.Retries) + healthCheck.Retries = &retries + } + return &healthCheck +} + +func (s *composeService) toComposeVolumes(volumes []types.MountPoint) (map[string]compose.VolumeConfig, + []compose.ServiceVolumeConfig, map[string]compose.SecretConfig, []compose.ServiceSecretConfig) { + volumeConfigs := make(map[string]compose.VolumeConfig) + secretConfigs := make(map[string]compose.SecretConfig) + var serviceVolumeConfigs []compose.ServiceVolumeConfig + var serviceSecretConfigs []compose.ServiceSecretConfig + + for _, volume := range volumes { + serviceVC := compose.ServiceVolumeConfig{ + Type: string(volume.Type), + Source: volume.Source, + Target: volume.Destination, + ReadOnly: !volume.RW, + } + switch volume.Type { + case mount.TypeVolume: + serviceVC.Source = volume.Name + vol := compose.VolumeConfig{} + if volume.Driver != "local" { + vol.Driver = volume.Driver + vol.Name = volume.Name + } + volumeConfigs[volume.Name] = vol + serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC) + case mount.TypeBind: + if strings.HasPrefix(volume.Destination, "/run/secrets") { + destination := strings.Split(volume.Destination, "/") + secret := compose.SecretConfig{ + Name: destination[len(destination)-1], + File: strings.TrimPrefix(volume.Source, "/host_mnt"), + } + secretConfigs[secret.Name] = secret + serviceSecretConfigs = append(serviceSecretConfigs, compose.ServiceSecretConfig{ + Source: secret.Name, + Target: volume.Destination, + }) + } else { + serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC) + } + } + } + return volumeConfigs, serviceVolumeConfigs, secretConfigs, serviceSecretConfigs +} diff --git a/pkg/compose/down.go b/pkg/compose/down.go index c3fc35bdce..3c9b3a101c 100644 --- a/pkg/compose/down.go +++ b/pkg/compose/down.go @@ -368,7 +368,7 @@ func (s *composeService) stopAndRemoveContainer(ctx context.Context, container m func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) { containers = containers.filter(isNotOneOff) - p, err := s.projectFromName(containers, projectName) + p, err := s.projectFromName(containers, projectName, false) if err != nil && !api.IsNotFoundError(err) { return nil, err } diff --git a/pkg/compose/generate.go b/pkg/compose/generate.go index 2842ffa9b8..a329fd9ca7 100644 --- a/pkg/compose/generate.go +++ b/pkg/compose/generate.go @@ -57,5 +57,5 @@ func (s *composeService) Generate(ctx context.Context, options api.ReverseOption } } - return s.projectFromName(containers, options.Project.Name) + return s.projectFromName(containers, options.Project.Name, true) } diff --git a/pkg/compose/start.go b/pkg/compose/start.go index e4ce16fd47..0cdd736e81 100644 --- a/pkg/compose/start.go +++ b/pkg/compose/start.go @@ -51,7 +51,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options return err } - project, err = s.projectFromName(containers, projectName, options.AttachTo...) + project, err = s.projectFromName(containers, projectName, false, options.AttachTo...) if err != nil { return err }