Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 58 additions & 17 deletions pkg/compose/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ import (
cdi "tags.cncf.io/container-device-interface/pkg/parser"
)

const (
secretsDir = "/run/secrets/"
dockerConfigPathInContainer = secretsDir + "/docker/config.json"
)

type createOptions struct {
AutoRemove bool
AttachStdin bool
Expand Down Expand Up @@ -241,6 +246,12 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
env := proxyConfig.OverrideBy(service.Environment)

if hasAPISocket(service) {
// Inject the Docker API socket credentials path in the environment.
p := dockerConfigPathInContainer
env["DOCKER_CONFIG"] = &p
}

var mainNwName string
var mainNw *types.ServiceNetworkConfig
if len(service.Networks) > 0 {
Expand Down Expand Up @@ -371,6 +382,16 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
return cfgs, nil
}

func hasAPISocket(service types.ServiceConfig) bool {
for _, v := range service.Volumes {
if v.Type == "api-socket" {
return true
}
}

return false
}

// 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
Expand Down Expand Up @@ -1001,7 +1022,7 @@ func (s *composeService) buildContainerMountOptions(ctx context.Context, p types
}
}

mounts, err := fillBindMounts(p, service, mounts)
mounts, err := s.fillBindMounts(p, service, mounts)
if err != nil {
return nil, err
}
Expand All @@ -1013,16 +1034,16 @@ func (s *composeService) buildContainerMountOptions(ctx context.Context, p types
return values, nil
}

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)
func (s *composeService) fillBindMounts(p types.Project, service types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
for _, v := range service.Volumes {
bindMount, err := s.buildMount(p, v)
if err != nil {
return nil, err
}
m[bindMount.Target] = bindMount
}

secrets, err := buildContainerSecretMounts(p, s)
secrets, err := s.buildContainerSecretMounts(p, service)
if err != nil {
return nil, err
}
Expand All @@ -1033,7 +1054,7 @@ func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.M
m[s.Target] = s
}

configs, err := buildContainerConfigMounts(p, s)
configs, err := s.buildContainerConfigMounts(p, service)
if err != nil {
return nil, err
}
Expand All @@ -1046,11 +1067,11 @@ func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.M
return m, nil
}

func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
func (s *composeService) buildContainerConfigMounts(p types.Project, service types.ServiceConfig) ([]mount.Mount, error) {
mounts := map[string]mount.Mount{}

configsBaseDir := "/"
for _, config := range s.Configs {
for _, config := range service.Configs {
target := config.Target
if config.Target == "" {
target = configsBaseDir + config.Source
Expand Down Expand Up @@ -1078,7 +1099,7 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
logrus.Warn("config `uid`, `gid` and `mode` are not supported, they will be ignored")
}

bindMount, err := buildMount(p, types.ServiceVolumeConfig{
bindMount, err := s.buildMount(p, types.ServiceVolumeConfig{
Type: types.VolumeTypeBind,
Source: definedConfig.File,
Target: target,
Expand All @@ -1096,11 +1117,10 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
return values, nil
}

func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
func (s *composeService) buildContainerSecretMounts(p types.Project, service types.ServiceConfig) ([]mount.Mount, error) {
mounts := map[string]mount.Mount{}

secretsDir := "/run/secrets/"
for _, secret := range s.Secrets {
for _, secret := range service.Secrets {
target := secret.Target
if secret.Target == "" {
target = secretsDir + secret.Source
Expand Down Expand Up @@ -1132,7 +1152,7 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
logrus.Warnf("secret file %s does not exist", definedSecret.Name)
}

mnt, err := buildMount(p, types.ServiceVolumeConfig{
mnt, err := s.buildMount(p, types.ServiceVolumeConfig{
Type: types.VolumeTypeBind,
Source: definedSecret.File,
Target: target,
Expand Down Expand Up @@ -1171,7 +1191,7 @@ func isWindowsAbs(p string) bool {
return false
}

func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
func (s *composeService) buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
source := volume.Source
// on windows, filepath.IsAbs(source) is false for unix style abs path like /var/run/docker.sock.
// do not replace these with filepath.Abs(source) that will include a default drive.
Expand All @@ -1192,14 +1212,32 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
}
}

vtype := mount.Type(volume.Type)
if volume.Type == "api-socket" { // TODO: use VolumeTypeAPISocket when compose-go PR merged
vtype = mount.TypeBind // rewrite to a bind mount

if volume.Source == "" {
socketPath := s.dockerCli.DockerEndpoint().Host
if !strings.HasPrefix(socketPath, "unix://") {
return mount.Mount{}, fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socketPath)
}
socketPath = strings.TrimPrefix(socketPath, "unix://") // should we confirm absolute path?
volume.Source = socketPath
}

if volume.Target == "" {
volume.Target = "/var/run/docker.sock"
}
}

bind, vol, tmpfs, img := buildMountOptions(volume)

if bind != nil {
volume.Type = types.VolumeTypeBind
vtype = mount.TypeBind
}

return mount.Mount{
Type: mount.Type(volume.Type),
Type: vtype,
Source: source,
Target: volume.Target,
ReadOnly: volume.ReadOnly,
Expand All @@ -1212,7 +1250,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
}

func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions, *mount.ImageOptions) {
if volume.Type != types.VolumeTypeBind && volume.Bind != nil {
if volume.Type != types.VolumeTypeBind && volume.Type != "api-socket" && volume.Bind != nil { // TODO: use VolumeTypeAPISocket when compose-go PR merged
logrus.Warnf("mount of type `%s` should not define `bind` option", volume.Type)
}
if volume.Type != types.VolumeTypeVolume && volume.Volume != nil {
Expand All @@ -1228,6 +1266,8 @@ func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *m
switch volume.Type {
case "bind":
return buildBindOption(volume.Bind), nil, nil, nil
case "api-socket":
return nil, nil, nil, nil // defer to defaults when binding the API socket
case "volume":
return nil, buildVolumeOptions(volume.Volume), nil, nil
case "tmpfs":
Expand All @@ -1242,6 +1282,7 @@ func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
if bind == nil {
return nil
}

opts := &mount.BindOptions{
Propagation: mount.Propagation(bind.Propagation),
CreateMountpoint: bind.CreateHostPath,
Expand Down
32 changes: 30 additions & 2 deletions pkg/compose/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,38 @@ import (
"time"

"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/docker/api/types/container"
)

func (s *composeService) injectSecrets(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error {
if hasAPISocket(service) {
// Generate a "secret" for the authconfig for the API socket.
creds, err := s.dockerCli.ConfigFile().GetAllCredentials()
if err != nil {
return fmt.Errorf("resolving credentials failed: %w", err)
}

// Create a new config file with just the auth.
newConfig := &configfile.ConfigFile{
AuthConfigs: creds,
}
var configBuf bytes.Buffer
if err := newConfig.SaveToWriter(&configBuf); err != nil {
return fmt.Errorf("saving creds for API socket: %w", err)
}

mode := types.FileMode(0o400)
b, err := createTar(configBuf.String(), types.FileReferenceConfig{
Target: dockerConfigPathInContainer,
Mode: &mode,
})

if err = s.apiClient().CopyToContainer(ctx, id, "/", &b, container.CopyToContainerOptions{}); err != nil {
return fmt.Errorf("copying creds for API socket: %w", err)
}
}

for _, config := range service.Secrets {
file := project.Secrets[config.Source]
if file.Environment == "" {
Expand All @@ -40,9 +68,9 @@ func (s *composeService) injectSecrets(ctx context.Context, project *types.Proje
}

if config.Target == "" {
config.Target = "/run/secrets/" + config.Source
config.Target = secretsDir + config.Source
} else if !isAbsTarget(config.Target) {
config.Target = "/run/secrets/" + config.Target
config.Target = secretsDir + config.Target
}

content := file.Content
Expand Down
Loading