Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement #745: Ability to set default project name in YML #3118

Closed
wants to merge 1 commit into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions compose/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ def get_client(verbose=False, version=None, tls_config=None, host=None):
def get_project(project_dir, config_path=None, project_name=None, verbose=False,
host=None, tls_config=None):
config_details = config.find(project_dir, config_path)
project_name = get_project_name(config_details.working_dir, project_name)
config_data = config.load(config_details)
default_project_name = config_data.project.get('default_name', None)
project_name = get_project_name(config_details.working_dir, project_name, default_project_name)

api_version = os.environ.get(
'COMPOSE_API_VERSION',
Expand All @@ -69,11 +70,11 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False,
return Project.from_config(project_name, config_data, client)


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

project_name = project_name or os.environ.get('COMPOSE_PROJECT_NAME')
project_name = project_name or os.environ.get('COMPOSE_PROJECT_NAME') or default_project_name
if project_name:
return normalize_name(project_name)

Expand Down
29 changes: 28 additions & 1 deletion compose/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ def version(self):

return version

def get_project_option(self, name):
return self.get_project_options_dict()[name]

def get_project_options_dict(self):
return {} if self.version == V1 else self.config.get('project', {})

def get_service(self, name):
return self.get_service_dicts()[name]

Expand All @@ -192,6 +198,25 @@ class Config(namedtuple('_Config', 'version services volumes networks')):
:type networks: :class:`dict`
"""

@classmethod
def with_project_info(cls, version, project, services, volumes, networks):
"""
:param version: configuration version
:type version: int
:param project: Dictionary mapping project specific options
:type project: :class:`dict`
:param services: List of service description dictionaries
:type services: :class:`list`
:param volumes: Dictionary mapping volume names to description dictionaries
:type volumes: :class:`dict`
:param networks: Dictionary mapping network names to description dictionaries
:type networks: :class:`dict`
"""
config = cls(version, services, volumes, networks)
config.project = project

return config


class ServiceConfig(namedtuple('_ServiceConfig', 'working_dir filename name config')):

Expand Down Expand Up @@ -306,11 +331,13 @@ def load(config_details):
main_file,
[file.get_service_dicts() for file in config_details.config_files])

project_options = main_file.get_project_options_dict()

if main_file.version != V1:
for service_dict in service_dicts:
match_named_volumes(service_dict, volumes)

return Config(main_file.version, service_dicts, volumes, networks)
return Config.with_project_info(main_file.version, project_options, service_dicts, volumes, networks)


def load_mapping(config_files, get_func, entity_type):
Expand Down
9 changes: 9 additions & 0 deletions compose/config/config_schema_v2.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
"type": "string"
},

"project": {
"id": "#/properties/project",
"type": "object",
"properties": {
"default_name": {"type": "string"}
},
"additionalProperties": false
},

"services": {
"id": "#/properties/services",
"type": "object",
Expand Down
2 changes: 2 additions & 0 deletions compose/config/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ def serialize_config(config):
'services': {service.pop('name'): service for service in config.services},
'networks': config.networks,
'volumes': config.volumes,
'project': config.project
}

return yaml.safe_dump(
output,
default_flow_style=False,
Expand Down
23 changes: 18 additions & 5 deletions docs/compose-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ You can use environment variables in configuration values with a Bash-like
`${VARIABLE}` syntax - see [variable substitution](#variable-substitution) for
full details.


## Service configuration reference

> **Note:** There are two versions of the Compose file format – version 1 (the
> legacy format, which does not support volumes or networks) and version 2 (the
> legacy format, which does not support project, volumes or networks) and version 2 (the
> most up-to-date). For more information, see the [Versioning](#versioning)
> section.

Expand Down Expand Up @@ -892,6 +891,20 @@ refer to it within the Compose file:
external:
name: actual-name-of-network

## Project configuration reference

> **Note:** There are two versions of the Compose file format – version 1 (the
> legacy format, which does not support project, volumes or networks) and version 2 (the
> most up-to-date). For more information, see the [Versioning](#versioning)
> section.

This section contains a list of all configuration options supported by a project
definition.

### default_name

This options allows you define project default name. If it not defined,
basename of the project directory will be used as project default name.

## Versioning

Expand Down Expand Up @@ -945,7 +958,6 @@ Example:
redis:
image: redis


### Version 2

Compose files using the version 2 syntax must indicate the version number at
Expand All @@ -972,9 +984,11 @@ Simple example:
redis:
image: redis

A more extended example, defining volumes and networks:
A more extended example, defining project, volumes and networks:

version: '2'
project:
default_name: "myapp"
services:
web:
build: .
Expand All @@ -1000,7 +1014,6 @@ A more extended example, defining volumes and networks:
back-tier:
driver: bridge


### Upgrading

In the majority of cases, moving from version 1 to 2 is a very simple process:
Expand Down
5 changes: 5 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ set a custom project name using the [`-p` command line
option](./reference/overview.md) or the [`COMPOSE_PROJECT_NAME`
environment variable](./reference/envvars.md#compose-project-name).

If no custom project name provided, docker-compose will use default project name.
You can provide it in [your docker-compose.yml](./compose-file.md#default_name).
If it doesn't specified, then the basename of the project directory will be used
as default project name.

## What's the difference between `up`, `run`, and `start`?

Typically, you want `docker-compose up`. Use `up` to start or restart all the
Expand Down
5 changes: 3 additions & 2 deletions docs/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ identical to the container name.
> **Note:** Your app's network is given a name based on the "project name",
> which is based on the name of the directory it lives in. You can override the
> project name with either the [`--project-name`
> flag](reference/overview.md) or the [`COMPOSE_PROJECT_NAME` environment
> variable](reference/envvars.md#compose-project-name).
> flag](reference/overview.md), the [`COMPOSE_PROJECT_NAME` environment
> variable](reference/envvars.md#compose-project-name),
> or specify it in [docker-compose.yml](./compose-file.md#project-name).

For example, suppose your app is in a directory called `myapp`, and your `docker-compose.yml` looks like this:

Expand Down
7 changes: 4 additions & 3 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ Compose uses a project name to isolate environments from each other. You can mak
* on a shared host or dev host, to prevent different projects, which may use the
same service names, from interfering with each other

The default project name is the basename of the project directory. You can set
a custom project name by using the
[`-p` command line option](./reference/overview.md) or the
You can provide the default project name in [your docker-compose.yml](./compose-file.md#default_name).
If it doesn't specified, then the basename of the project directory will be used
as default project name. You can set a custom project name by using the
[`-p` command line option](./reference/overview.md) or
[`COMPOSE_PROJECT_NAME` environment variable](./reference/envvars.md#compose-project-name).

### Preserve volume data when containers are created
Expand Down
3 changes: 3 additions & 0 deletions tests/acceptance/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ def test_config_default(self):
output = yaml.load(result.stdout)
expected = {
'version': '2.0',
'project': {
'default_name': 'v2full'
},
'volumes': {'data': {'driver': 'local'}},
'networks': {'front': {}},
'services': {
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/v2-full/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

version: "2"

project:
default_name: v2full

volumes:
data:
driver: local
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ def test_project_name_from_environment_new_var(self):
project_name = get_project_name(None)
self.assertEquals(project_name, name)

def test_project_name_from_environment_new_var_over_config(self):
name = 'namefromenv'
with mock.patch.dict(os.environ):
os.environ['COMPOSE_PROJECT_NAME'] = name
project_name = get_project_name(None, None, 'name-from-config')
self.assertEquals(project_name, name)

def test_project_name_from_config(self):
default_project_name = 'defaultname'
project_name = get_project_name(None, project_name=None, default_project_name='default name')
self.assertEquals(project_name, default_project_name)

def test_project_name_with_empty_environment_var(self):
base_dir = 'tests/fixtures/simple-composefile'
with mock.patch.dict(os.environ):
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ def test_load_v2(self):
config_data = config.load(
build_config_details({
'version': '2',
'project': {
'default_name': 'example'
},
'services': {
'foo': {'image': 'busybox'},
'bar': {'image': 'busybox', 'environment': ['FOO=1']},
Expand Down