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. "maps"
  18. "slices"
  19. "strings"
  20. "github.com/compose-spec/compose-go/v2/types"
  21. "github.com/docker/compose/v2/pkg/api"
  22. "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. )
  27. func (s *composeService) Generate(ctx context.Context, options api.GenerateOptions) (*types.Project, error) {
  28. filtersListNames := filters.NewArgs()
  29. filtersListIDs := filters.NewArgs()
  30. for _, containerName := range options.Containers {
  31. filtersListNames.Add("name", containerName)
  32. filtersListIDs.Add("id", containerName)
  33. }
  34. containers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
  35. Filters: filtersListNames,
  36. All: true,
  37. })
  38. if err != nil {
  39. return nil, err
  40. }
  41. containersByIds, err := s.apiClient().ContainerList(ctx, container.ListOptions{
  42. Filters: filtersListIDs,
  43. All: true,
  44. })
  45. if err != nil {
  46. return nil, err
  47. }
  48. for _, ctr := range containersByIds {
  49. if !slices.ContainsFunc(containers, func(summary container.Summary) bool {
  50. return summary.ID == ctr.ID
  51. }) {
  52. containers = append(containers, ctr)
  53. }
  54. }
  55. if len(containers) == 0 {
  56. return nil, fmt.Errorf("no container(s) found with the following name(s): %s", strings.Join(options.Containers, ","))
  57. }
  58. return s.createProjectFromContainers(containers, options.ProjectName)
  59. }
  60. func (s *composeService) createProjectFromContainers(containers []container.Summary, projectName string) (*types.Project, error) {
  61. project := &types.Project{}
  62. services := types.Services{}
  63. networks := types.Networks{}
  64. volumes := types.Volumes{}
  65. secrets := types.Secrets{}
  66. if projectName != "" {
  67. project.Name = projectName
  68. }
  69. for _, c := range containers {
  70. // if the container is from a previous Compose application, use the existing service name
  71. serviceLabel, ok := c.Labels[api.ServiceLabel]
  72. if !ok {
  73. serviceLabel = getCanonicalContainerName(c)
  74. }
  75. service, ok := services[serviceLabel]
  76. if !ok {
  77. service = types.ServiceConfig{
  78. Name: serviceLabel,
  79. Image: c.Image,
  80. Labels: c.Labels,
  81. }
  82. }
  83. service.Scale = increment(service.Scale)
  84. inspect, err := s.apiClient().ContainerInspect(context.Background(), c.ID)
  85. if err != nil {
  86. services[serviceLabel] = service
  87. continue
  88. }
  89. s.extractComposeConfiguration(&service, inspect, volumes, secrets, networks)
  90. service.Labels = cleanDockerPreviousLabels(service.Labels)
  91. services[serviceLabel] = service
  92. }
  93. project.Services = services
  94. project.Networks = networks
  95. project.Volumes = volumes
  96. project.Secrets = secrets
  97. return project, nil
  98. }
  99. func (s *composeService) extractComposeConfiguration(service *types.ServiceConfig, inspect container.InspectResponse, volumes types.Volumes, secrets types.Secrets, networks types.Networks) {
  100. service.Environment = types.NewMappingWithEquals(inspect.Config.Env)
  101. if inspect.Config.Healthcheck != nil {
  102. healthConfig := inspect.Config.Healthcheck
  103. service.HealthCheck = s.toComposeHealthCheck(healthConfig)
  104. }
  105. if len(inspect.Mounts) > 0 {
  106. detectedVolumes, volumeConfigs, detectedSecrets, secretsConfigs := s.toComposeVolumes(inspect.Mounts)
  107. service.Volumes = append(service.Volumes, volumeConfigs...)
  108. service.Secrets = append(service.Secrets, secretsConfigs...)
  109. maps.Copy(volumes, detectedVolumes)
  110. maps.Copy(secrets, detectedSecrets)
  111. }
  112. if len(inspect.NetworkSettings.Networks) > 0 {
  113. detectedNetworks, networkConfigs := s.toComposeNetwork(inspect.NetworkSettings.Networks)
  114. service.Networks = networkConfigs
  115. maps.Copy(networks, detectedNetworks)
  116. }
  117. if len(inspect.HostConfig.PortBindings) > 0 {
  118. for key, portBindings := range inspect.HostConfig.PortBindings {
  119. for _, portBinding := range portBindings {
  120. service.Ports = append(service.Ports, types.ServicePortConfig{
  121. Target: uint32(key.Int()),
  122. Published: portBinding.HostPort,
  123. Protocol: key.Proto(),
  124. HostIP: portBinding.HostIP,
  125. })
  126. }
  127. }
  128. }
  129. }
  130. func (s *composeService) toComposeHealthCheck(healthConfig *container.HealthConfig) *types.HealthCheckConfig {
  131. var healthCheck types.HealthCheckConfig
  132. healthCheck.Test = healthConfig.Test
  133. if healthConfig.Timeout != 0 {
  134. timeout := types.Duration(healthConfig.Timeout)
  135. healthCheck.Timeout = &timeout
  136. }
  137. if healthConfig.Interval != 0 {
  138. interval := types.Duration(healthConfig.Interval)
  139. healthCheck.Interval = &interval
  140. }
  141. if healthConfig.StartPeriod != 0 {
  142. startPeriod := types.Duration(healthConfig.StartPeriod)
  143. healthCheck.StartPeriod = &startPeriod
  144. }
  145. if healthConfig.StartInterval != 0 {
  146. startInterval := types.Duration(healthConfig.StartInterval)
  147. healthCheck.StartInterval = &startInterval
  148. }
  149. if healthConfig.Retries != 0 {
  150. retries := uint64(healthConfig.Retries)
  151. healthCheck.Retries = &retries
  152. }
  153. return &healthCheck
  154. }
  155. func (s *composeService) toComposeVolumes(volumes []container.MountPoint) (map[string]types.VolumeConfig,
  156. []types.ServiceVolumeConfig, map[string]types.SecretConfig, []types.ServiceSecretConfig,
  157. ) {
  158. volumeConfigs := make(map[string]types.VolumeConfig)
  159. secretConfigs := make(map[string]types.SecretConfig)
  160. var serviceVolumeConfigs []types.ServiceVolumeConfig
  161. var serviceSecretConfigs []types.ServiceSecretConfig
  162. for _, volume := range volumes {
  163. serviceVC := types.ServiceVolumeConfig{
  164. Type: string(volume.Type),
  165. Source: volume.Source,
  166. Target: volume.Destination,
  167. ReadOnly: !volume.RW,
  168. }
  169. switch volume.Type {
  170. case mount.TypeVolume:
  171. serviceVC.Source = volume.Name
  172. vol := types.VolumeConfig{}
  173. if volume.Driver != "local" {
  174. vol.Driver = volume.Driver
  175. vol.Name = volume.Name
  176. }
  177. volumeConfigs[volume.Name] = vol
  178. serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
  179. case mount.TypeBind:
  180. if strings.HasPrefix(volume.Destination, "/run/secrets") {
  181. destination := strings.Split(volume.Destination, "/")
  182. secret := types.SecretConfig{
  183. Name: destination[len(destination)-1],
  184. File: strings.TrimPrefix(volume.Source, "/host_mnt"),
  185. }
  186. secretConfigs[secret.Name] = secret
  187. serviceSecretConfigs = append(serviceSecretConfigs, types.ServiceSecretConfig{
  188. Source: secret.Name,
  189. Target: volume.Destination,
  190. })
  191. } else {
  192. serviceVolumeConfigs = append(serviceVolumeConfigs, serviceVC)
  193. }
  194. }
  195. }
  196. return volumeConfigs, serviceVolumeConfigs, secretConfigs, serviceSecretConfigs
  197. }
  198. func (s *composeService) toComposeNetwork(networks map[string]*network.EndpointSettings) (map[string]types.NetworkConfig, map[string]*types.ServiceNetworkConfig) {
  199. networkConfigs := make(map[string]types.NetworkConfig)
  200. serviceNetworkConfigs := make(map[string]*types.ServiceNetworkConfig)
  201. for name, net := range networks {
  202. inspect, err := s.apiClient().NetworkInspect(context.Background(), name, network.InspectOptions{})
  203. if err != nil {
  204. networkConfigs[name] = types.NetworkConfig{}
  205. } else {
  206. networkConfigs[name] = types.NetworkConfig{
  207. Internal: inspect.Internal,
  208. }
  209. }
  210. serviceNetworkConfigs[name] = &types.ServiceNetworkConfig{
  211. Aliases: net.Aliases,
  212. }
  213. }
  214. return networkConfigs, serviceNetworkConfigs
  215. }
  216. func cleanDockerPreviousLabels(labels types.Labels) types.Labels {
  217. cleanedLabels := types.Labels{}
  218. for key, value := range labels {
  219. if !strings.HasPrefix(key, "com.docker.compose.") && !strings.HasPrefix(key, "desktop.docker.io") {
  220. cleanedLabels[key] = value
  221. }
  222. }
  223. return cleanedLabels
  224. }