Skip to content
Merged
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
54 changes: 13 additions & 41 deletions compose/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,14 @@
from ..project import Project
from ..service import ConfigError
from .docopt_command import DocoptCommand
from .utils import call_silently, is_mac, is_ubuntu, find_candidates_in_parent_dirs
from .utils import call_silently, is_mac, is_ubuntu
from .docker_client import docker_client
from . import verbose_proxy
from . import errors
from .. import __version__

log = logging.getLogger(__name__)

SUPPORTED_FILENAMES = [
'docker-compose.yml',
'docker-compose.yaml',
'fig.yml',
'fig.yaml',
]


class Command(DocoptCommand):
base_dir = '.'
Expand Down Expand Up @@ -59,7 +52,7 @@ def perform_command(self, options, handler, command_options):

explicit_config_path = options.get('--file') or os.environ.get('COMPOSE_FILE') or os.environ.get('FIG_FILE')
project = self.get_project(
self.get_config_path(explicit_config_path),
explicit_config_path,
project_name=options.get('--project-name'),
verbose=options.get('--verbose'))

Expand All @@ -76,55 +69,34 @@ def get_client(self, verbose=False):
return verbose_proxy.VerboseProxy('docker', client)
return client

def get_project(self, config_path, project_name=None, verbose=False):
def get_project(self, config_path=None, project_name=None, verbose=False):
config_details = config.find(self.base_dir, config_path)

try:
return Project.from_dicts(
self.get_project_name(config_path, project_name),
config.load(config_path),
self.get_project_name(config_details.working_dir, project_name),
config.load(config_details),
self.get_client(verbose=verbose))
except ConfigError as e:
raise errors.UserError(six.text_type(e))

def get_project_name(self, config_path, project_name=None):
def get_project_name(self, working_dir, project_name=None):
def normalize_name(name):
return re.sub(r'[^a-z0-9]', '', name.lower())

if 'FIG_PROJECT_NAME' in os.environ:
log.warn('The FIG_PROJECT_NAME environment variable is deprecated.')
log.warn('Please use COMPOSE_PROJECT_NAME instead.')

project_name = project_name or os.environ.get('COMPOSE_PROJECT_NAME') or os.environ.get('FIG_PROJECT_NAME')
project_name = (
project_name or
os.environ.get('COMPOSE_PROJECT_NAME') or
os.environ.get('FIG_PROJECT_NAME'))
if project_name is not None:
return normalize_name(project_name)

project = os.path.basename(os.path.dirname(os.path.abspath(config_path)))
project = os.path.basename(os.path.abspath(working_dir))
if project:
return normalize_name(project)

return 'default'

def get_config_path(self, file_path=None):
if file_path:
return os.path.join(self.base_dir, file_path)

(candidates, path) = find_candidates_in_parent_dirs(SUPPORTED_FILENAMES, self.base_dir)

if len(candidates) == 0:
raise errors.ComposeFileNotFound(SUPPORTED_FILENAMES)

winner = candidates[0]

if len(candidates) > 1:
log.warning("Found multiple config files with supported names: %s", ", ".join(candidates))
log.warning("Using %s\n", winner)

if winner == 'docker-compose.yaml':
log.warning("Please be aware that .yml is the expected extension "
"in most cases, and using .yaml can cause compatibility "
"issues in future.\n")

if winner.startswith("fig."):
log.warning("%s is deprecated and will not be supported in future. "
"Please rename your config file to docker-compose.yml\n" % winner)

return os.path.join(path, winner)
9 changes: 0 additions & 9 deletions compose/cli/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,3 @@ def __init__(self, url):

If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.
""" % url)


class ComposeFileNotFound(UserError):
def __init__(self, supported_filenames):
super(ComposeFileNotFound, self).__init__("""
Can't find a suitable configuration file in this directory or any parent. Are you in the right directory?

Supported filenames: %s
""" % ", ".join(supported_filenames))
68 changes: 64 additions & 4 deletions compose/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import logging
import os
import sys
import yaml
from collections import namedtuple

import six

from compose.cli.utils import find_candidates_in_parent_dirs


DOCKER_CONFIG_KEYS = [
'cap_add',
Expand Down Expand Up @@ -64,12 +70,57 @@
}


def load(filename):
working_dir = os.path.dirname(filename)
return from_dictionary(load_yaml(filename), working_dir=working_dir, filename=filename)
SUPPORTED_FILENAMES = [
'docker-compose.yml',
'docker-compose.yaml',
'fig.yml',
'fig.yaml',
]


log = logging.getLogger(__name__)


ConfigDetails = namedtuple('ConfigDetails', 'config working_dir filename')


def find(base_dir, filename):
if filename == '-':
return ConfigDetails(yaml.safe_load(sys.stdin), os.getcwd(), None)

if filename:
filename = os.path.join(base_dir, filename)
else:
filename = get_config_path(base_dir)
return ConfigDetails(load_yaml(filename), os.path.dirname(filename), filename)


def get_config_path(base_dir):
(candidates, path) = find_candidates_in_parent_dirs(SUPPORTED_FILENAMES, base_dir)

if len(candidates) == 0:
raise ComposeFileNotFound(SUPPORTED_FILENAMES)

winner = candidates[0]

if len(candidates) > 1:
log.warn("Found multiple config files with supported names: %s", ", ".join(candidates))
log.warn("Using %s\n", winner)

if winner == 'docker-compose.yaml':
log.warn("Please be aware that .yml is the expected extension "
"in most cases, and using .yaml can cause compatibility "
"issues in future.\n")

if winner.startswith("fig."):
log.warn("%s is deprecated and will not be supported in future. "
"Please rename your config file to docker-compose.yml\n" % winner)

def from_dictionary(dictionary, working_dir=None, filename=None):
return os.path.join(path, winner)


def load(config_details):
dictionary, working_dir, filename = config_details
service_dicts = []

for service_name, service_dict in list(dictionary.items()):
Expand Down Expand Up @@ -488,3 +539,12 @@ def msg(self):
for (filename, service_name) in self.trail
]
return "Circular reference:\n {}".format("\n extends ".join(lines))


class ComposeFileNotFound(ConfigurationError):
def __init__(self, supported_filenames):
super(ComposeFileNotFound, self).__init__("""
Can't find a suitable configuration file in this directory or any parent. Are you in the right directory?

Supported filenames: %s
""" % ", ".join(supported_filenames))
3 changes: 3 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ By default, if there are existing containers for a service, `docker-compose up`
for `docker-compose.yml` in the current working directory, and then each parent
directory successively, until found.

Use a `-` as the filename to read configuration from stdin. When stdin is used
all paths in the configuration will be relative to the current working
directory.

### -p, --project-name NAME

Expand Down
2 changes: 1 addition & 1 deletion tests/integration/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def project(self):
if hasattr(self, '_project'):
return self._project

return self.command.get_project(self.command.get_config_path())
return self.command.get_project()

def test_help(self):
old_base_dir = self.command.base_dir
Expand Down
18 changes: 11 additions & 7 deletions tests/integration/project_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
from .testcases import DockerClientTestCase


def build_service_dicts(service_config):
return config.load(config.ConfigDetails(service_config, 'working_dir', None))


class ProjectTest(DockerClientTestCase):

def test_containers(self):
Expand All @@ -32,7 +36,7 @@ def test_containers_with_service_names(self):
['composetest_web_1'])

def test_volumes_from_service(self):
service_dicts = config.from_dictionary({
service_dicts = build_service_dicts({
'data': {
'image': 'busybox:latest',
'volumes': ['/var/data'],
Expand All @@ -41,7 +45,7 @@ def test_volumes_from_service(self):
'image': 'busybox:latest',
'volumes_from': ['data'],
},
}, working_dir='.')
})
project = Project.from_dicts(
name='composetest',
service_dicts=service_dicts,
Expand All @@ -61,7 +65,7 @@ def test_volumes_from_container(self):
)
project = Project.from_dicts(
name='composetest',
service_dicts=config.from_dictionary({
service_dicts=build_service_dicts({
'db': {
'image': 'busybox:latest',
'volumes_from': ['composetest_data_container'],
Expand All @@ -75,7 +79,7 @@ def test_volumes_from_container(self):
def test_net_from_service(self):
project = Project.from_dicts(
name='composetest',
service_dicts=config.from_dictionary({
service_dicts=build_service_dicts({
'net': {
'image': 'busybox:latest',
'command': ["top"]
Expand Down Expand Up @@ -107,7 +111,7 @@ def test_net_from_container(self):

project = Project.from_dicts(
name='composetest',
service_dicts=config.from_dictionary({
service_dicts=build_service_dicts({
'web': {
'image': 'busybox:latest',
'net': 'container:composetest_net_container'
Expand Down Expand Up @@ -274,7 +278,7 @@ def test_project_up_starts_links(self):
def test_project_up_starts_depends(self):
project = Project.from_dicts(
name='composetest',
service_dicts=config.from_dictionary({
service_dicts=build_service_dicts({
'console': {
'image': 'busybox:latest',
'command': ["top"],
Expand Down Expand Up @@ -309,7 +313,7 @@ def test_project_up_starts_depends(self):
def test_project_up_with_no_deps(self):
project = Project.from_dicts(
name='composetest',
service_dicts=config.from_dictionary({
service_dicts=build_service_dicts({
'console': {
'image': 'busybox:latest',
'command': ["top"],
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/state_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def make_project(self, cfg):
return Project.from_dicts(
name='composetest',
client=self.client,
service_dicts=config.from_dictionary(cfg),
service_dicts=config.load(config.ConfigDetails(cfg, 'working_dir', None))
)


Expand Down
Loading