convert.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package convert
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "strings"
  8. "github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
  9. "github.com/Azure/go-autorest/autorest/to"
  10. "github.com/compose-spec/compose-go/types"
  11. "github.com/docker/api/compose"
  12. "github.com/docker/api/context/store"
  13. "github.com/sirupsen/logrus"
  14. )
  15. const (
  16. azureFileDriverName = "azure_file"
  17. volumeDriveroptsShareNameKey = "share_name"
  18. volumeDriveroptsAccountNameKey = "storage_account_name"
  19. volumeDriveroptsAccountKeyKey = "storage_account_key"
  20. singleContainerName = "single--container--aci"
  21. secretInlineMark = "inline:"
  22. )
  23. // ToContainerGroup converts a compose project into a ACI container group
  24. func ToContainerGroup(aciContext store.AciContext, p compose.Project) (containerinstance.ContainerGroup, error) {
  25. project := projectAciHelper(p)
  26. containerGroupName := strings.ToLower(project.Name)
  27. volumesCache, volumesSlice, err := project.getAciFileVolumes()
  28. if err != nil {
  29. return containerinstance.ContainerGroup{}, err
  30. }
  31. secretVolumes, err := project.getAciSecretVolumes()
  32. if err != nil {
  33. return containerinstance.ContainerGroup{}, err
  34. }
  35. allVolumes := append(volumesSlice, secretVolumes...)
  36. var volumes *[]containerinstance.Volume
  37. if len(allVolumes) == 0 {
  38. volumes = nil
  39. } else {
  40. volumes = &allVolumes
  41. }
  42. var containers []containerinstance.Container
  43. groupDefinition := containerinstance.ContainerGroup{
  44. Name: &containerGroupName,
  45. Location: &aciContext.Location,
  46. ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
  47. OsType: containerinstance.Linux,
  48. Containers: &containers,
  49. Volumes: volumes,
  50. },
  51. }
  52. for _, s := range project.Services {
  53. service := serviceConfigAciHelper(s)
  54. if s.Name != singleContainerName {
  55. logrus.Debugf("Adding %q\n", service.Name)
  56. }
  57. containerDefinition, err := service.getAciContainer(volumesCache)
  58. if err != nil {
  59. return containerinstance.ContainerGroup{}, err
  60. }
  61. if service.Ports != nil {
  62. var containerPorts []containerinstance.ContainerPort
  63. var groupPorts []containerinstance.Port
  64. for _, portConfig := range service.Ports {
  65. if portConfig.Published != 0 && portConfig.Published != portConfig.Target {
  66. msg := fmt.Sprintf("Port mapping is not supported with ACI, cannot map port %d to %d for container %s",
  67. portConfig.Published, portConfig.Target, service.Name)
  68. return groupDefinition, errors.New(msg)
  69. }
  70. portNumber := int32(portConfig.Target)
  71. containerPorts = append(containerPorts, containerinstance.ContainerPort{
  72. Port: to.Int32Ptr(portNumber),
  73. })
  74. groupPorts = append(groupPorts, containerinstance.Port{
  75. Port: to.Int32Ptr(portNumber),
  76. Protocol: containerinstance.TCP,
  77. })
  78. }
  79. containerDefinition.ContainerProperties.Ports = &containerPorts
  80. groupDefinition.ContainerGroupProperties.IPAddress = &containerinstance.IPAddress{
  81. Type: containerinstance.Public,
  82. Ports: &groupPorts,
  83. }
  84. }
  85. containers = append(containers, containerDefinition)
  86. }
  87. groupDefinition.ContainerGroupProperties.Containers = &containers
  88. return groupDefinition, nil
  89. }
  90. type projectAciHelper compose.Project
  91. func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) {
  92. var secretVolumes []containerinstance.Volume
  93. for secretName, filepathToRead := range p.Secrets {
  94. var data []byte
  95. if strings.HasPrefix(filepathToRead.File, secretInlineMark) {
  96. data = []byte(filepathToRead.File[len(secretInlineMark):])
  97. } else {
  98. var err error
  99. data, err = ioutil.ReadFile(filepathToRead.File)
  100. if err != nil {
  101. return secretVolumes, err
  102. }
  103. }
  104. if len(data) == 0 {
  105. continue
  106. }
  107. dataStr := base64.StdEncoding.EncodeToString(data)
  108. secretVolumes = append(secretVolumes, containerinstance.Volume{
  109. Name: to.StringPtr(secretName),
  110. Secret: map[string]*string{
  111. secretName: &dataStr,
  112. },
  113. })
  114. }
  115. return secretVolumes, nil
  116. }
  117. func (p projectAciHelper) getAciFileVolumes() (map[string]bool, []containerinstance.Volume, error) {
  118. azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
  119. var azureFileVolumesSlice []containerinstance.Volume
  120. for name, v := range p.Volumes {
  121. if v.Driver == azureFileDriverName {
  122. shareName, ok := v.DriverOpts[volumeDriveroptsShareNameKey]
  123. if !ok {
  124. return nil, nil, fmt.Errorf("cannot retrieve share name for Azurefile")
  125. }
  126. accountName, ok := v.DriverOpts[volumeDriveroptsAccountNameKey]
  127. if !ok {
  128. return nil, nil, fmt.Errorf("cannot retrieve account name for Azurefile")
  129. }
  130. accountKey, ok := v.DriverOpts[volumeDriveroptsAccountKeyKey]
  131. if !ok {
  132. return nil, nil, fmt.Errorf("cannot retrieve account key for Azurefile")
  133. }
  134. aciVolume := containerinstance.Volume{
  135. Name: to.StringPtr(name),
  136. AzureFile: &containerinstance.AzureFileVolume{
  137. ShareName: to.StringPtr(shareName),
  138. StorageAccountName: to.StringPtr(accountName),
  139. StorageAccountKey: to.StringPtr(accountKey),
  140. },
  141. }
  142. azureFileVolumesMap[name] = true
  143. azureFileVolumesSlice = append(azureFileVolumesSlice, aciVolume)
  144. }
  145. }
  146. return azureFileVolumesMap, azureFileVolumesSlice, nil
  147. }
  148. type serviceConfigAciHelper types.ServiceConfig
  149. func (s serviceConfigAciHelper) getAciFileVolumeMounts(volumesCache map[string]bool) ([]containerinstance.VolumeMount, error) {
  150. var aciServiceVolumes []containerinstance.VolumeMount
  151. for _, sv := range s.Volumes {
  152. if !volumesCache[sv.Source] {
  153. return []containerinstance.VolumeMount{}, fmt.Errorf("could not find volume source %q", sv.Source)
  154. }
  155. aciServiceVolumes = append(aciServiceVolumes, containerinstance.VolumeMount{
  156. Name: to.StringPtr(sv.Source),
  157. MountPath: to.StringPtr(sv.Target),
  158. })
  159. }
  160. return aciServiceVolumes, nil
  161. }
  162. func (s serviceConfigAciHelper) getAciSecretsVolumeMounts() []containerinstance.VolumeMount {
  163. var secretVolumeMounts []containerinstance.VolumeMount
  164. for _, secret := range s.Secrets {
  165. secretsMountPath := "/run/secrets"
  166. if secret.Target == "" {
  167. secret.Target = secret.Source
  168. }
  169. // Specifically use "/" here and not filepath.Join() to avoid windows path being sent and used inside containers
  170. secretsMountPath = secretsMountPath + "/" + secret.Target
  171. vmName := strings.Split(secret.Source, "=")[0]
  172. vm := containerinstance.VolumeMount{
  173. Name: to.StringPtr(vmName),
  174. MountPath: to.StringPtr(secretsMountPath),
  175. ReadOnly: to.BoolPtr(true), // TODO Confirm if the secrets are read only
  176. }
  177. secretVolumeMounts = append(secretVolumeMounts, vm)
  178. }
  179. return secretVolumeMounts
  180. }
  181. func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (containerinstance.Container, error) {
  182. secretVolumeMounts := s.getAciSecretsVolumeMounts()
  183. aciServiceVolumes, err := s.getAciFileVolumeMounts(volumesCache)
  184. if err != nil {
  185. return containerinstance.Container{}, err
  186. }
  187. allVolumes := append(aciServiceVolumes, secretVolumeMounts...)
  188. var volumes *[]containerinstance.VolumeMount
  189. if len(allVolumes) == 0 {
  190. volumes = nil
  191. } else {
  192. volumes = &allVolumes
  193. }
  194. return containerinstance.Container{
  195. Name: to.StringPtr(s.Name),
  196. ContainerProperties: &containerinstance.ContainerProperties{
  197. Image: to.StringPtr(s.Image),
  198. Resources: &containerinstance.ResourceRequirements{
  199. Limits: &containerinstance.ResourceLimits{
  200. MemoryInGB: to.Float64Ptr(1),
  201. CPU: to.Float64Ptr(1),
  202. },
  203. Requests: &containerinstance.ResourceRequests{
  204. MemoryInGB: to.Float64Ptr(1),
  205. CPU: to.Float64Ptr(1),
  206. },
  207. },
  208. VolumeMounts: volumes,
  209. },
  210. }, nil
  211. }