Skip to content

Commit

Permalink
Merge pull request #1488 from dnephin/config_from_stdin
Browse files Browse the repository at this point in the history
Support reading config from stdin
  • Loading branch information
aanand committed Jul 3, 2015
2 parents a6b9982 + ae96fc0 commit db7e512
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 147 deletions.
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

0 comments on commit db7e512

Please sign in to comment.