generate.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. ) {
  157. volumeConfigs := make(map[string]types.VolumeConfig)
  158. secretConfigs := make(map[string]types.SecretConfig)
  159. var serviceVolumeConfigs []types.ServiceVolumeConfig
  160. var serviceSecretConfigs []types.ServiceSecretConfig
  161. for _, volume := range volumes {
  162. serviceVC := types.ServiceVolumeConfig{
  163. Type: string(volume.Type),
  164. Source: volume.Source,
  165. Target: volume.Destination,
  166. ReadOnly: !volume.RW,
  167. }
  168. switch volume.Type {
  169. case mount.TypeVolume:
  170. serviceVC.Source = volume.Name
  171. vol := types.VolumeConfig{}
  172. if volume.Driver != "local" {
  173. vol.Driver = volume.Driver
  174. vol.Name = volume.Name
  175. }
  176. volumeConfigs[volume.Name] = vol
  177. serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
  178. case mount.TypeBind:
  179. if strings.HasPrefix(volume.Destination, "/run/secrets") {
  180. destination := strings.Split(volume.Destination, "/")
  181. secret := types.SecretConfig{
  182. Name: destination[len(destination)-1],
  183. File: strings.TrimPrefix(volume.Source, "/host_mnt"),
  184. }
  185. secretConfigs[secret.Name] = secret
  186. serviceSecretConfigs = append(serviceSecretConfigs, types.ServiceSecretConfig{
  187. Source: secret.Name,
  188. Target: volume.Destination,
  189. })
  190. } else {
  191. serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
  192. }
  193. }
  194. }
  195. return volumeConfigs, serviceVolumeConfigs, secretConfigs, serviceSecretConfigs
  196. }
  197. func (s *composeService) toComposeNetwork(networks map[string]*network.EndpointSettings) (map[string]types.NetworkConfig, map[string]*types.ServiceNetworkConfig) {
  198. networkConfigs := make(map[string]types.NetworkConfig)
  199. serviceNetworkConfigs := make(map[string]*types.ServiceNetworkConfig)
  200. for name, net := range networks {
  201. inspect, err := s.apiClient().NetworkInspect(context.Background(), name, network.InspectOptions{})
  202. if err != nil {
  203. networkConfigs[name] = types.NetworkConfig{}
  204. } else {
  205. networkConfigs[name] = types.NetworkConfig{
  206. Internal: inspect.Internal,
  207. }
  208. }
  209. serviceNetworkConfigs[name] = &types.ServiceNetworkConfig{
  210. Aliases: net.Aliases,
  211. }
  212. }
  213. return networkConfigs, serviceNetworkConfigs
  214. }
  215. func cleanDockerPreviousLabels(labels types.Labels) types.Labels {
  216. cleanedLabels := types.Labels{}
  217. for key, value := range labels {
  218. if !strings.HasPrefix(key, "com.docker.compose.") && !strings.HasPrefix(key, "desktop.docker.io") {
  219. cleanedLabels[key] = value
  220. }
  221. }
  222. return cleanedLabels
  223. }