2020-12-08 11:53:36 +01:00
/ *
Copyright 2020 Docker Compose CLI authors
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 .
* /
package compose
import (
2021-09-24 08:37:11 +02:00
"bytes"
2020-12-08 11:53:36 +01:00
"context"
2021-09-24 08:37:11 +02:00
"encoding/json"
2023-11-13 16:37:26 +01:00
"errors"
2020-12-08 11:53:36 +01:00
"fmt"
2022-06-25 14:02:21 +08:00
"os"
2020-12-08 11:53:36 +01:00
"path/filepath"
2025-05-27 14:27:45 +02:00
"slices"
2020-12-08 11:53:36 +01:00
"strconv"
"strings"
2025-04-28 10:37:51 +02:00
"github.com/compose-spec/compose-go/v2/paths"
2024-03-20 12:41:24 -06:00
"github.com/compose-spec/compose-go/v2/types"
2025-05-29 10:37:19 +02:00
cerrdefs "github.com/containerd/errdefs"
2021-04-12 15:46:18 +02:00
"github.com/docker/docker/api/types/blkiodev"
2020-12-08 11:53:36 +01:00
"github.com/docker/docker/api/types/container"
2022-06-01 15:28:42 -04:00
"github.com/docker/docker/api/types/filters"
2020-12-08 11:53:36 +01:00
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
2023-11-20 16:37:59 +01:00
"github.com/docker/docker/api/types/versions"
2024-06-07 17:57:54 +02:00
volumetypes "github.com/docker/docker/api/types/volume"
2020-12-08 11:53:36 +01:00
"github.com/docker/go-connections/nat"
2021-01-06 14:23:37 +01:00
"github.com/sirupsen/logrus"
2024-10-04 15:59:17 +02:00
cdi "tags.cncf.io/container-device-interface/pkg/parser"
2025-06-02 17:24:57 +02:00
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/prompt"
2020-12-08 11:53:36 +01:00
)
2023-06-28 11:27:58 -04:00
type createOptions struct {
AutoRemove bool
AttachStdin bool
UseNetworkAliases bool
Labels types . Labels
}
type createConfigs struct {
Container * container . Config
Host * container . HostConfig
Network * network . NetworkingConfig
Links [ ] string
}
2023-08-30 08:47:09 -04:00
func ( s * composeService ) Create ( ctx context . Context , project * types . Project , createOpts api . CreateOptions ) error {
2023-05-02 20:15:35 +02:00
return progress . RunWithTitle ( ctx , func ( ctx context . Context ) error {
2024-04-15 08:54:55 +02:00
return s . create ( ctx , project , createOpts )
2023-05-11 18:45:00 +02:00
} , s . stdinfo ( ) , "Creating" )
2021-06-07 14:21:55 +02:00
}
2024-04-15 08:54:55 +02:00
func ( s * composeService ) create ( ctx context . Context , project * types . Project , options api . CreateOptions ) error {
2021-06-07 14:21:55 +02:00
if len ( options . Services ) == 0 {
options . Services = project . ServiceNames ( )
2021-03-01 17:45:52 +01:00
}
2024-04-11 09:05:40 +02:00
err := project . CheckContainerNameUnicity ( )
if err != nil {
return err
}
2023-08-30 08:47:09 -04:00
err = s . ensureImagesExists ( ctx , project , options . Build , options . QuietPull )
2020-12-08 11:53:36 +01:00
if err != nil {
return err
}
2020-12-14 11:35:53 +01:00
prepareNetworks ( project )
2024-11-05 15:42:59 +01:00
networks , err := s . ensureNetworks ( ctx , project )
if err != nil {
2020-12-14 17:22:30 +01:00
return err
}
2024-12-12 09:36:24 +01:00
volumes , err := s . ensureProjectVolumes ( ctx , project , options . AssumeYes )
2024-10-21 12:01:50 +01:00
if err != nil {
2020-12-14 17:22:30 +01:00
return err
}
2024-11-05 15:42:59 +01:00
var observedState Containers
observedState , err = s . getContainers ( ctx , project . Name , oneOffInclude , true )
if err != nil {
return err
}
2024-07-08 09:40:04 +02:00
orphans := observedState . filter ( isOrphaned ( project ) )
2021-09-24 08:22:50 +02:00
if len ( orphans ) > 0 && ! options . IgnoreOrphans {
2021-06-07 14:21:55 +02:00
if options . RemoveOrphans {
2024-09-26 15:14:42 +02:00
err := s . removeContainers ( ctx , orphans , nil , nil , false )
2021-01-20 09:35:06 -03:00
if err != nil {
return err
}
2021-01-06 14:23:37 +01:00
} else {
logrus . Warnf ( "Found orphan containers (%s) for this project. If " +
"you removed or renamed this service in your compose " +
"file, you can run this command with the " +
"--remove-orphans flag to clean it up." , orphans . names ( ) )
}
}
2024-10-21 12:01:50 +01:00
return newConvergence ( options . Services , observedState , networks , volumes , s ) . apply ( ctx , project , options )
2020-12-14 17:22:30 +01:00
}
2020-12-14 11:35:53 +01:00
func prepareNetworks ( project * types . Project ) {
2024-06-07 17:57:54 +02:00
for k , nw := range project . Networks {
2024-11-05 15:42:59 +01:00
nw . CustomLabels = nw . CustomLabels .
Add ( api . NetworkLabel , k ) .
Add ( api . ProjectLabel , project . Name ) .
Add ( api . VersionLabel , api . ComposeVersion )
2024-06-07 17:57:54 +02:00
project . Networks [ k ] = nw
2020-12-14 11:35:53 +01:00
}
}
2024-11-05 15:42:59 +01:00
func ( s * composeService ) ensureNetworks ( ctx context . Context , project * types . Project ) ( map [ string ] string , error ) {
networks := map [ string ] string { }
for name , nw := range project . Networks {
id , err := s . ensureNetwork ( ctx , project , name , & nw )
2020-12-08 11:53:36 +01:00
if err != nil {
2024-11-05 15:42:59 +01:00
return nil , err
2020-12-08 11:53:36 +01:00
}
2024-11-05 15:42:59 +01:00
networks [ name ] = id
project . Networks [ name ] = nw
2020-12-08 11:53:36 +01:00
}
2024-11-05 15:42:59 +01:00
return networks , nil
2020-12-14 17:22:30 +01:00
}
2020-12-08 11:53:36 +01:00
2024-12-12 09:36:24 +01:00
func ( s * composeService ) ensureProjectVolumes ( ctx context . Context , project * types . Project , assumeYes bool ) ( map [ string ] string , error ) {
2024-10-21 12:01:50 +01:00
ids := map [ string ] string { }
2020-12-08 11:53:36 +01:00
for k , volume := range project . Volumes {
2024-12-12 09:36:24 +01:00
volume . CustomLabels = volume . CustomLabels . Add ( api . VolumeLabel , k )
volume . CustomLabels = volume . CustomLabels . Add ( api . ProjectLabel , project . Name )
volume . CustomLabels = volume . CustomLabels . Add ( api . VersionLabel , api . ComposeVersion )
id , err := s . ensureVolume ( ctx , k , volume , project , assumeYes )
2020-12-08 11:53:36 +01:00
if err != nil {
2024-10-21 12:01:50 +01:00
return nil , err
2020-12-08 11:53:36 +01:00
}
2024-10-21 12:01:50 +01:00
ids [ k ] = id
2020-12-08 11:53:36 +01:00
}
2024-03-20 12:41:24 -06:00
2024-10-21 12:01:50 +01:00
return ids , nil
2020-12-08 11:53:36 +01:00
}
2025-04-23 16:21:54 +02:00
//nolint:gocyclo
2023-06-28 11:27:58 -04:00
func ( s * composeService ) getCreateConfigs ( ctx context . Context ,
2023-03-17 09:49:39 +00:00
p * types . Project ,
service types . ServiceConfig ,
number int ,
2025-02-12 09:34:07 +01:00
inherit * container . Summary ,
2023-06-28 11:27:58 -04:00
opts createOptions ,
) ( createConfigs , error ) {
labels , err := s . prepareLabels ( opts . Labels , service , number )
2020-12-08 11:53:36 +01:00
if err != nil {
2023-06-28 11:27:58 -04:00
return createConfigs { } , err
2020-12-08 11:53:36 +01:00
}
2020-12-08 15:23:24 +01:00
2020-12-08 11:53:36 +01:00
var (
runCmd strslice . StrSlice
entrypoint strslice . StrSlice
)
2021-09-08 09:38:58 +02:00
if service . Command != nil {
2021-01-14 12:47:36 +01:00
runCmd = strslice . StrSlice ( service . Command )
2020-12-08 11:53:36 +01:00
}
2021-09-08 09:38:58 +02:00
if service . Entrypoint != nil {
2021-01-14 12:47:36 +01:00
entrypoint = strslice . StrSlice ( service . Entrypoint )
2020-12-08 11:53:36 +01:00
}
var (
2021-09-23 09:30:00 +02:00
tty = service . Tty
stdinOpen = service . StdinOpen
2020-12-08 11:53:36 +01:00
)
2022-02-23 11:28:56 +01:00
proxyConfig := types . MappingWithEquals ( s . configFile ( ) . ParseProxyConfig ( s . apiClient ( ) . DaemonHost ( ) , nil ) )
2021-08-31 14:04:31 +02:00
env := proxyConfig . OverrideBy ( service . Environment )
2023-11-20 16:37:59 +01:00
var mainNwName string
var mainNw * types . ServiceNetworkConfig
if len ( service . Networks ) > 0 {
mainNwName = service . NetworksByPriority ( ) [ 0 ]
mainNw = service . Networks [ mainNwName ]
}
macAddress , err := s . prepareContainerMACAddress ( ctx , service , mainNw , mainNwName )
if err != nil {
return createConfigs { } , err
}
2023-08-28 09:01:34 +02:00
healthcheck , err := s . ToMobyHealthCheck ( ctx , service . HealthCheck )
if err != nil {
return createConfigs { } , err
}
2024-12-10 10:30:37 +01:00
containerConfig := container . Config {
2021-01-14 12:47:36 +01:00
Hostname : service . Hostname ,
Domainname : service . DomainName ,
User : service . User ,
ExposedPorts : buildContainerPorts ( service ) ,
2020-12-08 11:53:36 +01:00
Tty : tty ,
OpenStdin : stdinOpen ,
2023-06-28 11:27:58 -04:00
StdinOnce : opts . AttachStdin && stdinOpen ,
AttachStdin : opts . AttachStdin ,
2020-12-08 11:53:36 +01:00
AttachStderr : true ,
AttachStdout : true ,
Cmd : runCmd ,
2022-02-16 15:51:10 +01:00
Image : api . GetImageNameOrDefault ( service , p . Name ) ,
2021-01-14 12:47:36 +01:00
WorkingDir : service . WorkingDir ,
2020-12-08 11:53:36 +01:00
Entrypoint : entrypoint ,
2021-01-14 12:47:36 +01:00
NetworkDisabled : service . NetworkMode == "disabled" ,
2025-02-12 09:34:07 +01:00
MacAddress : macAddress , // Field is deprecated since API v1.44, but kept for compatibility with older API versions.
2020-12-08 11:53:36 +01:00
Labels : labels ,
2021-01-14 12:47:36 +01:00
StopSignal : service . StopSignal ,
2021-08-31 14:04:31 +02:00
Env : ToMobyEnv ( env ) ,
2023-08-28 09:01:34 +02:00
Healthcheck : healthcheck ,
2021-06-15 09:57:38 +02:00
StopTimeout : ToSeconds ( service . StopGracePeriod ) ,
2023-08-28 09:01:34 +02:00
} // VOLUMES/MOUNTS/FILESYSTEMS
2021-03-18 09:39:48 +01:00
tmpfs := map [ string ] string { }
for _ , t := range service . Tmpfs {
if arr := strings . SplitN ( t , ":" , 2 ) ; len ( arr ) > 1 {
tmpfs [ arr [ 0 ] ] = arr [ 1 ]
} else {
tmpfs [ arr [ 0 ] ] = ""
}
}
2023-06-28 11:27:58 -04:00
binds , mounts , err := s . buildContainerVolumes ( ctx , * p , service , inherit )
if err != nil {
return createConfigs { } , err
2021-03-18 09:39:48 +01:00
}
2021-09-09 09:14:13 +02:00
2023-06-28 11:27:58 -04:00
// NETWORKING
2022-06-02 01:53:20 +02:00
links , err := s . getLinks ( ctx , p . Name , service , number )
if err != nil {
2023-06-28 11:27:58 -04:00
return createConfigs { } , err
2022-06-02 01:53:20 +02:00
}
2024-01-30 17:41:19 +00:00
apiVersion , err := s . RuntimeVersion ( ctx )
if err != nil {
return createConfigs { } , err
}
2025-04-23 16:21:54 +02:00
networkMode , networkingConfig , err := defaultNetworkSettings ( p , service , number , links , opts . UseNetworkAliases , apiVersion )
if err != nil {
return createConfigs { } , err
}
2023-06-28 11:27:58 -04:00
portBindings := buildContainerPortBindingOptions ( service )
2022-06-02 01:53:20 +02:00
2023-06-28 11:27:58 -04:00
// MISC
resources := getDeployResources ( service )
var logConfig container . LogConfig
if service . Logging != nil {
logConfig = container . LogConfig {
Type : service . Logging . Driver ,
Config : service . Logging . Options ,
}
}
2022-12-21 08:39:26 +01:00
securityOpts , unconfined , err := parseSecurityOpts ( p , service . SecurityOpt )
2021-09-24 08:37:11 +02:00
if err != nil {
2023-06-28 11:27:58 -04:00
return createConfigs { } , err
2021-09-24 08:37:11 +02:00
}
2023-06-28 11:27:58 -04:00
2020-12-08 11:53:36 +01:00
hostConfig := container . HostConfig {
2023-06-28 11:27:58 -04:00
AutoRemove : opts . AutoRemove ,
2024-03-21 13:52:11 +01:00
Annotations : service . Annotations ,
2021-01-11 15:54:56 +01:00
Binds : binds ,
2021-01-15 09:49:56 +01:00
Mounts : mounts ,
2021-01-14 12:47:36 +01:00
CapAdd : strslice . StrSlice ( service . CapAdd ) ,
CapDrop : strslice . StrSlice ( service . CapDrop ) ,
2023-06-28 11:27:58 -04:00
NetworkMode : networkMode ,
2021-01-14 12:47:36 +01:00
Init : service . Init ,
2021-06-24 16:35:38 +02:00
IpcMode : container . IpcMode ( service . Ipc ) ,
2022-12-20 10:59:22 +01:00
CgroupnsMode : container . CgroupnsMode ( service . Cgroup ) ,
2021-01-14 12:47:36 +01:00
ReadonlyRootfs : service . ReadOnly ,
2021-03-08 10:22:24 +01:00
RestartPolicy : getRestartPolicy ( service ) ,
2021-04-21 13:40:37 +02:00
ShmSize : int64 ( service . ShmSize ) ,
2021-03-18 09:39:48 +01:00
Sysctls : service . Sysctls ,
PortBindings : portBindings ,
Resources : resources ,
VolumeDriver : service . VolumeDriver ,
2023-09-14 09:11:27 +02:00
VolumesFrom : service . VolumesFrom ,
2021-03-18 09:39:48 +01:00
DNS : service . DNS ,
DNSSearch : service . DNSSearch ,
DNSOptions : service . DNSOpts ,
2023-11-27 10:14:31 +01:00
ExtraHosts : service . ExtraHosts . AsList ( ":" ) ,
2021-09-24 08:37:11 +02:00
SecurityOpt : securityOpts ,
2024-01-29 15:42:06 +01:00
StorageOpt : service . StorageOpt ,
2021-03-18 09:39:48 +01:00
UsernsMode : container . UsernsMode ( service . UserNSMode ) ,
2023-01-05 17:01:23 +01:00
UTSMode : container . UTSMode ( service . Uts ) ,
2021-03-18 09:39:48 +01:00
Privileged : service . Privileged ,
PidMode : container . PidMode ( service . Pid ) ,
Tmpfs : tmpfs ,
Isolation : container . Isolation ( service . Isolation ) ,
2021-10-12 08:07:25 +02:00
Runtime : service . Runtime ,
2021-03-18 09:39:48 +01:00
LogConfig : logConfig ,
2021-12-03 14:31:10 +08:00
GroupAdd : service . GroupAdd ,
2022-06-02 01:53:20 +02:00
Links : links ,
2022-11-24 15:32:03 +01:00
OomScoreAdj : int ( service . OomScoreAdj ) ,
2020-12-08 11:53:36 +01:00
}
2022-12-21 08:39:26 +01:00
if unconfined {
hostConfig . MaskedPaths = [ ] string { }
hostConfig . ReadonlyPaths = [ ] string { }
}
2023-06-28 11:27:58 -04:00
cfgs := createConfigs {
Container : & containerConfig ,
Host : & hostConfig ,
Network : networkingConfig ,
Links : links ,
}
return cfgs , nil
}
2023-11-20 16:37:59 +01:00
// prepareContainerMACAddress handles the service-level mac_address field and the newer mac_address field added to service
// network config. This newer field is only compatible with the Engine API v1.44 (and onwards), and this API version
// also deprecates the container-wide mac_address field. Thus, this method will validate service config and mutate the
// passed mainNw to provide backward-compatibility whenever possible.
//
// It returns the container-wide MAC address, but this value will be kept empty for newer API versions.
func ( s * composeService ) prepareContainerMACAddress ( ctx context . Context , service types . ServiceConfig , mainNw * types . ServiceNetworkConfig , nwName string ) ( string , error ) {
version , err := s . RuntimeVersion ( ctx )
if err != nil {
return "" , err
}
// Engine API 1.44 added support for endpoint-specific MAC address and now returns a warning when a MAC address is
// set in container.Config. Thus, we have to jump through a number of hoops:
//
// 1. Top-level mac_address and main endpoint's MAC address should be the same ;
// 2. If supported by the API, top-level mac_address should be migrated to the main endpoint and container.Config
// should be kept empty ;
// 3. Otherwise, the endpoint mac_address should be set in container.Config and no other endpoint-specific
// mac_address can be specified. If that's the case, use top-level mac_address ;
//
// After that, if an endpoint mac_address is set, it's either user-defined or migrated by the code below, so
// there's no need to check for API version in defaultNetworkSettings.
macAddress := service . MacAddress
if macAddress != "" && mainNw != nil && mainNw . MacAddress != "" && mainNw . MacAddress != macAddress {
return "" , fmt . Errorf ( "the service-level mac_address should have the same value as network %s" , nwName )
}
if versions . GreaterThanOrEqualTo ( version , "1.44" ) {
if mainNw != nil && mainNw . MacAddress == "" {
mainNw . MacAddress = macAddress
}
macAddress = ""
} else if len ( service . Networks ) > 0 {
var withMacAddress [ ] string
for nwName , nw := range service . Networks {
if nw != nil && nw . MacAddress != "" {
withMacAddress = append ( withMacAddress , nwName )
}
}
if len ( withMacAddress ) > 1 {
2025-04-23 16:21:54 +02:00
return "" , fmt . Errorf ( "a MAC address is specified for multiple networks (%s), but this feature requires Docker Engine v25 or later" , strings . Join ( withMacAddress , ", " ) )
2023-11-20 16:37:59 +01:00
}
2025-01-07 10:30:15 +01:00
if mainNw != nil && mainNw . MacAddress != "" {
2023-11-20 16:37:59 +01:00
macAddress = mainNw . MacAddress
}
}
return macAddress , nil
}
2025-01-10 11:28:49 +01:00
func getAliases ( project * types . Project , service types . ServiceConfig , serviceIndex int , cfg * types . ServiceNetworkConfig , useNetworkAliases bool ) [ ] string {
2023-06-28 11:27:58 -04:00
aliases := [ ] string { getContainerName ( project . Name , service , serviceIndex ) }
if useNetworkAliases {
aliases = append ( aliases , service . Name )
2025-01-10 11:28:49 +01:00
if cfg != nil {
2023-06-28 11:27:58 -04:00
aliases = append ( aliases , cfg . Aliases ... )
}
}
return aliases
2020-12-08 11:53:36 +01:00
}
2023-06-28 11:27:58 -04:00
func createEndpointSettings ( p * types . Project , service types . ServiceConfig , serviceIndex int , networkKey string , links [ ] string , useNetworkAliases bool ) * network . EndpointSettings {
2025-04-23 16:21:54 +02:00
const ifname = "com.docker.network.endpoint.ifname"
2023-06-28 11:27:58 -04:00
config := service . Networks [ networkKey ]
2022-12-21 09:06:12 +01:00
var ipam * network . EndpointIPAMConfig
var (
ipv4Address string
ipv6Address string
2023-11-20 16:37:59 +01:00
macAddress string
2024-05-14 12:00:06 +01:00
driverOpts types . Options
2025-02-20 11:16:12 +01:00
gwPriority int
2022-12-21 09:06:12 +01:00
)
if config != nil {
ipv4Address = config . Ipv4Address
ipv6Address = config . Ipv6Address
ipam = & network . EndpointIPAMConfig {
IPv4Address : ipv4Address ,
IPv6Address : ipv6Address ,
LinkLocalIPs : config . LinkLocalIPs ,
}
2023-11-20 16:37:59 +01:00
macAddress = config . MacAddress
2024-05-14 12:00:06 +01:00
driverOpts = config . DriverOpts
2025-04-23 16:21:54 +02:00
if config . InterfaceName != "" {
if driverOpts == nil {
driverOpts = map [ string ] string { }
}
if name , ok := driverOpts [ ifname ] ; ok && name != config . InterfaceName {
logrus . Warnf ( "ignoring services.%s.networks.%s.interface_name as %s driver_opts is already declared" , service . Name , networkKey , ifname )
}
driverOpts [ ifname ] = config . InterfaceName
}
2025-02-20 11:16:12 +01:00
gwPriority = config . GatewayPriority
2022-12-21 09:06:12 +01:00
}
2023-06-28 11:27:58 -04:00
return & network . EndpointSettings {
2025-01-10 11:28:49 +01:00
Aliases : getAliases ( p , service , serviceIndex , config , useNetworkAliases ) ,
2023-06-28 11:27:58 -04:00
Links : links ,
IPAddress : ipv4Address ,
IPv6Gateway : ipv6Address ,
IPAMConfig : ipam ,
2023-11-20 16:37:59 +01:00
MacAddress : macAddress ,
2024-05-14 12:00:06 +01:00
DriverOpts : driverOpts ,
2025-02-20 11:16:12 +01:00
GwPriority : gwPriority ,
2022-12-21 09:06:12 +01:00
}
}
2021-09-24 08:37:11 +02:00
// copy/pasted from https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673-L697 + RelativePath
// TODO find so way to share this code with docker/cli
2022-12-21 08:39:26 +01:00
func parseSecurityOpts ( p * types . Project , securityOpts [ ] string ) ( [ ] string , bool , error ) {
var (
unconfined bool
parsed [ ] string
)
for _ , opt := range securityOpts {
if opt == "systempaths=unconfined" {
unconfined = true
continue
}
2021-09-24 08:37:11 +02:00
con := strings . SplitN ( opt , "=" , 2 )
if len ( con ) == 1 && con [ 0 ] != "no-new-privileges" {
if strings . Contains ( opt , ":" ) {
con = strings . SplitN ( opt , ":" , 2 )
} else {
2025-03-31 17:22:58 +02:00
return securityOpts , false , fmt . Errorf ( "invalid security-opt: %q" , opt )
2021-09-24 08:37:11 +02:00
}
}
2025-01-17 13:11:28 +02:00
if con [ 0 ] == "seccomp" && con [ 1 ] != "unconfined" && con [ 1 ] != "builtin" {
2022-06-25 14:02:21 +08:00
f , err := os . ReadFile ( p . RelativePath ( con [ 1 ] ) )
2021-09-24 08:37:11 +02:00
if err != nil {
2023-09-26 00:57:12 +02:00
return securityOpts , false , fmt . Errorf ( "opening seccomp profile (%s) failed: %w" , con [ 1 ] , err )
2021-09-24 08:37:11 +02:00
}
b := bytes . NewBuffer ( nil )
if err := json . Compact ( b , f ) ; err != nil {
2023-09-26 00:57:12 +02:00
return securityOpts , false , fmt . Errorf ( "compacting json for seccomp profile (%s) failed: %w" , con [ 1 ] , err )
2021-09-24 08:37:11 +02:00
}
2022-12-21 08:39:26 +01:00
parsed = append ( parsed , fmt . Sprintf ( "seccomp=%s" , b . Bytes ( ) ) )
} else {
parsed = append ( parsed , opt )
2021-09-24 08:37:11 +02:00
}
}
2022-12-21 08:39:26 +01:00
return parsed , unconfined , nil
2021-09-24 08:37:11 +02:00
}
2023-03-17 09:49:39 +00:00
func ( s * composeService ) prepareLabels ( labels types . Labels , service types . ServiceConfig , number int ) ( map [ string ] string , error ) {
2021-09-20 08:16:59 +02:00
hash , err := ServiceHash ( service )
if err != nil {
return nil , err
}
labels [ api . ConfigHashLabel ] = hash
2021-11-24 09:11:27 +01:00
2024-10-21 15:37:42 +02:00
if number > 0 {
// One-off containers are not indexed
labels [ api . ContainerNumberLabel ] = strconv . Itoa ( number )
}
2021-11-24 09:11:27 +01:00
2021-09-20 08:16:59 +02:00
var dependencies [ ] string
2022-02-24 14:50:50 +03:30
for s , d := range service . DependsOn {
2023-03-01 17:34:33 +01:00
dependencies = append ( dependencies , fmt . Sprintf ( "%s:%s:%t" , s , d . Condition , d . Restart ) )
2021-09-20 08:16:59 +02:00
}
labels [ api . DependenciesLabel ] = strings . Join ( dependencies , "," )
return labels , nil
}
2023-06-28 11:27:58 -04:00
// defaultNetworkSettings determines the container.NetworkMode and corresponding network.NetworkingConfig (nil if not applicable).
2025-04-23 16:21:54 +02:00
func defaultNetworkSettings ( project * types . Project ,
service types . ServiceConfig , serviceIndex int ,
links [ ] string , useNetworkAliases bool ,
2024-01-30 17:41:19 +00:00
version string ,
2025-04-23 16:21:54 +02:00
) ( container . NetworkMode , * network . NetworkingConfig , error ) {
2023-06-28 11:27:58 -04:00
if service . NetworkMode != "" {
2025-04-23 16:21:54 +02:00
return container . NetworkMode ( service . NetworkMode ) , nil , nil
2023-06-28 11:27:58 -04:00
}
2022-01-28 14:31:55 +00:00
if len ( project . Networks ) == 0 {
2025-04-23 16:21:54 +02:00
return "none" , nil , nil
2021-03-24 19:01:04 +01:00
}
2022-01-28 14:31:55 +00:00
2024-01-30 17:41:19 +00:00
var primaryNetworkKey string
2022-01-28 14:31:55 +00:00
if len ( service . Networks ) > 0 {
2024-01-30 17:41:19 +00:00
primaryNetworkKey = service . NetworksByPriority ( ) [ 0 ]
2023-06-28 11:27:58 -04:00
} else {
2024-01-30 17:41:19 +00:00
primaryNetworkKey = "default"
}
primaryNetworkMobyNetworkName := project . Networks [ primaryNetworkKey ] . Name
2024-12-10 14:22:30 +01:00
primaryNetworkEndpoint := createEndpointSettings ( project , service , serviceIndex , primaryNetworkKey , links , useNetworkAliases )
endpointsConfig := map [ string ] * network . EndpointSettings { }
2024-01-30 17:41:19 +00:00
// Starting from API version 1.44, the Engine will take several EndpointsConfigs
// so we can pass all the extra networks we want the container to be connected to
// in the network configuration instead of connecting the container to each extra
// network individually after creation.
2024-12-10 14:22:30 +01:00
if versions . GreaterThanOrEqualTo ( version , "1.44" ) {
if len ( service . Networks ) > 1 {
serviceNetworks := service . NetworksByPriority ( )
for _ , networkKey := range serviceNetworks [ 1 : ] {
mobyNetworkName := project . Networks [ networkKey ] . Name
epSettings := createEndpointSettings ( project , service , serviceIndex , networkKey , links , useNetworkAliases )
endpointsConfig [ mobyNetworkName ] = epSettings
}
}
if primaryNetworkEndpoint . MacAddress == "" {
primaryNetworkEndpoint . MacAddress = service . MacAddress
2024-01-30 17:41:19 +00:00
}
2022-01-28 14:31:55 +00:00
}
2024-01-30 17:41:19 +00:00
2025-04-23 16:21:54 +02:00
if versions . LessThan ( version , "1.49" ) {
for _ , config := range service . Networks {
if config != nil && config . InterfaceName != "" {
return "" , nil , fmt . Errorf ( "interface_name requires Docker Engine v28.1 or later" )
}
}
}
2024-12-10 14:22:30 +01:00
endpointsConfig [ primaryNetworkMobyNetworkName ] = primaryNetworkEndpoint
2023-06-28 11:27:58 -04:00
networkConfig := & network . NetworkingConfig {
2024-01-30 17:41:19 +00:00
EndpointsConfig : endpointsConfig ,
2023-06-28 11:27:58 -04:00
}
2024-01-30 17:41:19 +00:00
2023-06-28 11:27:58 -04:00
// From the Engine API docs:
// > Supported standard values are: bridge, host, none, and container:<name|id>.
// > Any other value is taken as a custom network's name to which this container should connect to.
2025-04-23 16:21:54 +02:00
return container . NetworkMode ( primaryNetworkMobyNetworkName ) , networkConfig , nil
2021-03-24 19:01:04 +01:00
}
2021-03-08 10:22:24 +01:00
func getRestartPolicy ( service types . ServiceConfig ) container . RestartPolicy {
var restart container . RestartPolicy
if service . Restart != "" {
split := strings . Split ( service . Restart , ":" )
var attempts int
if len ( split ) > 1 {
attempts , _ = strconv . Atoi ( split [ 1 ] )
}
restart = container . RestartPolicy {
2023-12-04 10:12:20 -06:00
Name : mapRestartPolicyCondition ( split [ 0 ] ) ,
2021-03-08 10:22:24 +01:00
MaximumRetryCount : attempts ,
}
}
if service . Deploy != nil && service . Deploy . RestartPolicy != nil {
policy := * service . Deploy . RestartPolicy
var attempts int
if policy . MaxAttempts != nil {
attempts = int ( * policy . MaxAttempts )
}
restart = container . RestartPolicy {
2022-10-24 11:04:44 +02:00
Name : mapRestartPolicyCondition ( policy . Condition ) ,
2021-03-08 10:22:24 +01:00
MaximumRetryCount : attempts ,
}
}
return restart
}
2023-12-04 10:12:20 -06:00
func mapRestartPolicyCondition ( condition string ) container . RestartPolicyMode {
2022-10-24 11:04:44 +02:00
// map definitions of deploy.restart_policy to engine definitions
switch condition {
case "none" , "no" :
2023-12-05 16:53:20 +01:00
return container . RestartPolicyDisabled
2023-12-04 10:12:20 -06:00
case "on-failure" :
2023-12-05 16:53:20 +01:00
return container . RestartPolicyOnFailure
2023-12-04 10:12:20 -06:00
case "unless-stopped" :
2023-12-05 16:53:20 +01:00
return container . RestartPolicyUnlessStopped
2022-10-24 11:04:44 +02:00
case "any" , "always" :
2023-12-05 16:53:20 +01:00
return container . RestartPolicyAlways
2022-10-24 11:04:44 +02:00
default :
2023-12-05 16:53:20 +01:00
return container . RestartPolicyMode ( condition )
2022-10-24 11:04:44 +02:00
}
}
2021-01-06 17:11:03 +01:00
func getDeployResources ( s types . ServiceConfig ) container . Resources {
2021-04-12 15:46:18 +02:00
var swappiness * int64
if s . MemSwappiness != 0 {
val := int64 ( s . MemSwappiness )
swappiness = & val
}
resources := container . Resources {
CgroupParent : s . CgroupParent ,
Memory : int64 ( s . MemLimit ) ,
MemorySwap : int64 ( s . MemSwapLimit ) ,
MemorySwappiness : swappiness ,
MemoryReservation : int64 ( s . MemReservation ) ,
2021-10-11 17:52:31 +02:00
OomKillDisable : & s . OomKillDisable ,
2021-04-12 15:46:18 +02:00
CPUCount : s . CPUCount ,
CPUPeriod : s . CPUPeriod ,
CPUQuota : s . CPUQuota ,
CPURealtimePeriod : s . CPURTPeriod ,
CPURealtimeRuntime : s . CPURTRuntime ,
CPUShares : s . CPUShares ,
2022-12-19 14:59:31 +01:00
NanoCPUs : int64 ( s . CPUS * 1e9 ) ,
CPUPercent : int64 ( s . CPUPercent * 100 ) ,
2021-04-12 15:46:18 +02:00
CpusetCpus : s . CPUSet ,
2022-03-09 11:39:20 +01:00
DeviceCgroupRules : s . DeviceCgroupRules ,
2021-04-12 15:46:18 +02:00
}
2021-10-11 17:52:31 +02:00
if s . PidsLimit != 0 {
resources . PidsLimit = & s . PidsLimit
}
2021-04-12 15:46:18 +02:00
setBlkio ( s . BlkioConfig , & resources )
if s . Deploy != nil {
setLimits ( s . Deploy . Resources . Limits , & resources )
setReservations ( s . Deploy . Resources . Reservations , & resources )
2021-01-06 17:11:03 +01:00
}
2021-03-18 09:39:48 +01:00
2024-10-04 15:59:17 +02:00
var cdiDeviceNames [ ] string
2021-03-18 09:39:48 +01:00
for _ , device := range s . Devices {
2024-10-04 15:59:17 +02:00
if device . Source == device . Target && cdi . IsQualifiedName ( device . Source ) {
cdiDeviceNames = append ( cdiDeviceNames , device . Source )
continue
}
2021-03-18 09:39:48 +01:00
resources . Devices = append ( resources . Devices , container . DeviceMapping {
2024-09-11 15:27:47 +02:00
PathOnHost : device . Source ,
PathInContainer : device . Target ,
CgroupPermissions : device . Permissions ,
2021-03-18 09:39:48 +01:00
} )
}
2024-10-04 15:59:17 +02:00
if len ( cdiDeviceNames ) > 0 {
resources . DeviceRequests = append ( resources . DeviceRequests , container . DeviceRequest {
Driver : "cdi" ,
DeviceIDs : cdiDeviceNames ,
} )
}
2024-10-09 14:52:33 +02:00
for _ , gpus := range s . Gpus {
resources . DeviceRequests = append ( resources . DeviceRequests , container . DeviceRequest {
Driver : gpus . Driver ,
Count : int ( gpus . Count ) ,
DeviceIDs : gpus . IDs ,
Capabilities : [ ] [ ] string { append ( gpus . Capabilities , "gpu" ) } ,
Options : gpus . Options ,
} )
}
2023-11-13 19:10:29 +01:00
ulimits := toUlimits ( s . Ulimits )
resources . Ulimits = ulimits
return resources
}
2024-06-07 17:57:54 +02:00
func toUlimits ( m map [ string ] * types . UlimitsConfig ) [ ] * container . Ulimit {
var ulimits [ ] * container . Ulimit
2023-11-13 19:10:29 +01:00
for name , u := range m {
2021-05-06 09:58:28 +02:00
soft := u . Single
if u . Soft != 0 {
soft = u . Soft
}
hard := u . Single
if u . Hard != 0 {
hard = u . Hard
}
2024-06-07 17:57:54 +02:00
ulimits = append ( ulimits , & container . Ulimit {
2021-03-18 09:39:48 +01:00
Name : name ,
2021-06-10 09:49:09 +02:00
Hard : int64 ( hard ) ,
Soft : int64 ( soft ) ,
2021-03-18 09:39:48 +01:00
} )
}
2023-11-13 19:10:29 +01:00
return ulimits
2021-01-06 17:11:03 +01:00
}
2021-04-12 15:46:18 +02:00
func setReservations ( reservations * types . Resource , resources * container . Resources ) {
if reservations == nil {
return
}
2022-10-24 15:51:57 +02:00
// Cpu reservation is a swarm option and PIDs is only a limit
// So we only need to map memory reservation and devices
if reservations . MemoryBytes != 0 {
resources . MemoryReservation = int64 ( reservations . MemoryBytes )
}
2021-04-12 15:46:18 +02:00
for _ , device := range reservations . Devices {
resources . DeviceRequests = append ( resources . DeviceRequests , container . DeviceRequest {
Capabilities : [ ] [ ] string { device . Capabilities } ,
Count : int ( device . Count ) ,
DeviceIDs : device . IDs ,
Driver : device . Driver ,
2024-10-04 13:39:54 +02:00
Options : device . Options ,
2021-04-12 15:46:18 +02:00
} )
}
}
func setLimits ( limits * types . Resource , resources * container . Resources ) {
if limits == nil {
return
}
if limits . MemoryBytes != 0 {
resources . Memory = int64 ( limits . MemoryBytes )
}
2024-03-22 07:51:59 +01:00
if limits . NanoCPUs != 0 {
resources . NanoCPUs = int64 ( limits . NanoCPUs * 1e9 )
2021-04-12 15:46:18 +02:00
}
2023-05-23 14:06:45 +02:00
if limits . Pids > 0 {
resources . PidsLimit = & limits . Pids
2022-06-10 11:05:17 +02:00
}
2021-04-12 15:46:18 +02:00
}
func setBlkio ( blkio * types . BlkioConfig , resources * container . Resources ) {
if blkio == nil {
return
}
resources . BlkioWeight = blkio . Weight
for _ , b := range blkio . WeightDevice {
resources . BlkioWeightDevice = append ( resources . BlkioWeightDevice , & blkiodev . WeightDevice {
Path : b . Path ,
Weight : b . Weight ,
} )
}
for _ , b := range blkio . DeviceReadBps {
resources . BlkioDeviceReadBps = append ( resources . BlkioDeviceReadBps , & blkiodev . ThrottleDevice {
Path : b . Path ,
2023-04-17 12:09:16 +02:00
Rate : uint64 ( b . Rate ) ,
2021-04-12 15:46:18 +02:00
} )
}
for _ , b := range blkio . DeviceReadIOps {
resources . BlkioDeviceReadIOps = append ( resources . BlkioDeviceReadIOps , & blkiodev . ThrottleDevice {
Path : b . Path ,
2023-04-17 12:09:16 +02:00
Rate : uint64 ( b . Rate ) ,
2021-04-12 15:46:18 +02:00
} )
}
for _ , b := range blkio . DeviceWriteBps {
resources . BlkioDeviceWriteBps = append ( resources . BlkioDeviceWriteBps , & blkiodev . ThrottleDevice {
Path : b . Path ,
2023-04-17 12:09:16 +02:00
Rate : uint64 ( b . Rate ) ,
2021-04-12 15:46:18 +02:00
} )
}
for _ , b := range blkio . DeviceWriteIOps {
resources . BlkioDeviceWriteIOps = append ( resources . BlkioDeviceWriteIOps , & blkiodev . ThrottleDevice {
Path : b . Path ,
2023-04-17 12:09:16 +02:00
Rate : uint64 ( b . Rate ) ,
2021-04-12 15:46:18 +02:00
} )
}
}
2020-12-08 11:53:36 +01:00
func buildContainerPorts ( s types . ServiceConfig ) nat . PortSet {
ports := nat . PortSet { }
2021-07-30 11:14:26 +02:00
for _ , s := range s . Expose {
p := nat . Port ( s )
ports [ p ] = struct { } { }
}
2020-12-08 11:53:36 +01:00
for _ , p := range s . Ports {
p := nat . Port ( fmt . Sprintf ( "%d/%s" , p . Target , p . Protocol ) )
ports [ p ] = struct { } { }
}
return ports
}
2021-01-11 15:54:56 +01:00
func buildContainerPortBindingOptions ( s types . ServiceConfig ) nat . PortMap {
2020-12-08 11:53:36 +01:00
bindings := nat . PortMap { }
for _ , port := range s . Ports {
p := nat . Port ( fmt . Sprintf ( "%d/%s" , port . Target , port . Protocol ) )
2021-03-17 14:05:03 +01:00
binding := nat . PortBinding {
2022-03-01 11:44:51 +01:00
HostIP : port . HostIP ,
HostPort : port . Published ,
2021-03-17 14:05:03 +01:00
}
2022-03-01 11:44:51 +01:00
bindings [ p ] = append ( bindings [ p ] , binding )
2020-12-08 11:53:36 +01:00
}
return bindings
}
2021-03-24 19:01:04 +01:00
func getDependentServiceFromMode ( mode string ) string {
2023-05-23 14:06:45 +02:00
if strings . HasPrefix (
mode ,
types . NetworkModeServicePrefix ,
) {
2021-03-24 19:01:04 +01:00
return mode [ len ( types . NetworkModeServicePrefix ) : ]
2021-02-09 10:22:04 -03:00
}
2021-03-24 19:01:04 +01:00
return ""
2021-02-09 10:22:04 -03:00
}
2022-12-16 09:38:57 -05:00
func ( s * composeService ) buildContainerVolumes (
ctx context . Context ,
p types . Project ,
service types . ServiceConfig ,
2025-02-12 09:34:07 +01:00
inherit * container . Summary ,
2022-12-16 09:38:57 -05:00
) ( [ ] string , [ ] mount . Mount , error ) {
var mounts [ ] mount . Mount
var binds [ ] string
2021-01-15 09:49:56 +01:00
2025-04-11 08:37:32 +02:00
mountOptions , err := s . buildContainerMountOptions ( ctx , p , service , inherit )
2021-01-15 09:49:56 +01:00
if err != nil {
2022-12-16 09:38:57 -05:00
return nil , nil , err
2021-01-15 09:49:56 +01:00
}
for _ , m := range mountOptions {
2025-04-08 19:44:26 +02:00
switch m . Type {
case mount . TypeBind :
2024-09-20 10:12:09 +02:00
// `Mount` is preferred but does not offer option to created host path if missing
2022-04-13 14:36:22 +02:00
// so `Bind` API is used here with raw volume string
2024-09-20 10:12:09 +02:00
// see https://github.com/moby/moby/issues/43483
2025-04-08 19:44:26 +02:00
v := findVolumeByTarget ( service . Volumes , m . Target )
if v != nil {
2025-04-11 08:37:32 +02:00
if v . Type != types . VolumeTypeBind {
2025-04-08 19:44:26 +02:00
v . Source = m . Source
2025-04-11 08:37:32 +02:00
}
if ! bindRequiresMountAPI ( v . Bind ) {
2025-04-10 18:46:35 +02:00
source := m . Source
if vol := findVolumeByName ( p . Volumes , m . Source ) ; vol != nil {
source = m . Source
2022-05-31 14:59:18 +02:00
}
2025-04-10 18:46:35 +02:00
binds = append ( binds , toBindString ( source , v ) )
continue
2021-04-27 12:18:12 +02:00
}
2021-01-15 09:49:56 +01:00
}
2025-04-08 19:44:26 +02:00
case mount . TypeVolume :
v := findVolumeByTarget ( service . Volumes , m . Target )
vol := findVolumeByName ( p . Volumes , m . Source )
if v != nil && vol != nil {
2025-04-11 08:37:32 +02:00
// Prefer the bind API if no advanced option is used, to preserve backward compatibility
if ! volumeRequiresMountAPI ( v . Volume ) {
2025-04-08 19:44:26 +02:00
binds = append ( binds , toBindString ( vol . Name , v ) )
continue
}
}
case mount . TypeImage :
2025-04-09 09:52:31 +02:00
version , err := s . RuntimeVersion ( ctx )
if err != nil {
return nil , nil , err
}
if versions . LessThan ( version , "1.48" ) {
return nil , nil , fmt . Errorf ( "volume with type=image require Docker Engine v28 or later" )
}
}
2021-04-27 12:18:12 +02:00
mounts = append ( mounts , m )
2021-01-15 09:49:56 +01:00
}
2022-12-16 09:38:57 -05:00
return binds , mounts , nil
2021-01-15 09:49:56 +01:00
}
2025-04-08 19:44:26 +02:00
func toBindString ( name string , v * types . ServiceVolumeConfig ) string {
access := "rw"
if v . ReadOnly {
access = "ro"
}
options := [ ] string { access }
if v . Bind != nil && v . Bind . SELinux != "" {
options = append ( options , v . Bind . SELinux )
}
if v . Bind != nil && v . Bind . Propagation != "" {
options = append ( options , v . Bind . Propagation )
}
if v . Volume != nil && v . Volume . NoCopy {
options = append ( options , "nocopy" )
}
return fmt . Sprintf ( "%s:%s:%s" , name , v . Target , strings . Join ( options , "," ) )
}
func findVolumeByName ( volumes types . Volumes , name string ) * types . VolumeConfig {
for _ , vol := range volumes {
if vol . Name == name {
return & vol
}
}
return nil
}
func findVolumeByTarget ( volumes [ ] types . ServiceVolumeConfig , target string ) * types . ServiceVolumeConfig {
for _ , v := range volumes {
if v . Target == target {
return & v
}
}
return nil
}
2025-04-11 08:37:32 +02:00
// bindRequiresMountAPI check if Bind declaration can be implemented by the plain old Bind API or uses any of the advanced
2024-10-15 11:01:02 +02:00
// options which require use of Mount API
2025-04-11 08:37:32 +02:00
func bindRequiresMountAPI ( bind * types . ServiceVolumeBind ) bool {
2024-10-15 11:01:02 +02:00
switch {
case bind == nil :
return false
case ! bind . CreateHostPath :
return true
case bind . Propagation != "" :
return true
case bind . Recursive != "" :
return true
default :
return false
}
}
2025-04-11 08:37:32 +02:00
// volumeRequiresMountAPI check if Volume declaration can be implemented by the plain old Bind API or uses any of the advanced
// options which require use of Mount API
func volumeRequiresMountAPI ( vol * types . ServiceVolumeVolume ) bool {
switch {
case vol == nil :
return false
case len ( vol . Labels ) > 0 :
return true
case vol . Subpath != "" :
return true
case vol . NoCopy :
return true
default :
return false
}
}
func ( s * composeService ) buildContainerMountOptions ( ctx context . Context , p types . Project , service types . ServiceConfig , inherit * container . Summary ) ( [ ] mount . Mount , error ) {
2024-12-10 10:30:37 +01:00
mounts := map [ string ] mount . Mount { }
2020-12-08 11:53:36 +01:00
if inherit != nil {
for _ , m := range inherit . Mounts {
if m . Type == "tmpfs" {
continue
}
src := m . Source
if m . Type == "volume" {
src = m . Name
}
2021-06-11 16:30:19 +02:00
2025-04-11 08:37:32 +02:00
img , err := s . apiClient ( ) . ImageInspect ( ctx , api . GetImageNameOrDefault ( service , p . Name ) )
if err != nil {
return nil , err
}
2021-06-11 16:30:19 +02:00
if img . Config != nil {
if _ , ok := img . Config . Volumes [ m . Destination ] ; ok {
// inherit previous container's anonymous volume
mounts [ m . Destination ] = mount . Mount {
Type : m . Type ,
Source : src ,
Target : m . Destination ,
ReadOnly : ! m . RW ,
}
}
2021-01-11 15:54:56 +01:00
}
2021-10-05 09:16:44 +02:00
volumes := [ ] types . ServiceVolumeConfig { }
2025-04-11 08:37:32 +02:00
for _ , v := range service . Volumes {
2021-10-05 09:16:44 +02:00
if v . Target != m . Destination || v . Source != "" {
volumes = append ( volumes , v )
2021-06-22 14:37:52 +02:00
continue
}
2021-10-05 09:16:44 +02:00
// inherit previous container's anonymous volume
mounts [ m . Destination ] = mount . Mount {
Type : m . Type ,
Source : src ,
Target : m . Destination ,
ReadOnly : ! m . RW ,
2021-06-22 14:37:52 +02:00
}
}
2025-04-11 08:37:32 +02:00
service . Volumes = volumes
2020-12-08 11:53:36 +01:00
}
2021-01-11 15:54:56 +01:00
}
2021-02-04 09:15:01 -03:00
2025-04-11 08:37:32 +02:00
mounts , err := fillBindMounts ( p , service , mounts )
2021-02-04 09:15:01 -03:00
if err != nil {
return nil , err
}
values := make ( [ ] mount . Mount , 0 , len ( mounts ) )
for _ , v := range mounts {
values = append ( values , v )
}
return values , nil
}
2024-09-20 10:12:09 +02:00
func fillBindMounts ( p types . Project , s types . ServiceConfig , m map [ string ] mount . Mount ) ( map [ string ] mount . Mount , error ) {
for _ , v := range s . Volumes {
bindMount , err := buildMount ( p , v )
2020-12-08 11:53:36 +01:00
if err != nil {
return nil , err
}
2021-02-04 09:15:01 -03:00
m [ bindMount . Target ] = bindMount
2020-12-08 11:53:36 +01:00
}
2021-01-07 14:59:15 +01:00
2024-09-20 10:12:09 +02:00
secrets , err := buildContainerSecretMounts ( p , s )
2021-01-15 09:49:56 +01:00
if err != nil {
return nil , err
}
2024-09-20 10:12:09 +02:00
for _ , s := range secrets {
if _ , found := m [ s . Target ] ; found {
2021-01-15 09:49:56 +01:00
continue
}
2024-09-20 10:12:09 +02:00
m [ s . Target ] = s
2020-12-08 11:53:36 +01:00
}
2021-01-07 14:59:15 +01:00
2024-09-20 10:12:09 +02:00
configs , err := buildContainerConfigMounts ( p , s )
2021-02-04 09:15:01 -03:00
if err != nil {
return nil , err
}
for _ , c := range configs {
if _ , found := m [ c . Target ] ; found {
continue
}
m [ c . Target ] = c
}
return m , nil
}
2024-09-20 10:12:09 +02:00
func buildContainerConfigMounts ( p types . Project , s types . ServiceConfig ) ( [ ] mount . Mount , error ) {
2024-12-10 10:30:37 +01:00
mounts := map [ string ] mount . Mount { }
2021-02-04 09:15:01 -03:00
configsBaseDir := "/"
2024-09-20 10:12:09 +02:00
for _ , config := range s . Configs {
2021-02-04 09:15:01 -03:00
target := config . Target
if config . Target == "" {
2021-04-06 12:25:08 +02:00
target = configsBaseDir + config . Source
2023-07-18 10:00:32 +02:00
} else if ! isAbsTarget ( config . Target ) {
2021-04-06 12:25:08 +02:00
target = configsBaseDir + config . Target
2021-02-04 09:15:01 -03:00
}
definedConfig := p . Configs [ config . Source ]
2023-11-08 10:19:24 +01:00
if definedConfig . External {
2021-02-04 09:15:01 -03:00
return nil , fmt . Errorf ( "unsupported external config %s" , definedConfig . Name )
}
2023-11-13 16:37:26 +01:00
if definedConfig . Driver != "" {
2025-03-31 17:22:58 +02:00
return nil , errors . New ( "Docker Compose does not support configs.*.driver" ) //nolint:staticcheck
2023-11-13 16:37:26 +01:00
}
if definedConfig . TemplateDriver != "" {
2025-03-31 17:22:58 +02:00
return nil , errors . New ( "Docker Compose does not support configs.*.template_driver" ) //nolint:staticcheck
2023-11-13 16:37:26 +01:00
}
2023-11-13 19:10:29 +01:00
if definedConfig . Environment != "" || definedConfig . Content != "" {
continue
}
2024-10-24 15:11:03 +02:00
if config . UID != "" || config . GID != "" || config . Mode != nil {
logrus . Warn ( "config `uid`, `gid` and `mode` are not supported, they will be ignored" )
}
2024-09-20 10:12:09 +02:00
bindMount , err := buildMount ( p , types . ServiceVolumeConfig {
2021-02-04 09:15:01 -03:00
Type : types . VolumeTypeBind ,
Source : definedConfig . File ,
Target : target ,
ReadOnly : true ,
} )
if err != nil {
return nil , err
}
mounts [ target ] = bindMount
}
2021-01-11 15:54:56 +01:00
values := make ( [ ] mount . Mount , 0 , len ( mounts ) )
for _ , v := range mounts {
values = append ( values , v )
}
return values , nil
}
2024-09-20 10:12:09 +02:00
func buildContainerSecretMounts ( p types . Project , s types . ServiceConfig ) ( [ ] mount . Mount , error ) {
2024-12-10 10:30:37 +01:00
mounts := map [ string ] mount . Mount { }
2021-01-11 15:54:56 +01:00
2021-04-06 12:25:08 +02:00
secretsDir := "/run/secrets/"
2024-09-20 10:12:09 +02:00
for _ , secret := range s . Secrets {
2021-01-07 14:59:15 +01:00
target := secret . Target
if secret . Target == "" {
2021-04-06 12:25:08 +02:00
target = secretsDir + secret . Source
2023-07-18 10:00:32 +02:00
} else if ! isAbsTarget ( secret . Target ) {
2021-04-06 12:25:08 +02:00
target = secretsDir + secret . Target
2021-01-07 14:59:15 +01:00
}
definedSecret := p . Secrets [ secret . Source ]
2023-11-08 10:19:24 +01:00
if definedSecret . External {
2021-01-07 14:59:15 +01:00
return nil , fmt . Errorf ( "unsupported external secret %s" , definedSecret . Name )
}
2021-01-08 16:11:37 +01:00
2023-11-13 16:37:26 +01:00
if definedSecret . Driver != "" {
2025-03-31 17:22:58 +02:00
return nil , errors . New ( "Docker Compose does not support secrets.*.driver" ) //nolint:staticcheck
2023-11-13 16:37:26 +01:00
}
if definedSecret . TemplateDriver != "" {
2025-03-31 17:22:58 +02:00
return nil , errors . New ( "Docker Compose does not support secrets.*.template_driver" ) //nolint:staticcheck
2023-11-13 16:37:26 +01:00
}
2022-06-10 13:36:06 +02:00
if definedSecret . Environment != "" {
continue
}
2024-10-24 15:11:03 +02:00
if secret . UID != "" || secret . GID != "" || secret . Mode != nil {
logrus . Warn ( "secrets `uid`, `gid` and `mode` are not supported, they will be ignored" )
}
2024-09-23 09:48:07 +02:00
if _ , err := os . Stat ( definedSecret . File ) ; os . IsNotExist ( err ) {
logrus . Warnf ( "secret file %s does not exist" , definedSecret . Name )
}
2024-09-20 10:12:09 +02:00
mnt , err := buildMount ( p , types . ServiceVolumeConfig {
2021-01-15 09:49:56 +01:00
Type : types . VolumeTypeBind ,
Source : definedSecret . File ,
Target : target ,
ReadOnly : true ,
2024-09-23 09:48:07 +02:00
Bind : & types . ServiceVolumeBind {
CreateHostPath : false ,
} ,
2021-01-07 14:59:15 +01:00
} )
if err != nil {
return nil , err
}
2022-07-13 02:53:08 +02:00
mounts [ target ] = mnt
2021-01-07 14:59:15 +01:00
}
2021-01-11 15:54:56 +01:00
values := make ( [ ] mount . Mount , 0 , len ( mounts ) )
for _ , v := range mounts {
values = append ( values , v )
}
return values , nil
2020-12-08 11:53:36 +01:00
}
2023-07-18 10:00:32 +02:00
func isAbsTarget ( p string ) bool {
return isUnixAbs ( p ) || isWindowsAbs ( p )
}
2022-07-13 02:53:08 +02:00
func isUnixAbs ( p string ) bool {
return strings . HasPrefix ( p , "/" )
2021-04-06 12:25:08 +02:00
}
2023-07-18 10:00:32 +02:00
func isWindowsAbs ( p string ) bool {
2025-04-28 10:37:51 +02:00
return paths . IsWindowsAbs ( p )
2023-07-18 10:00:32 +02:00
}
2024-09-20 10:12:09 +02:00
func buildMount ( project types . Project , volume types . ServiceVolumeConfig ) ( mount . Mount , error ) {
2020-12-08 11:53:36 +01:00
source := volume . Source
2025-04-28 10:37:51 +02:00
switch volume . Type {
case types . VolumeTypeBind :
if ! filepath . IsAbs ( source ) && ! isUnixAbs ( source ) && ! isWindowsAbs ( source ) {
// volume source has already been prefixed with workdir if required, by compose-go project loader
var err error
source , err = filepath . Abs ( source )
if err != nil {
return mount . Mount { } , err
}
2020-12-08 11:53:36 +01:00
}
2025-04-28 10:37:51 +02:00
case types . VolumeTypeVolume :
2021-01-11 15:54:56 +01:00
if volume . Source != "" {
pVolume , ok := project . Volumes [ volume . Source ]
if ok {
source = pVolume . Name
}
2020-12-08 17:43:10 +01:00
}
}
2020-12-08 11:53:36 +01:00
2025-04-09 09:52:31 +02:00
bind , vol , tmpfs , img := buildMountOptions ( volume )
2021-04-13 11:53:06 +02:00
2022-05-31 14:59:18 +02:00
if bind != nil {
volume . Type = types . VolumeTypeBind
}
2020-12-08 11:53:36 +01:00
return mount . Mount {
Type : mount . Type ( volume . Type ) ,
Source : source ,
Target : volume . Target ,
ReadOnly : volume . ReadOnly ,
Consistency : mount . Consistency ( volume . Consistency ) ,
2021-04-13 11:53:06 +02:00
BindOptions : bind ,
VolumeOptions : vol ,
TmpfsOptions : tmpfs ,
2025-04-09 09:52:31 +02:00
ImageOptions : img ,
2020-12-08 11:53:36 +01:00
} , nil
}
2025-04-09 09:52:31 +02:00
func buildMountOptions ( volume types . ServiceVolumeConfig ) ( * mount . BindOptions , * mount . VolumeOptions , * mount . TmpfsOptions , * mount . ImageOptions ) {
if volume . Type != types . VolumeTypeBind && volume . Bind != nil {
logrus . Warnf ( "mount of type `%s` should not define `bind` option" , volume . Type )
}
if volume . Type != types . VolumeTypeVolume && volume . Volume != nil {
logrus . Warnf ( "mount of type `%s` should not define `volume` option" , volume . Type )
}
if volume . Type != types . VolumeTypeTmpfs && volume . Tmpfs != nil {
logrus . Warnf ( "mount of type `%s` should not define `tmpfs` option" , volume . Type )
}
if volume . Type != types . VolumeTypeImage && volume . Image != nil {
logrus . Warnf ( "mount of type `%s` should not define `image` option" , volume . Type )
}
2021-04-13 11:53:06 +02:00
switch volume . Type {
case "bind" :
2025-04-09 09:52:31 +02:00
return buildBindOption ( volume . Bind ) , nil , nil , nil
2021-04-13 11:53:06 +02:00
case "volume" :
2025-04-09 09:52:31 +02:00
return nil , buildVolumeOptions ( volume . Volume ) , nil , nil
2021-04-13 11:53:06 +02:00
case "tmpfs" :
2025-04-09 09:52:31 +02:00
return nil , nil , buildTmpfsOptions ( volume . Tmpfs ) , nil
case "image" :
return nil , nil , nil , buildImageOptions ( volume . Image )
2021-04-13 11:53:06 +02:00
}
2025-04-09 09:52:31 +02:00
return nil , nil , nil , nil
2021-04-13 11:53:06 +02:00
}
2024-09-20 10:12:09 +02:00
func buildBindOption ( bind * types . ServiceVolumeBind ) * mount . BindOptions {
2020-12-08 11:53:36 +01:00
if bind == nil {
2024-09-20 10:12:09 +02:00
return nil
2024-09-17 09:30:34 +02:00
}
2024-10-15 11:01:02 +02:00
opts := & mount . BindOptions {
Propagation : mount . Propagation ( bind . Propagation ) ,
CreateMountpoint : bind . CreateHostPath ,
}
switch bind . Recursive {
case "disabled" :
opts . NonRecursive = true
case "writable" :
opts . ReadOnlyNonRecursive = true
case "readonly" :
opts . ReadOnlyForceRecursive = true
2024-09-20 10:12:09 +02:00
}
2024-10-15 11:01:02 +02:00
return opts
2020-12-08 11:53:36 +01:00
}
func buildVolumeOptions ( vol * types . ServiceVolumeVolume ) * mount . VolumeOptions {
if vol == nil {
return nil
}
return & mount . VolumeOptions {
2024-03-22 13:58:05 +01:00
NoCopy : vol . NoCopy ,
Subpath : vol . Subpath ,
2025-04-09 09:52:31 +02:00
Labels : vol . Labels ,
2020-12-08 11:53:36 +01:00
// DriverConfig: , // FIXME missing from model ?
}
}
func buildTmpfsOptions ( tmpfs * types . ServiceVolumeTmpfs ) * mount . TmpfsOptions {
if tmpfs == nil {
return nil
}
return & mount . TmpfsOptions {
2021-09-30 17:23:37 +02:00
SizeBytes : int64 ( tmpfs . Size ) ,
2022-12-02 11:21:53 -05:00
Mode : os . FileMode ( tmpfs . Mode ) ,
2020-12-08 11:53:36 +01:00
}
}
2025-04-09 09:52:31 +02:00
func buildImageOptions ( image * types . ServiceVolumeImage ) * mount . ImageOptions {
if image == nil {
return nil
}
return & mount . ImageOptions {
Subpath : image . SubPath ,
}
}
2024-11-05 15:42:59 +01:00
func ( s * composeService ) ensureNetwork ( ctx context . Context , project * types . Project , name string , n * types . NetworkConfig ) ( string , error ) {
2023-11-08 10:19:24 +01:00
if n . External {
2023-05-31 20:46:23 +02:00
return s . resolveExternalNetwork ( ctx , n )
}
2024-11-05 15:42:59 +01:00
id , err := s . resolveOrCreateNetwork ( ctx , project , name , n )
2025-05-29 10:37:19 +02:00
if cerrdefs . IsConflict ( err ) {
2023-05-31 20:46:23 +02:00
// Maybe another execution of `docker compose up|run` created same network
// let's retry once
2024-12-24 10:47:32 +01:00
return s . resolveOrCreateNetwork ( ctx , project , name , n )
2023-05-31 20:46:23 +02:00
}
2024-11-05 15:42:59 +01:00
return id , err
2023-05-31 20:46:23 +02:00
}
2024-11-05 15:42:59 +01:00
func ( s * composeService ) resolveOrCreateNetwork ( ctx context . Context , project * types . Project , name string , n * types . NetworkConfig ) ( string , error ) { //nolint:gocyclo
2025-06-02 17:24:57 +02:00
// This is containers that could be left after a diverged network was removed
var dangledContainers Containers
2023-05-31 20:46:23 +02:00
// First, try to find a unique network matching by name or ID
2024-06-07 17:57:54 +02:00
inspect , err := s . apiClient ( ) . NetworkInspect ( ctx , n . Name , network . InspectOptions { } )
2023-05-31 20:46:23 +02:00
if err == nil {
// NetworkInspect will match on ID prefix, so double check we get the expected one
2024-09-08 07:01:35 +10:00
// as looking for network named `db` we could erroneously match network ID `db9086999caf`
2023-05-31 20:46:23 +02:00
if inspect . Name == n . Name || inspect . ID == n . Name {
2023-06-01 09:03:58 +02:00
p , ok := inspect . Labels [ api . ProjectLabel ]
if ! ok {
2023-05-31 20:46:23 +02:00
logrus . Warnf ( "a network with name %s exists but was not created by compose.\n" +
"Set `external: true` to use an existing network" , n . Name )
2024-11-05 15:42:59 +01:00
} else if p != project . Name {
2023-06-01 09:03:58 +02:00
logrus . Warnf ( "a network with name %s exists but was not created for project %q.\n" +
2024-11-05 15:42:59 +01:00
"Set `external: true` to use an existing network" , n . Name , project . Name )
2023-05-31 20:46:23 +02:00
}
2024-11-05 15:42:59 +01:00
if inspect . Labels [ api . NetworkLabel ] != name {
return "" , fmt . Errorf (
2024-10-15 14:31:50 +02:00
"network %s was found but has incorrect label %s set to %q (expected: %q)" ,
n . Name ,
api . NetworkLabel ,
inspect . Labels [ api . NetworkLabel ] ,
2024-11-05 15:42:59 +01:00
name ,
2024-10-15 14:31:50 +02:00
)
2023-05-31 20:46:23 +02:00
}
2024-11-05 15:42:59 +01:00
hash := inspect . Labels [ api . ConfigHashLabel ]
expected , err := NetworkHash ( n )
if err != nil {
return "" , err
}
if hash == "" || hash == expected {
return inspect . ID , nil
}
2025-06-02 17:24:57 +02:00
dangledContainers , err = s . removeDivergedNetwork ( ctx , project , name , n )
2024-11-05 15:42:59 +01:00
if err != nil {
return "" , err
}
2023-05-31 20:46:23 +02:00
}
}
// ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
// Either not found, or name is ambiguous - use NetworkList to list by name
2024-06-07 17:57:54 +02:00
networks , err := s . apiClient ( ) . NetworkList ( ctx , network . ListOptions {
2022-06-01 15:28:42 -04:00
Filters : filters . NewArgs ( filters . Arg ( "name" , n . Name ) ) ,
} )
2020-12-08 11:53:36 +01:00
if err != nil {
2024-11-05 15:42:59 +01:00
return "" , err
2022-06-01 15:28:42 -04:00
}
2023-05-31 20:46:23 +02:00
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
2025-05-27 14:27:45 +02:00
networks = slices . DeleteFunc ( networks , func ( net network . Summary ) bool {
return net . Name != n . Name
2023-05-31 20:46:23 +02:00
} )
2022-07-11 12:15:56 +02:00
for _ , net := range networks {
2024-11-05 15:42:59 +01:00
if net . Labels [ api . ProjectLabel ] == project . Name &&
net . Labels [ api . NetworkLabel ] == name {
return net . ID , nil
2022-07-11 12:15:56 +02:00
}
}
2023-05-31 20:46:23 +02:00
// we could have set NetworkList with a projectFilter and networkFilter but not doing so allows to catch this
// scenario were a network with same name exists but doesn't have label, and use of `CheckDuplicate: true`
// prevents to create another one.
if len ( networks ) > 0 {
logrus . Warnf ( "a network with name %s exists but was not created by compose.\n" +
"Set `external: true` to use an existing network" , n . Name )
2024-11-05 15:42:59 +01:00
return networks [ 0 ] . ID , nil
2023-05-31 20:46:23 +02:00
}
var ipam * network . IPAM
if n . Ipam . Config != nil {
var config [ ] network . IPAMConfig
for _ , pool := range n . Ipam . Config {
config = append ( config , network . IPAMConfig {
Subnet : pool . Subnet ,
IPRange : pool . IPRange ,
Gateway : pool . Gateway ,
AuxAddress : pool . AuxiliaryAddresses ,
} )
2022-06-01 15:28:42 -04:00
}
2023-05-31 20:46:23 +02:00
ipam = & network . IPAM {
Driver : n . Ipam . Driver ,
Config : config ,
2022-06-01 15:28:42 -04:00
}
2023-05-31 20:46:23 +02:00
}
2024-11-05 15:42:59 +01:00
hash , err := NetworkHash ( n )
if err != nil {
return "" , err
}
n . CustomLabels = n . CustomLabels . Add ( api . ConfigHashLabel , hash )
2024-06-07 17:57:54 +02:00
createOpts := network . CreateOptions {
2024-11-05 15:42:59 +01:00
Labels : mergeLabels ( n . Labels , n . CustomLabels ) ,
2024-06-07 17:57:54 +02:00
Driver : n . Driver ,
Options : n . DriverOpts ,
Internal : n . Internal ,
Attachable : n . Attachable ,
IPAM : ipam ,
2024-06-07 18:02:17 +02:00
EnableIPv6 : n . EnableIPv6 ,
2025-02-20 11:16:12 +01:00
EnableIPv4 : n . EnableIPv4 ,
2023-05-31 20:46:23 +02:00
}
2020-12-08 11:53:36 +01:00
2023-05-31 20:46:23 +02:00
if n . Ipam . Driver != "" || len ( n . Ipam . Config ) > 0 {
createOpts . IPAM = & network . IPAM { }
}
if n . Ipam . Driver != "" {
createOpts . IPAM . Driver = n . Ipam . Driver
}
2020-12-08 11:53:36 +01:00
2023-05-31 20:46:23 +02:00
for _ , ipamConfig := range n . Ipam . Config {
config := network . IPAMConfig {
Subnet : ipamConfig . Subnet ,
IPRange : ipamConfig . IPRange ,
Gateway : ipamConfig . Gateway ,
AuxAddress : ipamConfig . AuxiliaryAddresses ,
2022-06-01 15:28:42 -04:00
}
2023-05-31 20:46:23 +02:00
createOpts . IPAM . Config = append ( createOpts . IPAM . Config , config )
}
2025-02-20 11:16:12 +01:00
2023-05-31 20:46:23 +02:00
networkEventName := fmt . Sprintf ( "Network %s" , n . Name )
w := progress . ContextWriter ( ctx )
w . Event ( progress . CreatingEvent ( networkEventName ) )
2024-11-05 15:42:59 +01:00
resp , err := s . apiClient ( ) . NetworkCreate ( ctx , n . Name , createOpts )
2023-05-31 20:46:23 +02:00
if err != nil {
w . Event ( progress . ErrorEvent ( networkEventName ) )
2024-11-05 15:42:59 +01:00
return "" , fmt . Errorf ( "failed to create network %s: %w" , n . Name , err )
2023-05-31 20:46:23 +02:00
}
w . Event ( progress . CreatedEvent ( networkEventName ) )
2025-06-02 17:24:57 +02:00
err = s . connectNetwork ( ctx , n . Name , dangledContainers , nil )
if err != nil {
return "" , err
}
2024-11-05 15:42:59 +01:00
return resp . ID , nil
}
2025-06-02 17:24:57 +02:00
func ( s * composeService ) removeDivergedNetwork ( ctx context . Context , project * types . Project , name string , n * types . NetworkConfig ) ( Containers , error ) {
2024-11-05 15:42:59 +01:00
// Remove services attached to this network to force recreation
var services [ ] string
for _ , service := range project . Services . Filter ( func ( config types . ServiceConfig ) bool {
_ , ok := config . Networks [ name ]
return ok
} ) {
services = append ( services , service . Name )
}
// Stop containers so we can remove network
// They will be restarted (actually: recreated) with the updated network
err := s . stop ( ctx , project . Name , api . StopOptions {
Services : services ,
Project : project ,
} )
if err != nil {
2025-06-02 17:24:57 +02:00
return nil , err
}
containers , err := s . getContainers ( ctx , project . Name , oneOffExclude , true , services ... )
if err != nil {
return nil , err
}
err = s . disconnectNetwork ( ctx , n . Name , containers )
if err != nil {
return nil , err
2024-11-05 15:42:59 +01:00
}
err = s . apiClient ( ) . NetworkRemove ( ctx , n . Name )
eventName := fmt . Sprintf ( "Network %s" , n . Name )
progress . ContextWriter ( ctx ) . Event ( progress . RemovedEvent ( eventName ) )
2025-06-02 17:24:57 +02:00
return containers , err
}
func ( s * composeService ) disconnectNetwork (
ctx context . Context ,
network string ,
containers Containers ,
) error {
for _ , c := range containers {
err := s . apiClient ( ) . NetworkDisconnect ( ctx , network , c . ID , true )
if err != nil {
return err
}
}
return nil
}
func ( s * composeService ) connectNetwork (
ctx context . Context ,
network string ,
containers Containers ,
config * network . EndpointSettings ,
) error {
for _ , c := range containers {
err := s . apiClient ( ) . NetworkConnect ( ctx , network , c . ID , config )
if err != nil {
return err
}
}
return nil
2023-05-31 20:46:23 +02:00
}
2024-11-05 15:42:59 +01:00
func ( s * composeService ) resolveExternalNetwork ( ctx context . Context , n * types . NetworkConfig ) ( string , error ) {
2023-05-31 20:46:23 +02:00
// NetworkInspect will match on ID prefix, so NetworkList with a name
// filter is used to look for an exact match to prevent e.g. a network
// named `db` from getting erroneously matched to a network with an ID
// like `db9086999caf`
2024-06-07 17:57:54 +02:00
networks , err := s . apiClient ( ) . NetworkList ( ctx , network . ListOptions {
2023-05-31 20:46:23 +02:00
Filters : filters . NewArgs ( filters . Arg ( "name" , n . Name ) ) ,
} )
if err != nil {
2024-11-05 15:42:59 +01:00
return "" , err
2023-05-31 20:46:23 +02:00
}
2023-10-12 20:26:26 -04:00
if len ( networks ) == 0 {
2023-10-20 00:18:35 -04:00
// in this instance, n.Name is really an ID
2024-06-07 17:57:54 +02:00
sn , err := s . apiClient ( ) . NetworkInspect ( ctx , n . Name , network . InspectOptions { } )
2025-05-27 14:27:45 +02:00
if err == nil {
networks = append ( networks , sn )
2025-05-29 10:37:19 +02:00
} else if ! cerrdefs . IsNotFound ( err ) {
2024-11-05 15:42:59 +01:00
return "" , err
2023-10-12 20:26:26 -04:00
}
2025-05-27 14:27:45 +02:00
2023-10-12 20:26:26 -04:00
}
2023-05-31 20:46:23 +02:00
// NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
2025-05-27 14:27:45 +02:00
networks = slices . DeleteFunc ( networks , func ( net network . Inspect ) bool {
2023-10-12 20:26:26 -04:00
// this function is called during the rebuild stage of `compose watch`.
// we still require just one network back, but we need to run the search on the ID
2025-05-27 14:27:45 +02:00
return net . Name != n . Name && net . ID != n . Name
2023-05-31 20:46:23 +02:00
} )
2020-12-08 11:53:36 +01:00
2023-05-31 20:46:23 +02:00
switch len ( networks ) {
case 1 :
2024-11-05 15:42:59 +01:00
return networks [ 0 ] . ID , nil
2023-05-31 20:46:23 +02:00
case 0 :
2024-01-25 15:56:31 +01:00
enabled , err := s . isSWarmEnabled ( ctx )
if err != nil {
2024-11-05 15:42:59 +01:00
return "" , err
2024-01-25 15:56:31 +01:00
}
if enabled {
2023-05-31 20:46:23 +02:00
// Swarm nodes do not register overlay networks that were
// created on a different node unless they're in use.
2024-01-25 15:56:31 +01:00
// So we can't preemptively check network exists, but
2024-09-08 07:01:35 +10:00
// networkAttach will later fail anyway if network actually doesn't exist
2024-11-05 15:42:59 +01:00
return "swarm" , nil
2022-06-01 15:28:42 -04:00
}
2024-11-05 15:42:59 +01:00
return "" , fmt . Errorf ( "network %s declared as external, but could not be found" , n . Name )
2023-05-31 20:46:23 +02:00
default :
2024-11-05 15:42:59 +01:00
return "" , fmt . Errorf ( "multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity" , n . Name )
2020-12-08 11:53:36 +01:00
}
}
2024-12-12 09:36:24 +01:00
func ( s * composeService ) ensureVolume ( ctx context . Context , name string , volume types . VolumeConfig , project * types . Project , assumeYes bool ) ( string , error ) {
2022-02-23 11:28:56 +01:00
inspected , err := s . apiClient ( ) . VolumeInspect ( ctx , volume . Name )
2020-12-08 11:53:36 +01:00
if err != nil {
2025-05-29 10:37:19 +02:00
if ! cerrdefs . IsNotFound ( err ) {
2024-10-21 12:01:50 +01:00
return "" , err
2020-12-08 11:53:36 +01:00
}
2023-11-08 10:19:24 +01:00
if volume . External {
2024-10-21 12:01:50 +01:00
return "" , fmt . Errorf ( "external volume %q not found" , volume . Name )
2021-11-27 16:34:42 +01:00
}
2024-10-21 12:01:50 +01:00
err = s . createVolume ( ctx , volume )
2024-12-12 09:36:24 +01:00
return volume . Name , err
2021-11-19 08:30:33 +01:00
}
2023-11-08 10:19:24 +01:00
if volume . External {
2024-10-21 12:01:50 +01:00
return volume . Name , nil
2021-11-27 16:34:42 +01:00
}
2021-12-02 02:24:02 +01:00
// Volume exists with name, but let's double-check this is the expected one
2021-11-19 08:30:33 +01:00
p , ok := inspected . Labels [ api . ProjectLabel ]
if ! ok {
2021-12-02 02:24:02 +01:00
logrus . Warnf ( "volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume" , volume . Name )
2021-11-19 08:30:33 +01:00
}
2024-12-12 09:36:24 +01:00
if ok && p != project . Name {
logrus . Warnf ( "volume %q already exists but was created for project %q (expected %q). Use `external: true` to use an existing volume" , volume . Name , p , project . Name )
2021-11-19 08:30:33 +01:00
}
2024-10-21 12:01:50 +01:00
expected , err := VolumeHash ( volume )
if err != nil {
return "" , err
}
actual , ok := inspected . Labels [ api . ConfigHashLabel ]
if ok && actual != expected {
2024-12-10 10:30:37 +01:00
confirm := assumeYes
2024-12-12 09:36:24 +01:00
if ! assumeYes {
msg := fmt . Sprintf ( "Volume %q exists but doesn't match configuration in compose file. Recreate (data will be lost)?" , volume . Name )
confirm , err = prompt . NewPrompt ( s . stdin ( ) , s . stdout ( ) ) . Confirm ( msg , false )
if err != nil {
return "" , err
}
}
if confirm {
err = s . removeDivergedVolume ( ctx , name , volume , project )
if err != nil {
return "" , err
}
return volume . Name , s . createVolume ( ctx , volume )
}
2024-10-21 12:01:50 +01:00
}
return inspected . Name , nil
2021-11-19 08:30:33 +01:00
}
2024-12-12 09:36:24 +01:00
func ( s * composeService ) removeDivergedVolume ( ctx context . Context , name string , volume types . VolumeConfig , project * types . Project ) error {
// Remove services mounting divergent volume
var services [ ] string
for _ , service := range project . Services . Filter ( func ( config types . ServiceConfig ) bool {
for _ , cfg := range config . Volumes {
if cfg . Source == name {
return true
}
}
return false
} ) {
services = append ( services , service . Name )
}
err := s . stop ( ctx , project . Name , api . StopOptions {
Services : services ,
Project : project ,
} )
if err != nil {
return err
}
containers , err := s . getContainers ( ctx , project . Name , oneOffExclude , true , services ... )
if err != nil {
return err
}
// FIXME (ndeloof) we have to remove container so we can recreate volume
// but doing so we can't inherit anonymous volumes from previous instance
err = s . remove ( ctx , containers , api . RemoveOptions {
Services : services ,
Project : project ,
} )
if err != nil {
return err
}
return s . apiClient ( ) . VolumeRemove ( ctx , volume . Name , true )
}
2021-11-19 08:30:33 +01:00
func ( s * composeService ) createVolume ( ctx context . Context , volume types . VolumeConfig ) error {
eventName := fmt . Sprintf ( "Volume %q" , volume . Name )
w := progress . ContextWriter ( ctx )
w . Event ( progress . CreatingEvent ( eventName ) )
2024-12-12 09:36:24 +01:00
hash , err := VolumeHash ( volume )
if err != nil {
return err
}
volume . CustomLabels . Add ( api . ConfigHashLabel , hash )
_ , err = s . apiClient ( ) . VolumeCreate ( ctx , volumetypes . CreateOptions {
Labels : mergeLabels ( volume . Labels , volume . CustomLabels ) ,
2021-11-19 08:30:33 +01:00
Name : volume . Name ,
Driver : volume . Driver ,
DriverOpts : volume . DriverOpts ,
} )
if err != nil {
w . Event ( progress . ErrorEvent ( eventName ) )
return err
2020-12-08 11:53:36 +01:00
}
2021-11-19 08:30:33 +01:00
w . Event ( progress . CreatedEvent ( eventName ) )
2020-12-08 11:53:36 +01:00
return nil
}