generate.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. "github.com/docker/docker/api/types/container"
  22. "github.com/docker/docker/api/types/filters"
  23. "github.com/docker/docker/api/types/mount"
  24. "github.com/docker/docker/api/types/network"
  25. "golang.org/x/exp/maps"
  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 !utils.Contains(containers, ctr) {
  50. containers = append(containers, ctr)
  51. }
  52. }
  53. if len(containers) == 0 {
  54. return nil, fmt.Errorf("no container(s) found with the following name(s): %s", strings.Join(options.Containers, ","))
  55. }
  56. return s.createProjectFromContainers(containers, options.ProjectName)
  57. }
  58. func (s *composeService) createProjectFromContainers(containers []container.Summary, projectName string) (*types.Project, error) {
  59. project := &types.Project{}
  60. services := types.Services{}
  61. networks := types.Networks{}
  62. volumes := types.Volumes{}
  63. secrets := types.Secrets{}
  64. if projectName != "" {
  65. project.Name = projectName
  66. }
  67. for _, c := range containers {
  68. // if the container is from a previous Compose application, use the existing service name
  69. serviceLabel, ok := c.Labels[api.ServiceLabel]
  70. if !ok {
  71. serviceLabel = getCanonicalContainerName(c)
  72. }
  73. service, ok := services[serviceLabel]
  74. if !ok {
  75. service = types.ServiceConfig{
  76. Name: serviceLabel,
  77. Image: c.Image,
  78. Labels: c.Labels,
  79. }
  80. }
  81. service.Scale = increment(service.Scale)
  82. inspect, err := s.apiClient().ContainerInspect(context.Background(), c.ID)
  83. if err != nil {
  84. services[serviceLabel] = service
  85. continue
  86. }
  87. s.extractComposeConfiguration(&service, inspect, volumes, secrets, networks)
  88. service.Labels = cleanDockerPreviousLabels(service.Labels)
  89. services[serviceLabel] = service
  90. }
  91. project.Services = services
  92. project.Networks = networks
  93. project.Volumes = volumes
  94. project.Secrets = secrets
  95. return project, nil
  96. }
  97. func (s *composeService) extractComposeConfiguration(service *types.ServiceConfig, inspect container.InspectResponse, volumes types.Volumes, secrets types.Secrets, networks types.Networks) {
  98. service.Environment = types.NewMappingWithEquals(inspect.Config.Env)
  99. if inspect.Config.Healthcheck != nil {
  100. healthConfig := inspect.Config.Healthcheck
  101. service.HealthCheck = s.toComposeHealthCheck(healthConfig)
  102. }
  103. if len(inspect.Mounts) > 0 {
  104. detectedVolumes, volumeConfigs, detectedSecrets, secretsConfigs := s.toComposeVolumes(inspect.Mounts)
  105. service.Volumes = append(service.Volumes, volumeConfigs...)
  106. service.Secrets = append(service.Secrets, secretsConfigs...)
  107. maps.Copy(volumes, detectedVolumes)
  108. maps.Copy(secrets, detectedSecrets)
  109. }
  110. if len(inspect.NetworkSettings.Networks) > 0 {
  111. detectedNetworks, networkConfigs := s.toComposeNetwork(inspect.NetworkSettings.Networks)
  112. service.Networks = networkConfigs
  113. maps.Copy(networks, detectedNetworks)
  114. }
  115. if len(inspect.HostConfig.PortBindings) > 0 {
  116. for key, portBindings := range inspect.HostConfig.PortBindings {
  117. for _, portBinding := range portBindings {
  118. service.Ports = append(service.Ports, types.ServicePortConfig{
  119. Target: uint32(key.Int()),
  120. Published: portBinding.HostPort,
  121. Protocol: key.Proto(),
  122. HostIP: portBinding.HostIP,
  123. })
  124. }
  125. }
  126. }
  127. }
  128. func (s *composeService) toComposeHealthCheck(healthConfig *container.HealthConfig) *types.HealthCheckConfig {
  129. var healthCheck types.HealthCheckConfig
  130. healthCheck.Test = healthConfig.Test
  131. if healthConfig.Timeout != 0 {
  132. timeout := types.Duration(healthConfig.Timeout)
  133. healthCheck.Timeout = &timeout
  134. }
  135. if healthConfig.Interval != 0 {
  136. interval := types.Duration(healthConfig.Interval)
  137. healthCheck.Interval = &interval
  138. }
  139. if healthConfig.StartPeriod != 0 {
  140. startPeriod := types.Duration(healthConfig.StartPeriod)
  141. healthCheck.StartPeriod = &startPeriod
  142. }
  143. if healthConfig.StartInterval != 0 {
  144. startInterval := types.Duration(healthConfig.StartInterval)
  145. healthCheck.StartInterval = &startInterval
  146. }
  147. if healthConfig.Retries != 0 {
  148. retries := uint64(healthConfig.Retries)
  149. healthCheck.Retries = &retries
  150. }
  151. return &healthCheck
  152. }
  153. func (s *composeService) toComposeVolumes(volumes []container.MountPoint) (map[string]types.VolumeConfig,
  154. []types.ServiceVolumeConfig, map[string]types.SecretConfig, []types.ServiceSecretConfig,
  155. ) {
  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. }