convert.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. /*
  2. Copyright 2020 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 convert
  14. import (
  15. "context"
  16. "encoding/base64"
  17. "fmt"
  18. "io/ioutil"
  19. "math"
  20. "os"
  21. "path"
  22. "strconv"
  23. "strings"
  24. "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
  25. "github.com/Azure/go-autorest/autorest/to"
  26. "github.com/compose-spec/compose-go/types"
  27. "github.com/pkg/errors"
  28. "github.com/docker/compose-cli/aci/login"
  29. "github.com/docker/compose-cli/api/compose"
  30. "github.com/docker/compose-cli/api/containers"
  31. "github.com/docker/compose-cli/context/store"
  32. "github.com/docker/compose-cli/utils/formatter"
  33. )
  34. const (
  35. // StatusRunning name of the ACI running status
  36. StatusRunning = "Running"
  37. // ComposeDNSSidecarName name of the dns sidecar container
  38. ComposeDNSSidecarName = "aci--dns--sidecar"
  39. dnsSidecarImage = "busybox:1.31.1"
  40. azureFileDriverName = "azure_file"
  41. volumeDriveroptsShareNameKey = "share_name"
  42. volumeDriveroptsAccountNameKey = "storage_account_name"
  43. volumeReadOnly = "read_only"
  44. defaultSecretsPath = "/run/secrets"
  45. serviceSecretAbsPathPrefix = "aci-service-secret-path-"
  46. )
  47. // ToContainerGroup converts a compose project into a ACI container group
  48. func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.Project, storageHelper login.StorageLogin) (containerinstance.ContainerGroup, error) {
  49. project := projectAciHelper(p)
  50. containerGroupName := strings.ToLower(project.Name)
  51. volumesCache, volumesSlice, err := project.getAciFileVolumes(ctx, storageHelper)
  52. if err != nil {
  53. return containerinstance.ContainerGroup{}, err
  54. }
  55. secretVolumes, err := project.getAciSecretVolumes()
  56. if err != nil {
  57. return containerinstance.ContainerGroup{}, err
  58. }
  59. allVolumes := append(volumesSlice, secretVolumes...)
  60. var volumes *[]containerinstance.Volume
  61. if len(allVolumes) > 0 {
  62. volumes = &allVolumes
  63. }
  64. registryCreds, err := getRegistryCredentials(p, newCliRegistryConfLoader())
  65. if err != nil {
  66. return containerinstance.ContainerGroup{}, err
  67. }
  68. var containers []containerinstance.Container
  69. restartPolicy, err := project.getRestartPolicy()
  70. if err != nil {
  71. return containerinstance.ContainerGroup{}, err
  72. }
  73. groupDefinition := containerinstance.ContainerGroup{
  74. Name: &containerGroupName,
  75. Location: &aciContext.Location,
  76. ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
  77. OsType: containerinstance.Linux,
  78. Containers: &containers,
  79. Volumes: volumes,
  80. ImageRegistryCredentials: &registryCreds,
  81. RestartPolicy: restartPolicy,
  82. },
  83. }
  84. var groupPorts []containerinstance.Port
  85. var dnsLabelName *string
  86. for _, s := range project.Services {
  87. service := serviceConfigAciHelper(s)
  88. containerDefinition, err := service.getAciContainer(volumesCache)
  89. if err != nil {
  90. return containerinstance.ContainerGroup{}, err
  91. }
  92. if service.Labels != nil && len(service.Labels) > 0 {
  93. return containerinstance.ContainerGroup{}, errors.New("ACI integration does not support labels in compose applications")
  94. }
  95. containerPorts, serviceGroupPorts, serviceDomainName, err := convertPortsToAci(service)
  96. if err != nil {
  97. return groupDefinition, err
  98. }
  99. containerDefinition.ContainerProperties.Ports = &containerPorts
  100. groupPorts = append(groupPorts, serviceGroupPorts...)
  101. if serviceDomainName != nil {
  102. if dnsLabelName != nil && *serviceDomainName != *dnsLabelName {
  103. return containerinstance.ContainerGroup{}, fmt.Errorf("ACI integration does not support specifying different domain names on services in the same compose application")
  104. }
  105. dnsLabelName = serviceDomainName
  106. }
  107. containers = append(containers, containerDefinition)
  108. }
  109. if len(groupPorts) > 0 {
  110. groupDefinition.ContainerGroupProperties.IPAddress = &containerinstance.IPAddress{
  111. Type: containerinstance.Public,
  112. Ports: &groupPorts,
  113. DNSNameLabel: dnsLabelName,
  114. }
  115. }
  116. if len(containers) > 1 {
  117. dnsSideCar := getDNSSidecar(containers)
  118. containers = append(containers, dnsSideCar)
  119. }
  120. groupDefinition.ContainerGroupProperties.Containers = &containers
  121. return groupDefinition, nil
  122. }
  123. func convertPortsToAci(service serviceConfigAciHelper) ([]containerinstance.ContainerPort, []containerinstance.Port, *string, error) {
  124. var groupPorts []containerinstance.Port
  125. var containerPorts []containerinstance.ContainerPort
  126. for _, portConfig := range service.Ports {
  127. if portConfig.Published != 0 && portConfig.Published != portConfig.Target {
  128. msg := fmt.Sprintf("Port mapping is not supported with ACI, cannot map port %d to %d for container %s",
  129. portConfig.Published, portConfig.Target, service.Name)
  130. return nil, nil, nil, errors.New(msg)
  131. }
  132. portNumber := int32(portConfig.Target)
  133. containerPorts = append(containerPorts, containerinstance.ContainerPort{
  134. Port: to.Int32Ptr(portNumber),
  135. })
  136. groupPorts = append(groupPorts, containerinstance.Port{
  137. Port: to.Int32Ptr(portNumber),
  138. Protocol: containerinstance.TCP,
  139. })
  140. }
  141. var dnsLabelName *string = nil
  142. if service.DomainName != "" {
  143. dnsLabelName = &service.DomainName
  144. }
  145. return containerPorts, groupPorts, dnsLabelName, nil
  146. }
  147. func getDNSSidecar(containers []containerinstance.Container) containerinstance.Container {
  148. var commands []string
  149. for _, container := range containers {
  150. commands = append(commands, fmt.Sprintf("echo 127.0.0.1 %s >> /etc/hosts", *container.Name))
  151. }
  152. // ACI restart policy is currently at container group level, cannot let the sidecar terminate quietly once /etc/hosts has been edited
  153. // Pricing is done at the container group level so letting the sidecar container "sleep" should not impact the price for the whole group
  154. commands = append(commands, "sleep infinity")
  155. alpineCmd := []string{"sh", "-c", strings.Join(commands, ";")}
  156. dnsSideCar := containerinstance.Container{
  157. Name: to.StringPtr(ComposeDNSSidecarName),
  158. ContainerProperties: &containerinstance.ContainerProperties{
  159. Image: to.StringPtr(dnsSidecarImage),
  160. Command: &alpineCmd,
  161. Resources: &containerinstance.ResourceRequirements{
  162. Requests: &containerinstance.ResourceRequests{
  163. MemoryInGB: to.Float64Ptr(0.1),
  164. CPU: to.Float64Ptr(0.01),
  165. },
  166. },
  167. },
  168. }
  169. return dnsSideCar
  170. }
  171. type projectAciHelper types.Project
  172. func getServiceSecretKey(serviceName, targetDir string) string {
  173. return fmt.Sprintf("%s-%s--%s",
  174. serviceSecretAbsPathPrefix, serviceName, strings.ReplaceAll(targetDir, "/", "-"))
  175. }
  176. func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) {
  177. var secretVolumes []containerinstance.Volume
  178. for _, svc := range p.Services {
  179. squashedTargetVolumes := make(map[string]containerinstance.Volume)
  180. for _, scr := range svc.Secrets {
  181. data, err := ioutil.ReadFile(p.Secrets[scr.Source].File)
  182. if err != nil {
  183. return secretVolumes, err
  184. }
  185. if len(data) == 0 {
  186. continue
  187. }
  188. dataStr := base64.StdEncoding.EncodeToString(data)
  189. if scr.Target == "" {
  190. scr.Target = scr.Source
  191. }
  192. if !path.IsAbs(scr.Target) && strings.ContainsAny(scr.Target, "\\/") {
  193. return []containerinstance.Volume{},
  194. errors.Errorf("in service %q, secret with source %q cannot have a relative path as target. "+
  195. "Only absolute paths are allowed. Found %q",
  196. svc.Name, scr.Source, scr.Target)
  197. }
  198. if !path.IsAbs(scr.Target) {
  199. scr.Target = path.Join(defaultSecretsPath, scr.Target)
  200. }
  201. targetDir := path.Dir(scr.Target)
  202. targetDirKey := getServiceSecretKey(svc.Name, targetDir)
  203. if _, ok := squashedTargetVolumes[targetDir]; !ok {
  204. squashedTargetVolumes[targetDir] = containerinstance.Volume{
  205. Name: to.StringPtr(targetDirKey),
  206. Secret: make(map[string]*string),
  207. }
  208. }
  209. squashedTargetVolumes[targetDir].Secret[path.Base(scr.Target)] = &dataStr
  210. }
  211. for _, v := range squashedTargetVolumes {
  212. secretVolumes = append(secretVolumes, v)
  213. }
  214. }
  215. return secretVolumes, nil
  216. }
  217. func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageLogin) (map[string]bool, []containerinstance.Volume, error) {
  218. azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
  219. var azureFileVolumesSlice []containerinstance.Volume
  220. for name, v := range p.Volumes {
  221. if v.Driver == azureFileDriverName {
  222. shareName, ok := v.DriverOpts[volumeDriveroptsShareNameKey]
  223. if !ok {
  224. return nil, nil, fmt.Errorf("cannot retrieve fileshare name for Azurefile")
  225. }
  226. accountName, ok := v.DriverOpts[volumeDriveroptsAccountNameKey]
  227. if !ok {
  228. return nil, nil, fmt.Errorf("cannot retrieve account name for Azurefile")
  229. }
  230. readOnly, ok := v.DriverOpts[volumeReadOnly]
  231. if !ok {
  232. readOnly = "false"
  233. }
  234. ro, err := strconv.ParseBool(readOnly)
  235. if err != nil {
  236. return nil, nil, fmt.Errorf("invalid mode %q for volume", readOnly)
  237. }
  238. accountKey, err := helper.GetAzureStorageAccountKey(ctx, accountName)
  239. if err != nil {
  240. return nil, nil, err
  241. }
  242. aciVolume := containerinstance.Volume{
  243. Name: to.StringPtr(name),
  244. AzureFile: &containerinstance.AzureFileVolume{
  245. ShareName: to.StringPtr(shareName),
  246. StorageAccountName: to.StringPtr(accountName),
  247. StorageAccountKey: to.StringPtr(accountKey),
  248. ReadOnly: &ro,
  249. },
  250. }
  251. azureFileVolumesMap[name] = true
  252. azureFileVolumesSlice = append(azureFileVolumesSlice, aciVolume)
  253. }
  254. }
  255. return azureFileVolumesMap, azureFileVolumesSlice, nil
  256. }
  257. func (p projectAciHelper) getRestartPolicy() (containerinstance.ContainerGroupRestartPolicy, error) {
  258. var restartPolicyCondition containerinstance.ContainerGroupRestartPolicy
  259. if len(p.Services) >= 1 {
  260. alreadySpecified := false
  261. restartPolicyCondition = containerinstance.Always
  262. for _, service := range p.Services {
  263. if service.Deploy != nil &&
  264. service.Deploy.RestartPolicy != nil {
  265. if !alreadySpecified {
  266. alreadySpecified = true
  267. restartPolicyCondition = toAciRestartPolicy(service.Deploy.RestartPolicy.Condition)
  268. }
  269. if alreadySpecified && restartPolicyCondition != toAciRestartPolicy(service.Deploy.RestartPolicy.Condition) {
  270. return "", errors.New("ACI integration does not support specifying different restart policies on services in the same compose application")
  271. }
  272. }
  273. }
  274. }
  275. return restartPolicyCondition, nil
  276. }
  277. func toAciRestartPolicy(restartPolicy string) containerinstance.ContainerGroupRestartPolicy {
  278. switch restartPolicy {
  279. case containers.RestartPolicyNone:
  280. return containerinstance.Never
  281. case containers.RestartPolicyAny:
  282. return containerinstance.Always
  283. case containers.RestartPolicyOnFailure:
  284. return containerinstance.OnFailure
  285. default:
  286. return containerinstance.Always
  287. }
  288. }
  289. func toContainerRestartPolicy(aciRestartPolicy containerinstance.ContainerGroupRestartPolicy) string {
  290. switch aciRestartPolicy {
  291. case containerinstance.Never:
  292. return containers.RestartPolicyNone
  293. case containerinstance.Always:
  294. return containers.RestartPolicyAny
  295. case containerinstance.OnFailure:
  296. return containers.RestartPolicyOnFailure
  297. default:
  298. return containers.RestartPolicyAny
  299. }
  300. }
  301. type serviceConfigAciHelper types.ServiceConfig
  302. func (s serviceConfigAciHelper) getAciFileVolumeMounts(volumesCache map[string]bool) ([]containerinstance.VolumeMount, error) {
  303. var aciServiceVolumes []containerinstance.VolumeMount
  304. for _, sv := range s.Volumes {
  305. if !volumesCache[sv.Source] {
  306. return []containerinstance.VolumeMount{}, fmt.Errorf("could not find volume source %q", sv.Source)
  307. }
  308. aciServiceVolumes = append(aciServiceVolumes, containerinstance.VolumeMount{
  309. Name: to.StringPtr(sv.Source),
  310. MountPath: to.StringPtr(sv.Target),
  311. })
  312. }
  313. return aciServiceVolumes, nil
  314. }
  315. func (s serviceConfigAciHelper) getAciSecretsVolumeMounts() ([]containerinstance.VolumeMount, error) {
  316. vms := []containerinstance.VolumeMount{}
  317. presenceSet := make(map[string]bool)
  318. for _, scr := range s.Secrets {
  319. if scr.Target == "" {
  320. scr.Target = scr.Source
  321. }
  322. if !path.IsAbs(scr.Target) {
  323. scr.Target = path.Join(defaultSecretsPath, scr.Target)
  324. }
  325. presenceKey := path.Dir(scr.Target)
  326. if !presenceSet[presenceKey] {
  327. vms = append(vms, containerinstance.VolumeMount{
  328. Name: to.StringPtr(getServiceSecretKey(s.Name, path.Dir(scr.Target))),
  329. MountPath: to.StringPtr(path.Dir(scr.Target)),
  330. ReadOnly: to.BoolPtr(true),
  331. })
  332. presenceSet[presenceKey] = true
  333. }
  334. }
  335. err := validateMountPathCollisions(vms)
  336. if err != nil {
  337. return []containerinstance.VolumeMount{}, err
  338. }
  339. return vms, nil
  340. }
  341. func validateMountPathCollisions(vms []containerinstance.VolumeMount) error {
  342. for i, vm1 := range vms {
  343. for j, vm2 := range vms {
  344. if i == j {
  345. continue
  346. }
  347. var (
  348. biggerVMPath = strings.Split(*vm1.MountPath, "/")
  349. smallerVMPath = strings.Split(*vm2.MountPath, "/")
  350. )
  351. if len(smallerVMPath) > len(biggerVMPath) {
  352. tmp := biggerVMPath
  353. biggerVMPath = smallerVMPath
  354. smallerVMPath = tmp
  355. }
  356. isPrefixed := true
  357. for i := 0; i < len(smallerVMPath); i++ {
  358. if smallerVMPath[i] != biggerVMPath[i] {
  359. isPrefixed = false
  360. break
  361. }
  362. }
  363. if isPrefixed {
  364. return errors.Errorf("mount paths %q and %q collide. A volume mount cannot include another one.", *vm1.MountPath, *vm2.MountPath)
  365. }
  366. }
  367. }
  368. return nil
  369. }
  370. func (s serviceConfigAciHelper) getAciContainer(volumesCache map[string]bool) (containerinstance.Container, error) {
  371. aciServiceVolumes, err := s.getAciFileVolumeMounts(volumesCache)
  372. if err != nil {
  373. return containerinstance.Container{}, err
  374. }
  375. serviceSecretVolumes, err := s.getAciSecretsVolumeMounts()
  376. if err != nil {
  377. return containerinstance.Container{}, err
  378. }
  379. allVolumes := append(aciServiceVolumes, serviceSecretVolumes...)
  380. var volumes *[]containerinstance.VolumeMount
  381. if len(allVolumes) > 0 {
  382. volumes = &allVolumes
  383. }
  384. resource, err := s.getResourceRequestsLimits()
  385. if err != nil {
  386. return containerinstance.Container{}, err
  387. }
  388. return containerinstance.Container{
  389. Name: to.StringPtr(s.Name),
  390. ContainerProperties: &containerinstance.ContainerProperties{
  391. Image: to.StringPtr(s.Image),
  392. Command: to.StringSlicePtr(s.Command),
  393. EnvironmentVariables: getEnvVariables(s.Environment),
  394. Resources: resource,
  395. VolumeMounts: volumes,
  396. },
  397. }, nil
  398. }
  399. func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.ResourceRequirements, error) {
  400. memRequest := 1. // Default 1 Gb
  401. var cpuRequest float64 = 1
  402. var err error
  403. hasMemoryRequest := func() bool {
  404. return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.MemoryBytes != 0
  405. }
  406. hasCPURequest := func() bool {
  407. return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.NanoCPUs != ""
  408. }
  409. if hasMemoryRequest() {
  410. memRequest = BytesToGB(float64(s.Deploy.Resources.Reservations.MemoryBytes))
  411. }
  412. if hasCPURequest() {
  413. cpuRequest, err = strconv.ParseFloat(s.Deploy.Resources.Reservations.NanoCPUs, 0)
  414. if err != nil {
  415. return nil, err
  416. }
  417. }
  418. memLimit := memRequest
  419. cpuLimit := cpuRequest
  420. if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
  421. if s.Deploy.Resources.Limits.MemoryBytes != 0 {
  422. memLimit = BytesToGB(float64(s.Deploy.Resources.Limits.MemoryBytes))
  423. if !hasMemoryRequest() {
  424. memRequest = memLimit
  425. }
  426. }
  427. if s.Deploy.Resources.Limits.NanoCPUs != "" {
  428. cpuLimit, err = strconv.ParseFloat(s.Deploy.Resources.Limits.NanoCPUs, 0)
  429. if err != nil {
  430. return nil, err
  431. }
  432. if !hasCPURequest() {
  433. cpuRequest = cpuLimit
  434. }
  435. }
  436. }
  437. resources := containerinstance.ResourceRequirements{
  438. Requests: &containerinstance.ResourceRequests{
  439. MemoryInGB: to.Float64Ptr(memRequest),
  440. CPU: to.Float64Ptr(cpuRequest),
  441. },
  442. Limits: &containerinstance.ResourceLimits{
  443. MemoryInGB: to.Float64Ptr(memLimit),
  444. CPU: to.Float64Ptr(cpuLimit),
  445. },
  446. }
  447. return &resources, nil
  448. }
  449. func getEnvVariables(composeEnv types.MappingWithEquals) *[]containerinstance.EnvironmentVariable {
  450. result := []containerinstance.EnvironmentVariable{}
  451. for key, value := range composeEnv {
  452. var strValue string
  453. if value == nil {
  454. strValue = os.Getenv(key)
  455. } else {
  456. strValue = *value
  457. }
  458. result = append(result, containerinstance.EnvironmentVariable{
  459. Name: to.StringPtr(key),
  460. Value: to.StringPtr(strValue),
  461. })
  462. }
  463. return &result
  464. }
  465. // BytesToGB convert bytes To GB
  466. func BytesToGB(b float64) float64 {
  467. f := b / 1024 / 1024 / 1024 // from bytes to gigabytes
  468. return math.Round(f*100) / 100
  469. }
  470. func gbToBytes(memInBytes float64) uint64 {
  471. return uint64(memInBytes * 1024 * 1024 * 1024)
  472. }
  473. // ContainerGroupToServiceStatus convert from an ACI container definition to service status
  474. func ContainerGroupToServiceStatus(containerID string, group containerinstance.ContainerGroup, container containerinstance.Container, region string) compose.ServiceStatus {
  475. var replicas = 1
  476. if GetStatus(container, group) != StatusRunning {
  477. replicas = 0
  478. }
  479. return compose.ServiceStatus{
  480. ID: containerID,
  481. Name: *container.Name,
  482. Ports: formatter.PortsToStrings(ToPorts(group.IPAddress, *container.Ports), fqdn(group, region)),
  483. Replicas: replicas,
  484. Desired: 1,
  485. }
  486. }
  487. func fqdn(group containerinstance.ContainerGroup, region string) string {
  488. fqdn := ""
  489. if group.IPAddress != nil && group.IPAddress.DNSNameLabel != nil && *group.IPAddress.DNSNameLabel != "" {
  490. fqdn = *group.IPAddress.DNSNameLabel + "." + region + ".azurecontainer.io"
  491. }
  492. return fqdn
  493. }
  494. // ContainerGroupToContainer composes a Container from an ACI container definition
  495. func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container, region string) containers.Container {
  496. command := ""
  497. if cc.Command != nil {
  498. command = strings.Join(*cc.Command, " ")
  499. }
  500. status := GetStatus(cc, cg)
  501. platform := string(cg.OsType)
  502. var envVars map[string]string = nil
  503. if cc.EnvironmentVariables != nil && len(*cc.EnvironmentVariables) != 0 {
  504. envVars = map[string]string{}
  505. for _, envVar := range *cc.EnvironmentVariables {
  506. envVars[*envVar.Name] = *envVar.Value
  507. }
  508. }
  509. hostConfig := ToHostConfig(cc, cg)
  510. config := &containers.RuntimeConfig{
  511. FQDN: fqdn(cg, region),
  512. Env: envVars,
  513. }
  514. c := containers.Container{
  515. ID: containerID,
  516. Status: status,
  517. Image: to.String(cc.Image),
  518. Command: command,
  519. CPUTime: 0,
  520. MemoryUsage: 0,
  521. PidsCurrent: 0,
  522. PidsLimit: 0,
  523. Ports: ToPorts(cg.IPAddress, *cc.Ports),
  524. Platform: platform,
  525. Config: config,
  526. HostConfig: hostConfig,
  527. }
  528. return c
  529. }
  530. // ToHostConfig convert an ACI container to host config value
  531. func ToHostConfig(cc containerinstance.Container, cg containerinstance.ContainerGroup) *containers.HostConfig {
  532. memLimits := uint64(0)
  533. memRequest := uint64(0)
  534. cpuLimit := 0.
  535. cpuReservation := 0.
  536. if cc.Resources != nil {
  537. if cc.Resources.Limits != nil {
  538. if cc.Resources.Limits.MemoryInGB != nil {
  539. memLimits = gbToBytes(*cc.Resources.Limits.MemoryInGB)
  540. }
  541. if cc.Resources.Limits.CPU != nil {
  542. cpuLimit = *cc.Resources.Limits.CPU
  543. }
  544. }
  545. if cc.Resources.Requests != nil {
  546. if cc.Resources.Requests.MemoryInGB != nil {
  547. memRequest = gbToBytes(*cc.Resources.Requests.MemoryInGB)
  548. }
  549. if cc.Resources.Requests.CPU != nil {
  550. cpuReservation = *cc.Resources.Requests.CPU
  551. }
  552. }
  553. }
  554. hostConfig := &containers.HostConfig{
  555. CPULimit: cpuLimit,
  556. CPUReservation: cpuReservation,
  557. MemoryLimit: memLimits,
  558. MemoryReservation: memRequest,
  559. RestartPolicy: toContainerRestartPolicy(cg.RestartPolicy),
  560. }
  561. return hostConfig
  562. }
  563. // GetStatus returns status for the specified container
  564. func GetStatus(container containerinstance.Container, group containerinstance.ContainerGroup) string {
  565. status := GetGroupStatus(group)
  566. if container.InstanceView != nil && container.InstanceView.CurrentState != nil {
  567. status = *container.InstanceView.CurrentState.State
  568. }
  569. return status
  570. }
  571. // GetGroupStatus returns status for the container group
  572. func GetGroupStatus(group containerinstance.ContainerGroup) string {
  573. if group.InstanceView != nil && group.InstanceView.State != nil {
  574. return "Node " + *group.InstanceView.State
  575. }
  576. return compose.UNKNOWN
  577. }