convert.go 8.5 KB

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