From 12706547831c20a80f5b0c45449e02a6e447fc32 Mon Sep 17 00:00:00 2001 From: Xavier-Do Date: Wed, 10 Jun 2026 14:38:37 +0200 Subject: [PATCH] wip careful to adapt pre upgrade test too use the correct postgres_dockerfile_id (the one of the build, not the one of the source) --- runbot/container.py | 102 +++++++++++++++++++++------- runbot/data/dockerfile_data.xml | 88 ++++++++++++++++++++++++ runbot/models/batch.py | 2 + runbot/models/build.py | 66 ++++++++++++------ runbot/models/build_config.py | 5 +- runbot/models/bundle.py | 1 + runbot/models/docker.py | 15 +++- runbot/models/project.py | 2 + runbot/models/repo.py | 1 + runbot/models/version.py | 1 + runbot/security/ir.model.access.csv | 5 ++ runbot/security/runbot_security.xml | 1 - runbot/templates/versions.xml | 7 +- runbot/views/build_views.xml | 1 + runbot/views/bundle_views.xml | 6 ++ runbot/views/config_views.xml | 1 + runbot/views/dockerfile_views.xml | 35 ++++++++++ 17 files changed, 289 insertions(+), 50 deletions(-) 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 + + + +