diff --git a/runbot/container.py b/runbot/container.py
index 2fe340d9d..4c70fe730 100644
--- a/runbot/container.py
+++ b/runbot/container.py
@@ -17,6 +17,7 @@
import re
import subprocess
import time
+import yaml
from odoo.tools import file_path
@@ -235,7 +236,7 @@ def docker_run(*args, **kwargs):
return _docker_run(*args, **kwargs)
-def _docker_run(cmd=False, log_path=False, build_dir=False, container_name=False, image_tag=False, exposed_ports=None, cpu_limit=None, cpu_period=100000, cpus=0, memory=None, preexec_fn=None, ro_volumes=None, env_variables=None, network_enabled=False):
+def _docker_run(cmd=False, log_path=False, build_dir=False, container_name=False, image_tag=False, docker_compose_content=None, exposed_ports=None, cpu_limit=None, cpus=0, memory=None, preexec_fn=None, ro_volumes=None, env_variables=None, network_enabled=False):
"""Run tests in a docker container
:param run_cmd: command string to run in container
:param log_path: path to the logfile that will contain odoo stdout and stderr
@@ -244,8 +245,7 @@ def _docker_run(cmd=False, log_path=False, build_dir=False, container_name=False
:param container_name: used to give a name to the container for later reference
:param image_tag: Docker image tag name to select which docker image to use
:param exposed_ports: if not None, starting at 8069, ports will be exposed as exposed_ports numbers
- :param cpu_period: Specify the CPU CFS scheduler period, which is used alongside cpu_quota
- :param cpus: used to compute cpu_quota = cpu_period * cpus (equivalent of --cpus in docker CLI)
+ :param cpus: used to compute cpu_quota = 100000 * cpus (equivalent of --cpus in docker CLI)
:param memory: memory limit in bytes for the container
:params ro_volumes: dict of dest:source volumes to mount readonly in builddir
:params env_variables: list of environment variables
@@ -273,7 +273,6 @@ def _docker_run(cmd=False, log_path=False, build_dir=False, container_name=False
logs.write("Docker command:\n%s\n=================================================\n" % cmd_str)
# create start script
volumes = {
- '/var/run/postgresql': {'bind': '/var/run/postgresql', 'mode': 'rw'},
f'{build_dir}': {'bind': '/data/build', 'mode': 'rw'},
f'{log_path}': {'bind': '/data/buildlogs.txt', 'mode': 'rw'}
}
@@ -294,25 +293,69 @@ def _docker_run(cmd=False, log_path=False, build_dir=False, container_name=False
ulimits.append(docker.types.Ulimit(name='cpu', soft=cpu_limit, hard=cpu_limit))
docker_client = docker.from_env()
- container = docker_client.containers.run(
- image_tag,
- name=container_name,
- volumes=volumes,
- shm_size='128m',
- mem_limit=memory,
- ports=ports,
- ulimits=ulimits,
- cpu_period=cpu_period,
- cpu_quota=int(cpus * cpu_period ) if cpus else None,
- environment=env_variables,
- init=True,
- command=['/bin/bash', '-c',
- f'exec &>> /data/buildlogs.txt ;{run_cmd}'],
- auto_remove=True,
- detach=True,
- user=USERNAME,
- network_mode=None if network_enabled else 'none'
- )
+ if docker_compose_content:
+ docker_compose = yaml.safe_load(docker_compose_content)
+ for service_name, service in docker_compose['services'].items():
+ service["restart"] = "no"
+ if service_name == 'main':
+ service["container_name"] = container_name
+ else:
+ service["container_name"] = container_name + '-' + service_name
+ service = docker_compose['services']['main']
+ service['command'] = ['/bin/bash', '-c', f'exec &>> /data/buildlogs.txt ;{run_cmd}']
+ service['volumes'] = service.get('volumes', []) + [f'{source}:{volume["bind"]}:{volume["mode"]}'for source, volume in volumes.items()]
+ service["ports"] = [f"{hp}:{dp}/tcp" for dp, hp in enumerate(exposed_ports, start=8069)]
+ service["user"] = USERNAME
+ service["ulimits"] = {u["name"]: {"soft": u["soft"], "hard": u["hard"]} for u in ulimits}
+ service["environment"] = env_variables or {}
+ service["shm_size"] = '128m'
+ service["init"] = True
+ if not network_enabled:
+ service["network_mode"] = "none"
+
+ limits = {}
+ if memory:
+ limits["memory"] = memory
+ if cpus:
+ limits["cpus"] = str(cpus)
+ if limits:
+ service["deploy"] = {"resources": {"limits": limits}}
+
+ compose_path = os.path.join(build_dir, "docker-compose.yml")
+ with open(compose_path, 'w') as f:
+ yaml.dump(docker_compose, f, default_flow_style=False, sort_keys=False)
+ cmd = [
+ "docker", "compose",
+ "-f", compose_path,
+ "-p", container_name,
+ "up", "-d", "--remove-orphans",
+ ]
+ subprocess.run(cmd, check=True)
+ container = docker_client.containers.get(container_name)
+
+ else:
+
+ volumes['/var/run/postgresql'] = {'bind': '/var/run/postgresql', 'mode': 'rw'}
+ cpu_period = 100000
+ container = docker_client.containers.run(
+ image_tag,
+ name=container_name,
+ volumes=volumes,
+ shm_size='128m',
+ mem_limit=memory,
+ ports=ports,
+ ulimits=ulimits,
+ cpu_period=cpu_period,
+ cpu_quota=int(cpus * cpu_period) if cpus else None,
+ environment=env_variables,
+ init=True,
+ command=['/bin/bash', '-c',
+ f'exec &>> /data/buildlogs.txt ;{run_cmd}'],
+ auto_remove=True,
+ detach=True,
+ user=USERNAME,
+ network_mode=None if network_enabled else 'none'
+ )
if container.status not in ('running', 'created') :
_logger.error('Container %s started but status is not running or created: %s', container_name, container.status)
else:
@@ -324,6 +367,17 @@ def docker_stop(container_name, build_dir=None):
return _docker_stop(container_name, build_dir)
+def docker_compose_cleanup(container_name):
+ docker_client = docker.from_env()
+ for container in docker_client.containers.list(all=True, filters={'label': f'com.docker.compose.project={container_name}'}):
+ try:
+ container.stop(timeout=1)
+ container.remove(v=True)
+ except docker.errors.NotFound:
+ pass
+ except docker.errors.APIError as e:
+ _logger.warning('compose cleanup: %s on %s', e, container.name)
+
def _docker_stop(container_name, build_dir):
"""Stops the container named container_name"""
container_name = sanitize_container_name(container_name)
@@ -342,8 +396,10 @@ def _docker_stop(container_name, build_dir):
else:
_logger.info('Stopping docker without defined build_dir')
try:
+ docker_compose_cleanup(container_name)
container = docker_client.containers.get(container_name)
container.stop(timeout=1)
+ container.remove(v=True)
return
except docker.errors.NotFound:
_logger.error('Cannnot stop container %s. Container not found', container_name)
diff --git a/runbot/data/dockerfile_data.xml b/runbot/data/dockerfile_data.xml
index 720a70bf6..26989dbf5 100644
--- a/runbot/data/dockerfile_data.xml
+++ b/runbot/data/dockerfile_data.xml
@@ -201,6 +201,67 @@ ADD --chown={USERNAME} https://raw.githubusercontent.com/odoo/odoo/{odoo_branch}
RUN python3 -m pip install --no-cache-dir -r /tmp/requirements.txt
+
+ PostgresqlDockerDefault
+ True
+ Default Postgresql Dockerfile for latest Odoo versions.
+
+
+
+
+
+ raw
+ FROM postgres:18
+ FROM postgres:18
+
+
+
+
+
+ reference_layer
+ Install postgresql extensions
+ postgresql-18-pgvector
+
+
+
+
+
+
+ reference_layer
+ Create user for postgresql docker
+
+
+
+
+
+
+ raw
+ Create template
+ RUN printf '%s\n' \
+'#!/bin/bash' \
+'set -e' \
+'createdb -U "$POSTGRES_USER" -O odoo template_runbot' \
+'psql -U "$POSTGRES_USER" -d template_runbot <<-'"'"'SQL'"'"'' \
+' SET statement_timeout = 0;' \
+' SET lock_timeout = 0;' \
+' SET idle_in_transaction_session_timeout = 0;' \
+' SET client_encoding = '"'"'UTF8'"'"';' \
+' SET standard_conforming_strings = on;' \
+' SELECT pg_catalog.set_config('"'"'search_path'"'"', '"'"''"'"', false);' \
+' SET check_function_bodies = false;' \
+' SET xmloption = content;' \
+' SET client_min_messages = warning;' \
+' SET row_security = off;' \
+' CREATE EXTENSION IF NOT EXISTS fuzzystrmatch WITH SCHEMA public;' \
+' CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;' \
+' CREATE EXTENSION IF NOT EXISTS unaccent WITH SCHEMA public;' \
+' CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public;' \
+'SQL' \
+'psql -U "$POSTGRES_USER" -c "UPDATE pg_database SET datistemplate = true WHERE datname = '"'"'template_runbot'"'"';"' \
+< /docker-entrypoint-initdb.d/10-template_runbot.sh
+
+
+
Sync Identifiers
@@ -212,4 +273,31 @@ RUN python3 -m pip install --no-cache-dir -r /tmp/requirements.txt
+
+ Base odoo docker compose
+
+services:
+ postgres:
+ image: {postgres_image_tag}
+ environment:
+ POSTGRES_PASSWORD: odoo
+ POSTGRES_USER: odoo
+ POSTGRES_INITDB_ARGS: "--auth-local=peer --auth-host=scram-sha-256"
+ volumes:
+ - {build_dir}/pg_data:/var/lib/postgresql
+ - pg_socket:/var/run/postgresql
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U odoo -h /var/run/postgresql"]
+ interval: 2s
+ retries: 10
+
+ main:
+ image: {image_tag}
+ depends_on:
+ postgres:
+ condition: service_healthy
+ volumes:
+ - pg_socket:/var/run/postgresql
+
+
diff --git a/runbot/models/batch.py b/runbot/models/batch.py
index 5f03d4973..d9efaf21f 100644
--- a/runbot/models/batch.py
+++ b/runbot/models/batch.py
@@ -214,6 +214,7 @@ def _prepare(self, auto_rebase=False, use_base_commits=False):
_logger.error('No version found on bundle %s in project %s', bundle.name, project.name)
dockerfile_id = bundle.dockerfile_id or bundle.base_id.dockerfile_id or bundle.project_id.dockerfile_id or bundle.version_id.dockerfile_id
+ postgres_dockerfile_id = bundle.postgres_dockerfile_id or bundle.base_id.postgres_dockerfile_id or bundle.project_id.postgres_dockerfile_id or bundle.version_id.postgres_dockerfile_id
if not dockerfile_id:
_logger.error('No dockerfile found !')
triggers = self.env['runbot.trigger'].search([ # could be optimised for multiple batches. Ormcached method?
@@ -425,6 +426,7 @@ def _fill_missing(branch_commits, match_type):
'commit_link_ids': [(6, 0, commits_links)],
'modules': bundle.modules,
'dockerfile_id': dockerfile_id,
+ 'postgres_dockerfile_id': postgres_dockerfile_id,
'create_batch_id': self.id,
'used_custom_trigger': bool(trigger_custom.config_id or trigger_custom.extra_params or trigger_custom.config_data or trigger_custom.use_base_commits),
}
diff --git a/runbot/models/build.py b/runbot/models/build.py
index ba08d8ee0..54c2b9692 100644
--- a/runbot/models/build.py
+++ b/runbot/models/build.py
@@ -40,7 +40,7 @@
transactioncache,
DEFAULT_MAX_FILE_SIZE,
)
-from ..container import Command, docker_pull, docker_run, docker_state, docker_stop
+from ..container import Command, docker_pull, docker_run, docker_state, docker_stop, docker_compose_cleanup
from ..fields import JsonDictField
_logger = logging.getLogger(__name__)
@@ -90,6 +90,7 @@ class BuildParameters(models.Model):
create_batch_id = fields.Many2one('runbot.batch', index=True)
category = fields.Char('Category', index=True) # normal vs nightly vs weekly, ...
dockerfile_id = fields.Many2one('runbot.dockerfile', index=True, default=lambda self: self.env.ref('runbot.docker_default', raise_if_not_found=False))
+ postgres_dockerfile_id = fields.Many2one('runbot.dockerfile', index=True, default=lambda self: self.env.ref('runbot.postgresql_docker_default', raise_if_not_found=False))
skip_requirements = fields.Boolean('Skip requirements.txt auto install')
# other informations
extra_params = fields.Char('Extra cmd args')
@@ -162,6 +163,8 @@ def get_commit_links_ident(commit_link):
cleaned_vals['dynamic_config_position'] = param.dynamic_config_position
if param.dynamic_config.dict:
cleaned_vals['dynamic_config'] = param.dynamic_config.dict
+ if param.postgres_dockerfile_id:
+ cleaned_vals['postgres_dockerfile_id'] = param.postgres_dockerfile_id.id
param.fingerprint = hashlib.sha256(str(cleaned_vals).encode('utf8')).hexdigest()
@@ -909,6 +912,7 @@ def _schedule(self):
build._log('_schedule', 'Docker was likely killed, skipping%s' % details, level='ERROR')
if self.env['runbot.host']._fetch_local_logs(build_ids=build.ids):
return True # avoid to make results with remaining logs
+ docker_compose_cleanup(build._get_docker_name())
# No job running, make result and select next job
if build.docker_start:
docker_duration = int(time.time() - dt2time(build.docker_start))
@@ -1008,37 +1012,60 @@ def _run_job(self):
build._log("run", message, level='ERROR')
build._kill(result='ko')
- def _docker_run(self, step, cmd=None, ro_volumes=None, env_variables=None, **kwargs):
+ def _docker_run(self, step, cmd=None, ro_volumes=None, env_variables=None, image_tag=None, postgres_image_tag=None, use_docker_compose=False, **kwargs):
self.ensure_one()
_ro_volumes = ro_volumes or {}
ro_volumes = {}
for dest, source in _ro_volumes.items():
ro_volumes[f'/data/build/{dest}'] = source
- if 'image_tag' not in kwargs:
- kwargs.update({'image_tag': step.dockerfile_id.image_tag or self.params_id.dockerfile_id.image_tag})
+ if image_tag is None:
+ image_tag = step.dockerfile_id.image_tag or self.params_id.dockerfile_id.image_tag
+
+ if postgres_image_tag is None:
+ postgres_image_tag = step.postgres_dockerfile_id.image_tag or self.params_id.postgres_dockerfile_id.image_tag or self.params_id.version_id.postgres_dockerfile_id.image_tag
+
dockerfile_variant = self.params_id.config_data.get('dockerfile_variant', step.dockerfile_variant)
- if dockerfile_variant and f'.{dockerfile_variant.lower()}' not in kwargs['image_tag']:
- kwargs['image_tag'] += f'.{dockerfile_variant.lower()}'
+ if dockerfile_variant and f'.{dockerfile_variant.lower()}' not in image_tag:
+ image_tag += f'.{dockerfile_variant.lower()}'
if self.params_id.config_data.get('docker_use_future') and not kwargs['image_tag'].endswith('.future'):
- kwargs['image_tag'] += '.future'
+ image_tag += '.future'
+ if postgres_image_tag:
+ postgres_image_tag += '.future'
docker_registry_url = self.host_id._get_docker_registry_url()
image_id = None
if docker_registry_url and self.host_id.use_remote_docker_registry:
- try:
- result = docker_pull(f"{docker_registry_url}/{kwargs['image_tag']}")
- if result['success']:
- result['image'].tag(kwargs['image_tag'])
- if result.get('log_progress'):
- self._log('Docker Run', f'Docker image was pulled {"" if result["success"] else "with errors"}')
- image_id = result.get('image_id')
- except Exception:
- _logger.exception('Failed to pull docker image %s', kwargs['image_tag'])
- self._log('Docker Run', 'Failed to pull docker image')
-
- self._log('Preparing', 'Using Dockerfile Tag [%s](/runbot/dockerfile_result/%s/%s)', kwargs['image_tag'], kwargs['image_tag'], image_id, log_type='markdown')
+ for pull_image_tag in (postgres_image_tag, image_tag):
+ try:
+ result = docker_pull(f"{docker_registry_url}/{pull_image_tag}")
+ if result['success']:
+ result['image'].tag(pull_image_tag)
+ if result.get('log_progress'):
+ self._log('Docker Run', f'Docker image was pulled {"" if result["success"] else "with errors"}')
+ image_id = result.get('image_id')
+ except Exception:
+ _logger.exception('Failed to pull docker image %s', pull_image_tag)
+ self._log('Docker Run', 'Failed to pull docker image')
+
+ self._log('Preparing', 'Using Dockerfile Tag [%s](/runbot/dockerfile_result/%s/%s)', pull_image_tag, pull_image_tag, image_id, log_type='markdown')
# network is disabled by default, can be enabled via kwargs['network_enabled'] (run, restore) or config_data['network_enabled'] (external, nightly,...)
kwargs['network_enabled'] = kwargs.get('network_enabled') or self.params_id.config_data.get('network_enabled') or self.params_id.trigger_id.network_enabled or False
+ use_docker_compose = use_docker_compose or self.params_id.config_data.get('use_docker_compose') or self.params_id.trigger_id.use_docker_compose or False
+ docker_compose_content = None
+ build_dir = self._path()
+ if use_docker_compose:
+ docker_compose_id = self.params_id.config_data.get('docker_compose_id') or step.docker_compose_id.id or self.params_id.project_id.docker_compose_id.id
+ docker_compose = self.env['runbot.docker_compose'].browse(docker_compose_id)
+ if docker_compose:
+ docker_compose_content = docker_compose._render({
+ 'image_tag': image_tag,
+ 'postgres_image_tag': postgres_image_tag,
+ 'build_dir': build_dir,
+ })
+ if docker_compose_content:
+ kwargs['docker_compose_content'] = docker_compose_content
+ else:
+ kwargs['image_tag'] = image_tag
containers_memory_limit = self.env['ir.config_parameter'].sudo().get_param('runbot.runbot_containers_memory', 0)
if containers_memory_limit and 'memory' not in kwargs:
@@ -1065,7 +1092,6 @@ def _docker_run(self, step, cmd=None, ro_volumes=None, env_variables=None, **kwa
kwargs.pop('log_path', False)
kwargs.pop('container_name', False)
log_path = self._path('logs', '%s.txt' % step.sanitized_name(self))
- build_dir = self._path()
container_name = self._get_docker_name()
self.env.flush_all()
env_variables = env_variables or []
diff --git a/runbot/models/build_config.py b/runbot/models/build_config.py
index 2d0a70f3c..23299a6ae 100644
--- a/runbot/models/build_config.py
+++ b/runbot/models/build_config.py
@@ -426,6 +426,8 @@ class ConfigStep(models.Model):
make_stats = fields.Boolean('Make stats', default=False)
build_stat_regex_ids = fields.Many2many('runbot.build.stat.regex', string='Stats Regexes')
dockerfile_id = fields.Many2one('runbot.dockerfile', string='Dockerfile')
+ postgres_dockerfile_id = fields.Many2one('runbot.dockerfile', string='PostgreSQL Dockerfile')
+ docker_compose_id = fields.Many2one('runbot.docker_compose', string='Docker Compose configuration')
dockerfile_variant = fields.Char('Docker Variant')
# install_odoo
create_db = fields.Boolean('Create Db', default=True, tracking=True) # future
@@ -1047,6 +1049,7 @@ def get_reference_builds_for_versions(versions):
'version_id': target.params_id.version_id.id,
'trigger_id': None,
'dockerfile_id': target.params_id.dockerfile_id.id,
+ 'postgres_dockerfile_id': target.params_id.postgres_dockerfile_id.id,
})
source_description = source.params_id.version_id.name
target_description = target.params_id.version_id.name
@@ -1111,7 +1114,7 @@ def _run_test_upgrade(self, build):
env_variables.append(exception_env)
if config_env_variables := build.params_id.config_data.get('env_variables', False):
env_variables += config_env_variables.split(';')
- return dict(cmd=migrate_cmd, ro_volumes=exports, env_variables=env_variables, image_tag=build.params_id.dockerfile_id.image_tag)
+ return dict(cmd=migrate_cmd, ro_volumes=exports, env_variables=env_variables)
def _run_restore(self, build, config_data=None):
# exports = build._checkout()
diff --git a/runbot/models/bundle.py b/runbot/models/bundle.py
index 29d010232..40cd5f8d7 100644
--- a/runbot/models/bundle.py
+++ b/runbot/models/bundle.py
@@ -53,6 +53,7 @@ class Bundle(models.Model):
all_trigger_custom_ids = fields.Many2many('runbot.bundle.trigger.custom', compute='_compute_all_trigger_custom_ids', recursive=True)
host_id = fields.Many2one('runbot.host', compute="_compute_host_id", store=True)
dockerfile_id = fields.Many2one('runbot.dockerfile', index=True, help="Use a custom Dockerfile")
+ postgres_dockerfile_id = fields.Many2one('runbot.dockerfile', index=True, help="Use a custom PostgreSQL Dockerfile")
commit_limit = fields.Integer("Commit limit")
file_limit = fields.Integer("File limit")
disable_codeowner = fields.Boolean("Disable codeowners", tracking=True)
diff --git a/runbot/models/docker.py b/runbot/models/docker.py
index d7115e3d5..131f7d3b3 100644
--- a/runbot/models/docker.py
+++ b/runbot/models/docker.py
@@ -46,7 +46,6 @@ class DockerLayer(models.Model):
reference_count = fields.Integer('Number of references', compute='_compute_references')
has_xml_id = fields.Boolean(compute='_compute_has_xml_id')
-
@api.depends('referencing_dockerlayer_ids', 'dockerfile_id.referencing_dockerlayer_ids')
def _compute_references(self):
for record in self:
@@ -476,3 +475,17 @@ def _getdocker_metadata_diff(self, other_build_result_id):
if negativ_diff or positiv_diff:
diff_dict[k] = [f'- {s}' for s in negativ_diff] + [f'+ {s}' for s in positiv_diff]
return diff_dict
+
+
+class DockerCompose(models.Model):
+ _name = 'runbot.docker_compose'
+ _description = "Docker compose file"
+
+ name = fields.Char('Name', required=True)
+ content = fields.Text('Content', required=True)
+
+ def _render(self, values):
+ rendered = self.content
+ for key, value in values.items():
+ rendered = rendered.replace('{%s}' % key, str(value))
+ return rendered
diff --git a/runbot/models/project.py b/runbot/models/project.py
index 7032754c4..2d17b5522 100644
--- a/runbot/models/project.py
+++ b/runbot/models/project.py
@@ -12,6 +12,8 @@ class Project(models.Model):
keep_sticky_running = fields.Boolean('Keep last sticky builds running')
trigger_ids = fields.One2many('runbot.trigger', 'project_id', string='Triggers')
dockerfile_id = fields.Many2one('runbot.dockerfile', index=True, help="Project Default Dockerfile")
+ postgres_dockerfile_id = fields.Many2one('runbot.dockerfile', index=True, help="Project Default Postgres Dockerfile")
+ docker_compose_id = fields.Many2one('runbot.docker_compose', index=True, help="Project Default Docker Compose configuration")
repo_ids = fields.One2many('runbot.repo', 'project_id', string='Repos')
sequence = fields.Integer('Sequence')
organisation = fields.Char('organisation', default=lambda self: self.env['ir.config_parameter'].sudo().get_param('runbot.runbot_organisation'))
diff --git a/runbot/models/repo.py b/runbot/models/repo.py
index 32c12acb6..71e9ba1bd 100644
--- a/runbot/models/repo.py
+++ b/runbot/models/repo.py
@@ -77,6 +77,7 @@ class Trigger(models.Model):
light_config_id = fields.Many2one('runbot.build.config', string="Light config", help="Alternative config to use when light mode is enabled")
config_data = JsonDictField('Config Data')
network_enabled = fields.Boolean('Network Enabled')
+ use_docker_compose = fields.Boolean('Use docker compose', help="Use docker compose to run the build, allowing to easily run multiple containers and use a custom network. Note that it will disable log streaming and automatic resource cleanup.")
batch_dependent = fields.Boolean('Batch Dependent', help="Force adding batch in build parameters to make it unique and give access to bundle")
version_dependent = fields.Boolean('Version Dependent', default=True, help="Add the version in build parameters. Uncheck if the version is not needed to determine the build result")
diff --git a/runbot/models/version.py b/runbot/models/version.py
index 7e4e44a1b..18f0e692a 100644
--- a/runbot/models/version.py
+++ b/runbot/models/version.py
@@ -25,6 +25,7 @@ class Version(models.Model):
next_intermediate_version_ids = fields.Many2many('runbot.version', compute='_compute_version_relations')
dockerfile_id = fields.Many2one('runbot.dockerfile', default=lambda self: self.env['runbot.version'].search([('name', '=', 'master')], limit=1).dockerfile_id or self.env.ref('runbot.docker_default', raise_if_not_found=False))
+ postgres_dockerfile_id = fields.Many2one('runbot.dockerfile', default=lambda self: self.env['runbot.version'].search([('name', '=', 'master')], limit=1).postgres_dockerfile_id or self.env.ref('runbot.postgresql_docker_default', raise_if_not_found=False))
_unique_name = models.Constraint(
'unique (name)',
diff --git a/runbot/security/ir.model.access.csv b/runbot/security/ir.model.access.csv
index f0ca84182..6404d186d 100644
--- a/runbot/security/ir.model.access.csv
+++ b/runbot/security/ir.model.access.csv
@@ -160,6 +160,11 @@ access_runbot_docker_layer_admin,access_runbot_docker_layer_admin,runbot.model_r
access_runbot_docker_build_result_public,access_runbot_docker_build_result_public,runbot.model_runbot_docker_build_result,runbot.base_runbot_model_access,1,0,0,0
access_runbot_docker_build_result_admin,access_runbot_docker_build_result_admin,runbot.model_runbot_docker_build_result,runbot.group_runbot_admin,1,1,1,1
+
+access_runbot_docker_compose_user,access_runbot_docker_compose_user,runbot.model_runbot_docker_compose,runbot.group_user,1,0,0,0
+access_runbot_docker_compose_admin,access_runbot_docker_compose_admin,runbot.model_runbot_docker_compose,runbot.group_runbot_admin,1,1,1,1
+
+
access_runbot_codeowner_admin,runbot_codeowner_admin,runbot.model_runbot_codeowner,runbot.group_runbot_admin,1,1,1,1
access_runbot_codeownermanager,runbot_codeowner_manager,runbot.model_runbot_codeowner,runbot.group_runbot_codeowner_manager,1,1,1,1
access_runbot_codeowner_user,runbot_codeowner_user,runbot.model_runbot_codeowner,group_user,1,0,0,0
diff --git a/runbot/security/runbot_security.xml b/runbot/security/runbot_security.xml
index 390b4940c..78fd5257e 100644
--- a/runbot/security/runbot_security.xml
+++ b/runbot/security/runbot_security.xml
@@ -112,7 +112,6 @@
-
Advanced runbot user
diff --git a/runbot/templates/versions.xml b/runbot/templates/versions.xml
index 4563fa229..de6bb82b2 100644
--- a/runbot/templates/versions.xml
+++ b/runbot/templates/versions.xml
@@ -9,6 +9,7 @@
| Version |
Docker file |
+ Postgres Docker file |
Host |
|
@@ -17,9 +18,10 @@
|
+ |
+ |
- |
|
@@ -34,9 +36,6 @@
|
-
-
- |
diff --git a/runbot/views/build_views.xml b/runbot/views/build_views.xml
index 71a1b49e5..ca87ebce7 100644
--- a/runbot/views/build_views.xml
+++ b/runbot/views/build_views.xml
@@ -11,6 +11,7 @@
+
diff --git a/runbot/views/bundle_views.xml b/runbot/views/bundle_views.xml
index a8b4c8aae..47bb49436 100644
--- a/runbot/views/bundle_views.xml
+++ b/runbot/views/bundle_views.xml
@@ -10,6 +10,8 @@
+
+
@@ -33,6 +35,8 @@
+
+
@@ -78,6 +82,7 @@
+
@@ -233,6 +238,7 @@
+
diff --git a/runbot/views/config_views.xml b/runbot/views/config_views.xml
index 11d52ac7c..3f88bc7e1 100644
--- a/runbot/views/config_views.xml
+++ b/runbot/views/config_views.xml
@@ -48,6 +48,7 @@
+
diff --git a/runbot/views/dockerfile_views.xml b/runbot/views/dockerfile_views.xml
index 27d45ac5e..47f79d61c 100644
--- a/runbot/views/dockerfile_views.xml
+++ b/runbot/views/dockerfile_views.xml
@@ -237,6 +237,31 @@
+
+ runbot.docker_compose.form
+ runbot.docker_compose
+
+
+
+
+
+
+ runbot.docker_compose.list
+ runbot.docker_compose
+
+
+
+
+
+
+
Duplicate With Variants
@@ -281,5 +306,15 @@
docker-layer
+
+
+ Docker Compose files
+ runbot.docker_compose
+ list,form
+ docker-compose
+
+
+
+