2020-06-18 16:13:24 +02:00
/ *
2020-09-22 12:13:00 +02:00
Copyright 2020 Docker Compose CLI authors
2020-06-18 16:13:24 +02:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2020-05-01 15:48:20 +02:00
package convert
import (
2020-08-14 17:01:35 +02:00
"context"
2020-05-01 15:48:20 +02:00
"fmt"
2020-06-30 07:44:45 +02:00
"math"
2020-07-06 23:25:01 +02:00
"os"
2020-06-24 09:44:47 +02:00
"strconv"
2020-05-01 15:48:20 +02:00
"strings"
2020-10-29 02:11:08 +01:00
"time"
2020-05-01 15:48:20 +02:00
2020-11-23 22:15:15 +01:00
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance"
2020-05-01 15:48:20 +02:00
"github.com/Azure/go-autorest/autorest/to"
"github.com/compose-spec/compose-go/types"
2020-08-14 16:24:43 +02:00
"github.com/pkg/errors"
2020-05-13 07:52:43 +02:00
2020-08-21 17:24:53 +02:00
"github.com/docker/compose-cli/aci/login"
2020-09-07 13:22:08 +02:00
"github.com/docker/compose-cli/api/containers"
2021-01-15 16:31:59 +01:00
"github.com/docker/compose-cli/api/context/store"
2021-06-14 16:26:14 +02:00
"github.com/docker/compose-cli/pkg/api"
2020-09-22 11:45:02 +02:00
"github.com/docker/compose-cli/utils/formatter"
2020-05-01 15:48:20 +02:00
)
const (
2020-09-04 15:09:57 +02:00
// StatusRunning name of the ACI running status
StatusRunning = "Running"
2020-06-18 15:51:13 +02:00
// ComposeDNSSidecarName name of the dns sidecar container
ComposeDNSSidecarName = "aci--dns--sidecar"
2020-06-18 15:50:49 +02:00
2020-10-28 17:19:12 +01:00
dnsSidecarImage = "docker/aci-hostnames-sidecar:1.0"
2020-05-01 15:48:20 +02:00
)
2020-05-04 23:00:21 +02:00
// ToContainerGroup converts a compose project into a ACI container group
2020-09-22 11:45:02 +02:00
func ToContainerGroup ( ctx context . Context , aciContext store . AciContext , p types . Project , storageHelper login . StorageLogin ) ( containerinstance . ContainerGroup , error ) {
2020-05-01 15:48:20 +02:00
project := projectAciHelper ( p )
containerGroupName := strings . ToLower ( project . Name )
2020-11-10 16:58:56 +01:00
volumesSlice , err := project . getAciFileVolumes ( ctx , storageHelper )
2020-05-01 15:48:20 +02:00
if err != nil {
return containerinstance . ContainerGroup { } , err
}
secretVolumes , err := project . getAciSecretVolumes ( )
if err != nil {
return containerinstance . ContainerGroup { } , err
}
allVolumes := append ( volumesSlice , secretVolumes ... )
var volumes * [ ] containerinstance . Volume
2020-09-14 18:32:38 +02:00
if len ( allVolumes ) > 0 {
2020-05-01 15:48:20 +02:00
volumes = & allVolumes
}
2020-06-02 15:29:16 +02:00
registryCreds , err := getRegistryCredentials ( p , newCliRegistryConfLoader ( ) )
if err != nil {
return containerinstance . ContainerGroup { } , err
}
2020-10-29 02:11:08 +01:00
var ctnrs [ ] containerinstance . Container
2020-08-06 18:23:04 +02:00
restartPolicy , err := project . getRestartPolicy ( )
if err != nil {
return containerinstance . ContainerGroup { } , err
}
2020-05-01 15:48:20 +02:00
groupDefinition := containerinstance . ContainerGroup {
Name : & containerGroupName ,
Location : & aciContext . Location ,
ContainerGroupProperties : & containerinstance . ContainerGroupProperties {
2020-06-02 15:29:16 +02:00
OsType : containerinstance . Linux ,
2020-10-29 02:11:08 +01:00
Containers : & ctnrs ,
2020-06-02 15:29:16 +02:00
Volumes : volumes ,
ImageRegistryCredentials : & registryCreds ,
2020-08-06 18:23:04 +02:00
RestartPolicy : restartPolicy ,
2020-05-01 15:48:20 +02:00
} ,
}
2020-06-23 15:54:47 +02:00
var groupPorts [ ] containerinstance . Port
2020-09-22 14:15:05 +02:00
var dnsLabelName * string
2020-05-01 15:48:20 +02:00
for _ , s := range project . Services {
service := serviceConfigAciHelper ( s )
2020-11-10 16:58:56 +01:00
containerDefinition , err := service . getAciContainer ( )
2020-05-01 15:48:20 +02:00
if err != nil {
return containerinstance . ContainerGroup { } , err
}
2020-08-12 10:28:55 +02:00
if service . Labels != nil && len ( service . Labels ) > 0 {
return containerinstance . ContainerGroup { } , errors . New ( "ACI integration does not support labels in compose applications" )
}
2020-09-22 14:15:05 +02:00
containerPorts , serviceGroupPorts , serviceDomainName , err := convertPortsToAci ( service )
if err != nil {
return groupDefinition , err
}
containerDefinition . ContainerProperties . Ports = & containerPorts
groupPorts = append ( groupPorts , serviceGroupPorts ... )
if serviceDomainName != nil {
if dnsLabelName != nil && * serviceDomainName != * dnsLabelName {
return containerinstance . ContainerGroup { } , fmt . Errorf ( "ACI integration does not support specifying different domain names on services in the same compose application" )
2020-05-01 15:48:20 +02:00
}
2020-09-22 14:15:05 +02:00
dnsLabelName = serviceDomainName
2020-05-01 15:48:20 +02:00
}
2020-10-29 02:11:08 +01:00
ctnrs = append ( ctnrs , containerDefinition )
2020-05-01 15:48:20 +02:00
}
2020-09-22 14:15:05 +02:00
if len ( groupPorts ) > 0 {
groupDefinition . ContainerGroupProperties . IPAddress = & containerinstance . IPAddress {
Type : containerinstance . Public ,
Ports : & groupPorts ,
DNSNameLabel : dnsLabelName ,
}
}
2021-01-12 15:43:38 +01:00
if len ( project . Services ) > 1 {
dnsSideCar := getDNSSidecar ( project . Services )
2020-10-29 02:11:08 +01:00
ctnrs = append ( ctnrs , dnsSideCar )
2020-06-18 15:50:49 +02:00
}
2020-10-29 02:11:08 +01:00
groupDefinition . ContainerGroupProperties . Containers = & ctnrs
2020-06-18 15:50:49 +02:00
2020-05-01 15:48:20 +02:00
return groupDefinition , nil
}
2020-10-29 02:11:08 +01:00
func durationToSeconds ( d * types . Duration ) * int32 {
2020-11-06 11:37:47 +01:00
if d == nil || * d == 0 {
2020-10-29 02:11:08 +01:00
return nil
}
v := int32 ( time . Duration ( * d ) . Seconds ( ) )
return & v
}
2021-01-12 15:43:38 +01:00
func getDNSSidecar ( services types . Services ) containerinstance . Container {
2020-10-26 15:03:37 +01:00
names := [ ] string { "/hosts" }
2021-01-12 15:43:38 +01:00
for _ , service := range services {
names = append ( names , service . Name )
if service . ContainerName != "" {
names = append ( names , service . ContainerName )
}
2020-06-18 15:50:49 +02:00
}
dnsSideCar := containerinstance . Container {
2020-06-18 15:51:13 +02:00
Name : to . StringPtr ( ComposeDNSSidecarName ) ,
2020-06-18 15:50:49 +02:00
ContainerProperties : & containerinstance . ContainerProperties {
2020-06-18 16:44:56 +02:00
Image : to . StringPtr ( dnsSidecarImage ) ,
2020-10-16 14:29:36 +02:00
Command : & names ,
2020-06-18 15:50:49 +02:00
Resources : & containerinstance . ResourceRequirements {
Requests : & containerinstance . ResourceRequests {
2020-06-18 17:55:56 +02:00
MemoryInGB : to . Float64Ptr ( 0.1 ) ,
CPU : to . Float64Ptr ( 0.01 ) ,
2020-06-18 15:50:49 +02:00
} ,
} ,
} ,
}
return dnsSideCar
}
2020-07-02 16:05:45 +02:00
type projectAciHelper types . Project
2020-05-01 15:48:20 +02:00
type serviceConfigAciHelper types . ServiceConfig
2020-11-10 16:58:56 +01:00
func ( s serviceConfigAciHelper ) getAciContainer ( ) ( containerinstance . Container , error ) {
aciServiceVolumes , err := s . getAciFileVolumeMounts ( )
2020-05-01 15:48:20 +02:00
if err != nil {
return containerinstance . Container { } , err
}
2020-10-15 08:55:20 +02:00
serviceSecretVolumes , err := s . getAciSecretsVolumeMounts ( )
if err != nil {
return containerinstance . Container { } , err
2020-10-12 08:40:46 +02:00
}
2020-10-15 08:55:20 +02:00
allVolumes := append ( aciServiceVolumes , serviceSecretVolumes ... )
2020-05-01 15:48:20 +02:00
var volumes * [ ] containerinstance . VolumeMount
2020-10-12 08:40:46 +02:00
if len ( allVolumes ) > 0 {
2020-05-01 15:48:20 +02:00
volumes = & allVolumes
}
2020-06-24 09:44:47 +02:00
2020-10-05 17:23:08 +02:00
resource , err := s . getResourceRequestsLimits ( )
if err != nil {
return containerinstance . Container { } , err
2020-06-24 09:44:47 +02:00
}
2020-10-05 17:23:08 +02:00
2021-01-12 15:43:38 +01:00
containerName := s . Name
if s . ContainerName != "" {
containerName = s . ContainerName
}
2020-05-01 15:48:20 +02:00
return containerinstance . Container {
2021-01-12 15:43:38 +01:00
Name : to . StringPtr ( containerName ) ,
2020-05-01 15:48:20 +02:00
ContainerProperties : & containerinstance . ContainerProperties {
2020-07-06 23:25:01 +02:00
Image : to . StringPtr ( s . Image ) ,
2020-08-24 18:41:16 +02:00
Command : to . StringSlicePtr ( s . Command ) ,
2020-07-06 23:25:01 +02:00
EnvironmentVariables : getEnvVariables ( s . Environment ) ,
2020-10-05 17:23:08 +02:00
Resources : resource ,
VolumeMounts : volumes ,
2020-10-29 02:11:08 +01:00
LivenessProbe : s . getLivenessProbe ( ) ,
2020-05-01 15:48:20 +02:00
} ,
} , nil
2020-07-06 23:25:01 +02:00
}
2020-05-01 15:48:20 +02:00
2020-10-05 17:23:08 +02:00
func ( s serviceConfigAciHelper ) getResourceRequestsLimits ( ) ( * containerinstance . ResourceRequirements , error ) {
memRequest := 1. // Default 1 Gb
var cpuRequest float64 = 1
var err error
hasMemoryRequest := func ( ) bool {
return s . Deploy != nil && s . Deploy . Resources . Reservations != nil && s . Deploy . Resources . Reservations . MemoryBytes != 0
}
hasCPURequest := func ( ) bool {
return s . Deploy != nil && s . Deploy . Resources . Reservations != nil && s . Deploy . Resources . Reservations . NanoCPUs != ""
}
if hasMemoryRequest ( ) {
2020-10-15 12:49:43 +02:00
memRequest = BytesToGB ( float64 ( s . Deploy . Resources . Reservations . MemoryBytes ) )
2020-10-05 17:23:08 +02:00
}
if hasCPURequest ( ) {
cpuRequest , err = strconv . ParseFloat ( s . Deploy . Resources . Reservations . NanoCPUs , 0 )
if err != nil {
return nil , err
}
}
memLimit := memRequest
cpuLimit := cpuRequest
if s . Deploy != nil && s . Deploy . Resources . Limits != nil {
if s . Deploy . Resources . Limits . MemoryBytes != 0 {
2020-10-15 12:49:43 +02:00
memLimit = BytesToGB ( float64 ( s . Deploy . Resources . Limits . MemoryBytes ) )
2020-10-05 17:23:08 +02:00
if ! hasMemoryRequest ( ) {
memRequest = memLimit
}
}
if s . Deploy . Resources . Limits . NanoCPUs != "" {
cpuLimit , err = strconv . ParseFloat ( s . Deploy . Resources . Limits . NanoCPUs , 0 )
if err != nil {
return nil , err
}
if ! hasCPURequest ( ) {
cpuRequest = cpuLimit
}
}
}
resources := containerinstance . ResourceRequirements {
Requests : & containerinstance . ResourceRequests {
MemoryInGB : to . Float64Ptr ( memRequest ) ,
CPU : to . Float64Ptr ( cpuRequest ) ,
} ,
Limits : & containerinstance . ResourceLimits {
MemoryInGB : to . Float64Ptr ( memLimit ) ,
CPU : to . Float64Ptr ( cpuLimit ) ,
} ,
}
return & resources , nil
}
2020-10-29 02:11:08 +01:00
func ( s serviceConfigAciHelper ) getLivenessProbe ( ) * containerinstance . ContainerProbe {
if s . HealthCheck != nil && ! s . HealthCheck . Disable && len ( s . HealthCheck . Test ) > 0 {
testArray := s . HealthCheck . Test
switch s . HealthCheck . Test [ 0 ] {
case "NONE" , "CMD" , "CMD-SHELL" :
testArray = s . HealthCheck . Test [ 1 : ]
}
if len ( testArray ) == 0 {
return nil
}
var retries * int32
if s . HealthCheck . Retries != nil {
retries = to . Int32Ptr ( int32 ( * s . HealthCheck . Retries ) )
}
2020-11-06 11:37:47 +01:00
probe := containerinstance . ContainerProbe {
2020-10-29 02:11:08 +01:00
Exec : & containerinstance . ContainerExec {
Command : to . StringSlicePtr ( testArray ) ,
} ,
InitialDelaySeconds : durationToSeconds ( s . HealthCheck . StartPeriod ) ,
PeriodSeconds : durationToSeconds ( s . HealthCheck . Interval ) ,
TimeoutSeconds : durationToSeconds ( s . HealthCheck . Timeout ) ,
}
2020-11-06 11:37:47 +01:00
if retries != nil && * retries > 0 {
probe . FailureThreshold = retries
}
return & probe
2020-10-29 02:11:08 +01:00
}
return nil
}
2020-07-06 23:25:01 +02:00
func getEnvVariables ( composeEnv types . MappingWithEquals ) * [ ] containerinstance . EnvironmentVariable {
result := [ ] containerinstance . EnvironmentVariable { }
for key , value := range composeEnv {
var strValue string
if value == nil {
strValue = os . Getenv ( key )
} else {
strValue = * value
}
result = append ( result , containerinstance . EnvironmentVariable {
Name : to . StringPtr ( key ) ,
Value : to . StringPtr ( strValue ) ,
} )
}
return & result
2020-05-01 15:48:20 +02:00
}
2020-06-15 10:38:37 +02:00
2020-10-15 12:49:43 +02:00
// BytesToGB convert bytes To GB
func BytesToGB ( b float64 ) float64 {
f := b / 1024 / 1024 / 1024 // from bytes to gigabytes
2020-06-30 07:44:45 +02:00
return math . Round ( f * 100 ) / 100
2020-06-24 09:44:47 +02:00
}
2020-10-09 11:48:06 +02:00
func gbToBytes ( memInBytes float64 ) uint64 {
return uint64 ( memInBytes * 1024 * 1024 * 1024 )
}
2020-08-28 14:38:51 +02:00
// ContainerGroupToServiceStatus convert from an ACI container definition to service status
2021-06-14 16:26:14 +02:00
func ContainerGroupToServiceStatus ( containerID string , group containerinstance . ContainerGroup , container containerinstance . Container , region string ) api . ServiceStatus {
2020-08-28 14:38:51 +02:00
var replicas = 1
2020-09-04 15:09:57 +02:00
if GetStatus ( container , group ) != StatusRunning {
2020-08-28 14:38:51 +02:00
replicas = 0
}
2021-06-14 16:26:14 +02:00
return api . ServiceStatus {
2020-08-28 14:38:51 +02:00
ID : containerID ,
Name : * container . Name ,
2020-12-07 14:46:36 +01:00
Ports : formatter . PortsToStrings ( ToPorts ( group . IPAddress , * container . Ports ) , FQDN ( group , region ) ) ,
2020-08-28 14:38:51 +02:00
Replicas : replicas ,
Desired : 1 ,
}
}
2020-12-07 14:46:36 +01:00
// FQDN retrieve the fully qualified domain name for a ContainerGroup
func FQDN ( group containerinstance . ContainerGroup , region string ) string {
2020-09-21 12:42:44 +02:00
fqdn := ""
if group . IPAddress != nil && group . IPAddress . DNSNameLabel != nil && * group . IPAddress . DNSNameLabel != "" {
fqdn = * group . IPAddress . DNSNameLabel + "." + region + ".azurecontainer.io"
}
return fqdn
}
2020-06-16 10:59:40 +02:00
// ContainerGroupToContainer composes a Container from an ACI container definition
2020-09-21 12:42:44 +02:00
func ContainerGroupToContainer ( containerID string , cg containerinstance . ContainerGroup , cc containerinstance . Container , region string ) containers . Container {
2020-06-15 10:38:37 +02:00
command := ""
if cc . Command != nil {
command = strings . Join ( * cc . Command , " " )
}
2020-08-18 10:50:56 +02:00
status := GetStatus ( cc , cg )
2020-07-06 12:15:07 +02:00
platform := string ( cg . OsType )
2020-06-15 10:38:37 +02:00
2021-05-19 17:00:13 +02:00
var envVars map [ string ] string
2020-09-10 11:59:49 +02:00
if cc . EnvironmentVariables != nil && len ( * cc . EnvironmentVariables ) != 0 {
envVars = map [ string ] string { }
for _ , envVar := range * cc . EnvironmentVariables {
envVars [ * envVar . Name ] = * envVar . Value
}
}
2020-10-15 12:49:43 +02:00
hostConfig := ToHostConfig ( cc , cg )
2020-10-07 18:25:01 +02:00
config := & containers . RuntimeConfig {
2020-12-07 14:46:36 +01:00
FQDN : FQDN ( cg , region ) ,
2020-10-07 18:25:01 +02:00
Env : envVars ,
}
2020-10-29 02:11:08 +01:00
var healthcheck = containers . Healthcheck {
Disable : true ,
}
if cc . LivenessProbe != nil &&
cc . LivenessProbe . Exec != nil &&
cc . LivenessProbe . Exec . Command != nil {
if len ( * cc . LivenessProbe . Exec . Command ) > 0 {
healthcheck . Disable = false
healthcheck . Test = * cc . LivenessProbe . Exec . Command
if cc . LivenessProbe . PeriodSeconds != nil {
healthcheck . Interval = types . Duration ( int64 ( * cc . LivenessProbe . PeriodSeconds ) * int64 ( time . Second ) )
}
2020-11-13 11:55:14 +01:00
if cc . LivenessProbe . FailureThreshold != nil {
healthcheck . Retries = int ( * cc . LivenessProbe . FailureThreshold )
2020-10-29 02:11:08 +01:00
}
if cc . LivenessProbe . TimeoutSeconds != nil {
healthcheck . Timeout = types . Duration ( int64 ( * cc . LivenessProbe . TimeoutSeconds ) * int64 ( time . Second ) )
}
if cc . LivenessProbe . InitialDelaySeconds != nil {
healthcheck . StartPeriod = types . Duration ( int64 ( * cc . LivenessProbe . InitialDelaySeconds ) * int64 ( time . Second ) )
}
}
}
2020-06-15 10:38:37 +02:00
c := containers . Container {
2020-10-07 18:25:01 +02:00
ID : containerID ,
Status : status ,
Image : to . String ( cc . Image ) ,
Command : command ,
CPUTime : 0 ,
MemoryUsage : 0 ,
PidsCurrent : 0 ,
PidsLimit : 0 ,
Ports : ToPorts ( cg . IPAddress , * cc . Ports ) ,
Platform : platform ,
Config : config ,
HostConfig : hostConfig ,
2020-10-29 02:11:08 +01:00
Healthcheck : healthcheck ,
2020-06-15 10:38:37 +02:00
}
2020-08-18 10:50:56 +02:00
return c
}
2020-10-15 12:49:43 +02:00
// ToHostConfig convert an ACI container to host config value
func ToHostConfig ( cc containerinstance . Container , cg containerinstance . ContainerGroup ) * containers . HostConfig {
memLimits := uint64 ( 0 )
memRequest := uint64 ( 0 )
cpuLimit := 0.
cpuReservation := 0.
if cc . Resources != nil {
if cc . Resources . Limits != nil {
if cc . Resources . Limits . MemoryInGB != nil {
memLimits = gbToBytes ( * cc . Resources . Limits . MemoryInGB )
}
if cc . Resources . Limits . CPU != nil {
cpuLimit = * cc . Resources . Limits . CPU
}
}
if cc . Resources . Requests != nil {
if cc . Resources . Requests . MemoryInGB != nil {
memRequest = gbToBytes ( * cc . Resources . Requests . MemoryInGB )
}
if cc . Resources . Requests . CPU != nil {
cpuReservation = * cc . Resources . Requests . CPU
}
}
}
hostConfig := & containers . HostConfig {
CPULimit : cpuLimit ,
CPUReservation : cpuReservation ,
MemoryLimit : memLimits ,
MemoryReservation : memRequest ,
RestartPolicy : toContainerRestartPolicy ( cg . RestartPolicy ) ,
}
return hostConfig
}
2020-08-18 10:50:56 +02:00
// GetStatus returns status for the specified container
func GetStatus ( container containerinstance . Container , group containerinstance . ContainerGroup ) string {
2020-10-14 16:06:45 +02:00
status := GetGroupStatus ( group )
2020-08-18 10:50:56 +02:00
if container . InstanceView != nil && container . InstanceView . CurrentState != nil {
status = * container . InstanceView . CurrentState . State
}
return status
2020-06-15 10:38:37 +02:00
}
2020-10-14 16:06:45 +02:00
// GetGroupStatus returns status for the container group
func GetGroupStatus ( group containerinstance . ContainerGroup ) string {
if group . InstanceView != nil && group . InstanceView . State != nil {
return "Node " + * group . InstanceView . State
}
2021-06-14 16:26:14 +02:00
return api . UNKNOWN
2020-10-14 16:06:45 +02:00
}