[IMP] runbot: add secret environment variables

Adds secret variables in addition to publicly readable ones, to
be able to provide ultra secret information to our builds without
requiring custom python code.
This commit is contained in:
William Braeckman 2025-03-11 16:24:06 +01:00
parent e44ac41a19
commit cc7ed99ceb
3 changed files with 44 additions and 5 deletions

View File

@ -159,6 +159,7 @@ class ConfigStep(models.Model):
sub_command = fields.Char('Subcommand', tracking=True) sub_command = fields.Char('Subcommand', tracking=True)
extra_params = fields.Char('Extra cmd args', tracking=True) extra_params = fields.Char('Extra cmd args', tracking=True)
additionnal_env = fields.Char('Extra env', help='Example: foo="bar";bar="foo". Cannot contains \' ', tracking=True) additionnal_env = fields.Char('Extra env', help='Example: foo="bar";bar="foo". Cannot contains \' ', tracking=True)
secret_env = fields.Char('Secret env', help='Hidden environment variables\nExample: foo="bar";bar="foo". Cannot contain \' ', groups="runbot.group_runbot_admin")
enable_log_db = fields.Boolean("Enable log db", default=True) enable_log_db = fields.Boolean("Enable log db", default=True)
demo_mode = fields.Selection( demo_mode = fields.Selection(
[('default', 'Default'), ('without_demo', 'Without Demo'), ('with_demo', 'With Demo')], [('default', 'Default'), ('without_demo', 'Without Demo'), ('with_demo', 'With Demo')],
@ -269,6 +270,18 @@ class ConfigStep(models.Model):
_logger.log('%s tried to create an non supported test_param %s' % (self.env.user.name, values.get('extra_params'))) _logger.log('%s tried to create an non supported test_param %s' % (self.env.user.name, values.get('extra_params')))
raise UserError('Invalid extra_params on config step') raise UserError('Invalid extra_params on config step')
def _get_env_variables(self, build=None) -> list[str]:
"""Returns a list of environment variables in the format KEY=VALUE"""
self.ensure_one()
variables = []
if self.additionnal_env:
variables.extend(self.additionnal_env.split(';'))
if self.secret_env:
variables.extend(self.secret_env.split(';'))
if build and (config_env_variables := build.params_id.config_data.get('env_variables', False)):
variables.extend(config_env_variables.split(';'))
return variables
def _run(self, build): def _run(self, build):
build.write({'job_start': now(), 'job_end': False}) # state, ... build.write({'job_start': now(), 'job_end': False}) # state, ...
log = build._log('run', f'Starting step **{self.name}** from config **{build.params_id.config_id.name}**', log_type='markdown', level='SEPARATOR') log = build._log('run', f'Starting step **{self.name}** from config **{build.params_id.config_id.name}**', log_type='markdown', level='SEPARATOR')
@ -405,7 +418,7 @@ class ConfigStep(models.Model):
extra_params = self.extra_params or '' extra_params = self.extra_params or ''
if extra_params: if extra_params:
cmd.extend(shlex.split(extra_params)) cmd.extend(shlex.split(extra_params))
env_variables = self.additionnal_env.split(';') if self.additionnal_env else [] env_variables = self._get_env_variables(build=build)
build_port = build.port build_port = build.port
try: try:
@ -508,9 +521,7 @@ class ConfigStep(models.Model):
if self.flamegraph: if self.flamegraph:
cmd.finals.append(['flamegraph.pl', '--title', 'Flamegraph %s for build %s' % (self.name, build.id), self._perfs_data_path(), '>', self._perfs_data_path(ext='svg')]) cmd.finals.append(['flamegraph.pl', '--title', 'Flamegraph %s for build %s' % (self.name, build.id), self._perfs_data_path(), '>', self._perfs_data_path(ext='svg')])
cmd.finals.append(['gzip', '-f', self._perfs_data_path()]) # keep data but gz them to save disc space cmd.finals.append(['gzip', '-f', self._perfs_data_path()]) # keep data but gz them to save disc space
env_variables = self.additionnal_env.split(';') if self.additionnal_env else [] env_variables = self._get_env_variables(build=build)
if config_env_variables := build.params_id.config_data.get('env_variables', False):
env_variables += config_env_variables.split(';')
return dict(cmd=cmd, ro_volumes=exports, env_variables=env_variables) return dict(cmd=cmd, ro_volumes=exports, env_variables=env_variables)
def _upgrade_create_childs(self): def _upgrade_create_childs(self):
@ -789,7 +800,7 @@ class ConfigStep(models.Model):
migrate_cmd.finals.append(['psql', migrate_db_name, '-c', '"SELECT id, name, state FROM ir_module_module WHERE state NOT IN (\'installed\', \'uninstalled\', \'uninstallable\') AND name NOT LIKE \'test_%\' "', '>', '/data/build/logs/modules_states.txt']) migrate_cmd.finals.append(['psql', migrate_db_name, '-c', '"SELECT id, name, state FROM ir_module_module WHERE state NOT IN (\'installed\', \'uninstalled\', \'uninstallable\') AND name NOT LIKE \'test_%\' "', '>', '/data/build/logs/modules_states.txt'])
env_variables = self.additionnal_env.split(';') if self.additionnal_env else [] env_variables = self._get_env_variables(build=build)
exception_env = self.env['runbot.upgrade.exception']._generate() exception_env = self.env['runbot.upgrade.exception']._generate()
if exception_env: if exception_env:
env_variables.append(exception_env) env_variables.append(exception_env)

View File

@ -548,10 +548,37 @@ class TestBuildConfigStep(TestBuildConfigStepCommon):
child = self.parent_build._add_child({'config_data': {'env_variables': 'CHROME_CPU_THROTTLE=10'}}) child = self.parent_build._add_child({'config_data': {'env_variables': 'CHROME_CPU_THROTTLE=10'}})
env_variables = config_step._get_env_variables(build=child)
self.assertEqual(env_variables, ['CHROME_CPU_THROTTLE=10'])
params = config_step._run_install_odoo(child) params = config_step._run_install_odoo(child)
env_variables = params.get('env_variables', []) env_variables = params.get('env_variables', [])
self.assertEqual(env_variables, ['CHROME_CPU_THROTTLE=10']) self.assertEqual(env_variables, ['CHROME_CPU_THROTTLE=10'])
@patch('odoo.addons.runbot.models.build.BuildResult._parse_config')
@patch('odoo.addons.runbot.models.build.BuildResult._checkout')
def test_config_env_variables(self, mock_checkout, parse_config):
parse_config.return_value = {'--test-enable', '--test-tags'}
config_step = self.ConfigStep.create({
'name': 'all',
'job_type': 'install_odoo',
'additionnal_env': 'CONFIG=1;CONFIG_2=2',
'secret_env': 'SECRET=secret',
})
child = self.parent_build._add_child({'config_data': {'env_variables': 'BUILD=FOO'}})
env_variables = config_step._get_env_variables(build=child)
self.assertEqual(
env_variables,
[
'CONFIG=1',
'CONFIG_2=2',
'SECRET=secret',
'BUILD=FOO',
],
)
@patch('odoo.addons.runbot.models.build.BuildResult._checkout') @patch('odoo.addons.runbot.models.build.BuildResult._checkout')
def test_db_name(self, mock_checkout): def test_db_name(self, mock_checkout):
config_step = self.ConfigStep.create({ config_step = self.ConfigStep.create({

View File

@ -78,6 +78,7 @@
<group string="Extra Parameters" invisible="job_type not in ('python', 'install_odoo', 'test_upgrade', 'run_odoo')"> <group string="Extra Parameters" invisible="job_type not in ('python', 'install_odoo', 'test_upgrade', 'run_odoo')">
<field name="extra_params"/> <field name="extra_params"/>
<field name="additionnal_env"/> <field name="additionnal_env"/>
<field name="secret_env"/>
<field name="enable_log_db"/> <field name="enable_log_db"/>
</group> </group>
<group string="Create settings" invisible="job_type not in ('python', 'create_build')"> <group string="Create settings" invisible="job_type not in ('python', 'create_build')">