generate.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /*
  2. Copyright 2023 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package compose
  14. import (
  15. "context"
  16. "fmt"
  17. "strings"
  18. "github.com/compose-spec/compose-go/v2/types"
  19. "github.com/docker/compose/v2/pkg/api"
  20. "github.com/docker/compose/v2/pkg/utils"
  21. moby "github.com/docker/docker/api/types"
  22. containerType "github.com/docker/docker/api/types/container"
  23. "github.com/docker/docker/api/types/filters"
  24. "github.com/docker/docker/api/types/mount"
  25. "github.com/docker/docker/api/types/network"
  26. "golang.org/x/exp/maps"
  27. )
  28. func (s *composeService) Generate(ctx context.Context, options api.GenerateOptions) (*types.Project, error) {
  29. filtersListNames := filters.NewArgs()
  30. filtersListIDs := filters.NewArgs()
  31. for _, containerName := range options.Containers {
  32. filtersListNames.Add("name", containerName)
  33. filtersListIDs.Add("id", containerName)
  34. }
  35. containers, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{
  36. Filters: filtersListNames,
  37. All: true,
  38. })
  39. if err != nil {
  40. return nil, err
  41. }
  42. containersByIds, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{
  43. Filters: filtersListIDs,
  44. All: true,
  45. })
  46. if err != nil {
  47. return nil, err
  48. }
  49. for _, container := range containersByIds {
  50. if !utils.Contains(containers, container) {
  51. containers = append(containers, container)
  52. }
  53. }
  54. if len(containers) == 0 {
  55. return nil, fmt.Errorf("no container(s) found with the following name(s): %s", strings.Join(options.Containers, ","))
  56. }
  57. return s.createProjectFromContainers(containers, options.ProjectName)
  58. }
  59. func (s *composeService) createProjectFromContainers(containers []moby.Container, projectName string) (*types.Project, error) {
  60. project := &types.Project{}
  61. services := types.Services{}
  62. networks := types.Networks{}
  63. volumes := types.Volumes{}
  64. secrets := types.Secrets{}
  65. if projectName != "" {
  66. project.Name = projectName
  67. }
  68. for _, c := range containers {
  69. // if the container is from a previous Compose application, use the existing service name
  70. serviceLabel, ok := c.Labels[api.ServiceLabel]
  71. if !ok {
  72. serviceLabel = getCanonicalContainerName(c)
  73. }
  74. service, ok := services[serviceLabel]
  75. if !ok {
  76. service = types.ServiceConfig{
  77. Name: serviceLabel,
  78. Image: c.Image,
  79. Labels: c.Labels,
  80. }
  81. }
  82. service.Scale = increment(service.Scale)
  83. inspect, err := s.apiClient().ContainerInspect(context.Background(), c.ID)
  84. if err != nil {
  85. services[serviceLabel] = service
  86. continue
  87. }
  88. s.extractComposeConfiguration(&service, inspect, volumes, secrets, networks)
  89. service.Labels = cleanDockerPreviousLabels(service.Labels)
  90. services[serviceLabel] = service
  91. }
  92. project.Services = services
  93. project.Networks = networks
  94. project.Volumes = volumes
  95. project.Secrets = secrets
  96. return project, nil
  97. }
  98. func (s *composeService) extractComposeConfiguration(service *types.ServiceConfig, inspect moby.ContainerJSON, volumes types.Volumes, secrets types.Secrets, networks types.Networks) {
  99. service.Environment = types.NewMappingWithEquals(inspect.Config.Env)
  100. if inspect.Config.Healthcheck != nil {
  101. healthConfig := inspect.Config.Healthcheck
  102. service.HealthCheck = s.toComposeHealthCheck(healthConfig)
  103. }
  104. if len(inspect.Mounts) > 0 {
  105. detectedVolumes, volumeConfigs, detectedSecrets, secretsConfigs := s.toComposeVolumes(inspect.Mounts)
  106. service.Volumes = append(service.Volumes, volumeConfigs...)
  107. service.Secrets = append(service.Secrets, secretsConfigs...)
  108. maps.Copy(volumes, detectedVolumes)
  109. maps.Copy(secrets, detectedSecrets)
  110. }
  111. if len(inspect.NetworkSettings.Networks) > 0 {
  112. detectedNetworks, networkConfigs := s.toComposeNetwork(inspect.NetworkSettings.Networks)
  113. service.Networks = networkConfigs
  114. maps.Copy(networks, detectedNetworks)
  115. }
  116. if len(inspect.HostConfig.PortBindings) > 0 {
  117. for key, portBindings := range inspect.HostConfig.PortBindings {
  118. for _, portBinding := range portBindings {
  119. service.Ports = append(service.Ports, types.ServicePortConfig{
  120. Target: uint32(key.Int()),
  121. Published: portBinding.HostPort,
  122. Protocol: key.Proto(),
  123. HostIP: portBinding.HostIP,
  124. })
  125. }
  126. }
  127. }
  128. }
  129. func (s *composeService) toComposeHealthCheck(healthConfig *containerType.HealthConfig) *types.HealthCheckConfig {
  130. var healthCheck types.HealthCheckConfig
  131. healthCheck.Test = healthConfig.Test
  132. if healthConfig.Timeout != 0 {
  133. timeout := types.Duration(healthConfig.Timeout)
  134. healthCheck.Timeout = &timeout
  135. }
  136. if healthConfig.Interval != 0 {
  137. interval := types.Duration(healthConfig.Interval)
  138. healthCheck.Interval = &interval
  139. }
  140. if healthConfig.StartPeriod != 0 {
  141. startPeriod := types.Duration(healthConfig.StartPeriod)
  142. healthCheck.StartPeriod = &startPeriod
  143. }
  144. if healthConfig.StartInterval != 0 {
  145. startInterval := types.Duration(healthConfig.StartInterval)
  146. healthCheck.StartInterval = &startInterval
  147. }
  148. if healthConfig.Retries != 0 {
  149. retries := uint64(healthConfig.Retries)
  150. healthCheck.Retries = &retries
  151. }
  152. return &healthCheck
  153. }
  154. func (s *composeService) toComposeVolumes(volumes []moby.MountPoint) (map[string]types.VolumeConfig,
  155. []types.ServiceVolumeConfig, map[string]types.SecretConfig, []types.ServiceSecretConfig) {
  156. volumeConfigs := make(map[string]types.VolumeConfig)
  157. secretConfigs := make(map[string]types.SecretConfig)
  158. var serviceVolumeConfigs []types.ServiceVolumeConfig
  159. var serviceSecretConfigs []types.ServiceSecretConfig
  160. for _, volume := range volumes {
  161. serviceVC := types.ServiceVolumeConfig{
  162. Type: string(volume.Type),
  163. Source: volume.Source,
  164. Target: volume.Destination,
  165. ReadOnly: !volume.RW,
  166. }
  167. switch volume.Type {
  168. case mount.TypeVolume:
  169. serviceVC.Source = volume.Name
  170. vol := types.VolumeConfig{}
  171. if volume.Driver != "local" {
  172. vol.Driver = volume.Driver
  173. vol.Name = volume.Name
  174. }
  175. volumeConfigs[volume.Name] = vol
  176. serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
  177. case mount.TypeBind:
  178. if strings.HasPrefix(volume.Destination, "/run/secrets") {
  179. destination := strings.Split(volume.Destination, "/")
  180. secret := types.SecretConfig{
  181. Name: destination[len(destination)-1],
  182. File: strings.TrimPrefix(volume.Source, "/host_mnt"),
  183. }
  184. secretConfigs[secret.Name] = secret
  185. serviceSecretConfigs = append(serviceSecretConfigs, types.ServiceSecretConfig{
  186. Source: secret.Name,
  187. Target: volume.Destination,
  188. })
  189. } else {
  190. serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
  191. }
  192. }
  193. }
  194. return volumeConfigs, serviceVolumeConfigs, secretConfigs, serviceSecretConfigs
  195. }
  196. func (s *composeService) toComposeNetwork(networks map[string]*network.EndpointSettings) (map[string]types.NetworkConfig, map[string]*types.ServiceNetworkConfig) {
  197. networkConfigs := make(map[string]types.NetworkConfig)
  198. serviceNetworkConfigs := make(map[string]*types.ServiceNetworkConfig)
  199. for name, net := range networks {
  200. inspect, err := s.apiClient().NetworkInspect(context.Background(), name, network.InspectOptions{})
  201. if err != nil {
  202. networkConfigs[name] = types.NetworkConfig{}
  203. } else {
  204. networkConfigs[name] = types.NetworkConfig{
  205. Internal: inspect.Internal,
  206. }
  207. }
  208. serviceNetworkConfigs[name] = &types.ServiceNetworkConfig{
  209. Aliases: net.Aliases,
  210. }
  211. }
  212. return networkConfigs, serviceNetworkConfigs
  213. }
  214. func cleanDockerPreviousLabels(labels types.Labels) types.Labels {
  215. cleanedLabels := types.Labels{}
  216. for key, value := range labels {
  217. if !strings.HasPrefix(key, "com.docker.compose.") && !strings.HasPrefix(key, "desktop.docker.io") {
  218. cleanedLabels[key] = value
  219. }
  220. }
  221. return cleanedLabels
  222. }