mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[IMP] runbot: some fixes for ps runbot
- searching on number will search for both pr and branche name - hooks are now using payload to define repo when not given in url - fixes .git cleaning in repo (remove rstrip since it can fail for repo starting with g, i, t) - recompute base on prepare if base was not found - remove local_result form write values if there is a single record (instead of raising, makes python step easier to write). - avoid stucked build/loop after removing a step from a config. - avoid to send ci for linked base_commit - add a fallback mechanism for base if no master branch is found - add option on project to avoid to keep sticky running, usefull when using a lots of projects WARNING: this is a change of default behaviour, need to update existing projects. - always discover new commits for branch matching base paterns. This is especially usefull to discover old versions on project with low merge frequency. - always create a batch, event if there is now trigger. This helps to notice that commits are discovered - add line-through on death branches/pr - manual trigger are now displayed on main page
This commit is contained in:
parent
d8b96db1b7
commit
fe987cd0f3
@ -133,8 +133,7 @@ class Runbot(Controller):
|
||||
for search_elem in search.split("|"):
|
||||
if search_elem.isnumeric():
|
||||
pr_numbers.append(int(search_elem))
|
||||
else:
|
||||
search_domains.append([('name', 'like', search_elem)])
|
||||
search_domains.append([('name', 'like', search_elem)])
|
||||
if pr_numbers:
|
||||
res = request.env['runbot.branch'].search([('name', 'in', pr_numbers)])
|
||||
if res:
|
||||
|
@ -12,23 +12,27 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class Hook(http.Controller):
|
||||
|
||||
@http.route(['/runbot/hook/<int:remote_id>'], type='http', auth="public", website=True, csrf=False)
|
||||
@http.route(['/runbot/hook', '/runbot/hook/<int:remote_id>'], type='http', auth="public", website=True, csrf=False)
|
||||
def hook(self, remote_id=None, **_post):
|
||||
event = request.httprequest.headers.get("X-Github-Event")
|
||||
payload = json.loads(request.params.get('payload', '{}'))
|
||||
if remote_id is None:
|
||||
repo_data = payload.get('repository')
|
||||
if repo_data and event in ['push', 'pull_request']:
|
||||
if repo_data:
|
||||
remote_domain = [
|
||||
'|', '|', ('name', '=', repo_data['ssh_url']),
|
||||
'|', '|', '|',
|
||||
('name', '=', repo_data['ssh_url']),
|
||||
('name', '=', repo_data['ssh_url'].replace('.git', '')),
|
||||
('name', '=', repo_data['clone_url']),
|
||||
('name', '=', repo_data['clone_url'].rstrip('.git')),
|
||||
('name', '=', repo_data['clone_url'].replace('.git', '')),
|
||||
]
|
||||
remote = request.env['runbot.remote'].sudo().search(
|
||||
remote_domain, limit=1)
|
||||
remote_id = remote.id
|
||||
|
||||
remote = request.env['runbot.remote'].sudo().browse([remote_id])
|
||||
if not remote_id:
|
||||
_logger.error("Remote %s not found", repo_data['ssh_url'])
|
||||
remote = request.env['runbot.remote'].sudo().browse(remote_id)
|
||||
_logger.info('Remote found %s', remote)
|
||||
|
||||
# force update of dependencies too in case a hook is lost
|
||||
if not payload or event == 'push':
|
||||
|
@ -140,6 +140,9 @@ class Batch(models.Model):
|
||||
|
||||
def _prepare(self, auto_rebase=False):
|
||||
_logger.info('Preparing batch %s', self.id)
|
||||
if not self.bundle_id.base_id:
|
||||
# in some case the base can be detected lately. If a bundle has no base, recompute the base before preparing
|
||||
self.bundle_id._compute_base_id()
|
||||
for level, message in self.bundle_id.consistency_warning():
|
||||
if level == "warning":
|
||||
self.warning("Bundle warning: %s" % message)
|
||||
|
@ -316,7 +316,11 @@ class BuildResult(models.Model):
|
||||
build_by_old_values[record.local_state] += record
|
||||
local_result = values.get('local_result')
|
||||
for build in self:
|
||||
assert not local_result or local_result == self._get_worst_result([build.local_result, local_result]) # dont write ok on a warn/error build
|
||||
if local_result and local_result != self._get_worst_result([build.local_result, local_result]): # dont write ok on a warn/error build
|
||||
if len(self) == 1:
|
||||
values.pop('local_result')
|
||||
else:
|
||||
raise ValidationError('Local result cannot be set to a less critical level')
|
||||
res = super(BuildResult, self).write(values)
|
||||
if 'log_counter' in values: # not 100% usefull but more correct ( see test_ir_logging)
|
||||
self.flush()
|
||||
@ -1009,7 +1013,13 @@ class BuildResult(models.Model):
|
||||
# means that a step has been run manually without using config
|
||||
return {'active_step': False, 'local_state': 'done'}
|
||||
|
||||
next_index = step_ids.index(self.active_step) + 1 if self.active_step else 0
|
||||
if not self.active_step:
|
||||
next_index = 0
|
||||
else:
|
||||
if self.active_step not in step_ids:
|
||||
self._log('run', 'Config was modified and current step does not exists anymore, skipping.', level='ERROR')
|
||||
return {'active_step': False, 'local_state': 'done', 'local_result': self._get_worst_result([self.local_result, 'ko'])}
|
||||
next_index = step_ids.index(self.active_step) + 1
|
||||
|
||||
while True:
|
||||
if next_index >= len(step_ids): # final job, build is done
|
||||
@ -1132,5 +1142,5 @@ class BuildResult(models.Model):
|
||||
if trigger.ci_context:
|
||||
for build_commit in self.params_id.commit_link_ids:
|
||||
commit = build_commit.commit_id
|
||||
if build_commit.match_type != 'default' and commit.repo_id in trigger.repo_ids:
|
||||
if 'base_' not in build_commit.match_type and commit.repo_id in trigger.repo_ids:
|
||||
commit._github_status(build, trigger.ci_context, state, target_url, desc, post_commit)
|
||||
|
@ -93,14 +93,17 @@ class Bundle(models.Model):
|
||||
continue
|
||||
project_id = bundle.project_id.id
|
||||
master_base = False
|
||||
fallback = False
|
||||
for bid, bname in self._get_base_ids(project_id):
|
||||
if bundle.name.startswith('%s-' % bname):
|
||||
bundle.base_id = self.browse(bid)
|
||||
break
|
||||
elif bname == 'master':
|
||||
master_base = self.browse(bid)
|
||||
elif not fallback or fallback.id < bid:
|
||||
fallback = self.browse(bid)
|
||||
else:
|
||||
bundle.base_id = master_base
|
||||
bundle.base_id = master_base or fallback
|
||||
|
||||
@tools.ormcache('project_id')
|
||||
def _get_base_ids(self, project_id):
|
||||
@ -213,14 +216,17 @@ class Bundle(models.Model):
|
||||
if self.defined_base_id:
|
||||
return [('info', 'This bundle has a forced base: %s' % self.defined_base_id.name)]
|
||||
warnings = []
|
||||
for branch in self.branch_ids:
|
||||
if branch.is_pr and branch.target_branch_name != self.base_id.name:
|
||||
if branch.target_branch_name.startswith(self.base_id.name):
|
||||
warnings.append(('info', 'PR %s targeting a non base branch: %s' % (branch.dname, branch.target_branch_name)))
|
||||
else:
|
||||
warnings.append(('warning' if branch.alive else 'info', 'PR %s targeting wrong version: %s (expecting %s)' % (branch.dname, branch.target_branch_name, self.base_id.name)))
|
||||
elif not branch.is_pr and not branch.name.startswith(self.base_id.name) and not self.defined_base_id:
|
||||
warnings.append(('warning', 'Branch %s not starting with version name (%s)' % (branch.dname, self.base_id.name)))
|
||||
if not self.base_id:
|
||||
warnings.append(('warning', 'No base defined on this bundle'))
|
||||
else:
|
||||
for branch in self.branch_ids:
|
||||
if branch.is_pr and branch.target_branch_name != self.base_id.name:
|
||||
if branch.target_branch_name.startswith(self.base_id.name):
|
||||
warnings.append(('info', 'PR %s targeting a non base branch: %s' % (branch.dname, branch.target_branch_name)))
|
||||
else:
|
||||
warnings.append(('warning' if branch.alive else 'info', 'PR %s targeting wrong version: %s (expecting %s)' % (branch.dname, branch.target_branch_name, self.base_id.name)))
|
||||
elif not branch.is_pr and not branch.name.startswith(self.base_id.name) and not self.defined_base_id:
|
||||
warnings.append(('warning', 'Branch %s not starting with version name (%s)' % (branch.dname, self.base_id.name)))
|
||||
return warnings
|
||||
|
||||
def branch_groups(self):
|
||||
|
@ -7,7 +7,7 @@ class Project(models.Model):
|
||||
|
||||
name = fields.Char('Project name', required=True, unique=True)
|
||||
group_ids = fields.Many2many('res.groups', string='Required groups')
|
||||
|
||||
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")
|
||||
|
||||
|
@ -365,7 +365,7 @@ class Repo(models.Model):
|
||||
if not git_refs:
|
||||
return []
|
||||
refs = [tuple(field for field in line.split('\x00')) for line in git_refs.split('\n')]
|
||||
refs = [r for r in refs if dateutil.parser.parse(r[2][:19]) + datetime.timedelta(days=max_age) > datetime.datetime.now()]
|
||||
refs = [r for r in refs if dateutil.parser.parse(r[2][:19]) + datetime.timedelta(days=max_age) > datetime.datetime.now() or self.env['runbot.branch'].match_is_base(r[0])]
|
||||
if ignore:
|
||||
refs = [r for r in refs if r[0].split('/')[-1] not in ignore]
|
||||
return refs
|
||||
@ -441,9 +441,6 @@ class Repo(models.Model):
|
||||
message = branch.remote_id.repo_id.invalid_branch_message or message
|
||||
branch.head._github_status(False, "Branch naming", 'failure', False, message)
|
||||
|
||||
if not self.trigger_ids:
|
||||
continue
|
||||
|
||||
bundle = branch.bundle_id
|
||||
if bundle.no_build:
|
||||
continue
|
||||
|
@ -90,7 +90,7 @@ class Runbot(models.AbstractModel):
|
||||
domain_host = self.build_domain_host(host)
|
||||
Build = self.env['runbot.build']
|
||||
cannot_be_killed_ids = Build.search(domain_host + [('keep_running', '=', True)]).ids
|
||||
sticky_bundles = self.env['runbot.bundle'].search([('sticky', '=', True)])
|
||||
sticky_bundles = self.env['runbot.bundle'].search([('sticky', '=', True), ('project_id.keep_sticky_running', '=', True)])
|
||||
cannot_be_killed_ids += [
|
||||
build.id
|
||||
for build in sticky_bundles.mapped('last_batchs.slot_ids.build_id')
|
||||
|
@ -47,7 +47,7 @@
|
||||
<a t-att-href="branch.branch_url" class="btn btn-default text-left" title="View Branch on Github"><i class="fa fa-github"/></a>
|
||||
<a groups="runbot.group_runbot_admin" class="btn btn-default fa fa-list text-left" t-attf-href="/web/#id={{branch.id}}&view_type=form&model=runbot.branch" target="new" title="View Branch in Backend"/>
|
||||
<a href="#" t-esc="branch.remote_id.short_name" class="btn btn-default disabled text-left"/>
|
||||
<a t-attf-href="/runbot/branch/{{branch.id}}" class="btn btn-default text-left" title="View Branch Details"><t t-esc="branch.name"/> <i t-if="not branch.alive" title="deleted/closed" class="fa fa-ban text-danger"/></a>
|
||||
<a t-attf-href="/runbot/branch/{{branch.id}}" class="btn btn-default text-left" title="View Branch Details"><span t-att-class="'' if branch.alive else 'line-through'" t-esc="branch.name"/> <i t-if="not branch.alive" title="deleted/closed" class="fa fa-ban text-danger"/></a>
|
||||
<t t-if="len(group[1]) == 1 and not branch.is_pr">
|
||||
<a t-attf-href="https://{{group[0].main_remote_id.base_url}}/compare/{{bundle.version_id.name}}...{{branch.remote_id.owner}}:{{branch.name}}?expand=1" class="btn btn-default text-left" title="Create pr"><i class="fa fa-code-fork"/> Create pr</a>
|
||||
</t>
|
||||
|
@ -94,7 +94,7 @@
|
||||
<div class="batch_slots">
|
||||
<t t-foreach="batch.slot_ids" t-as="slot">
|
||||
<t t-if="slot.build_id">
|
||||
<div t-if="not slot.trigger_id.manual and ((not slot.trigger_id.hide and trigger_display is None) or (trigger_display and slot.trigger_id.id in trigger_display))or slot.build_id.global_result == 'ko'"
|
||||
<div t-if="((not slot.trigger_id.hide and trigger_display is None) or (trigger_display and slot.trigger_id.id in trigger_display)) or slot.build_id.global_result == 'ko'"
|
||||
t-call="runbot.slot_button" class="slot_container"/>
|
||||
</t>
|
||||
</t>
|
||||
@ -120,7 +120,3 @@
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -67,7 +67,7 @@
|
||||
<t t-if="triggers">
|
||||
<input type="hidden" name="update_triggers" t-att-value="project.id"/>
|
||||
<t t-foreach="triggers" t-as="trigger">
|
||||
<div t-if="not trigger.manual" class="text-nowrap">
|
||||
<div class="text-nowrap">
|
||||
<input type="checkbox" t-attf-name="trigger_{{trigger.id}}" t-attf-id="trigger_{{trigger.id}}" t-att-checked="trigger_display is None or trigger.id in trigger_display"/>
|
||||
<label t-attf-for="trigger_{{trigger.id}}" t-esc="trigger.name"/>
|
||||
</div>
|
||||
@ -305,7 +305,9 @@
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<t t-foreach="bundle.branch_ids.sorted(key=lambda b: (b.remote_id.repo_id.sequence, b.remote_id.repo_id.id, b.is_pr))" t-as="branch">
|
||||
<t t-set="link_title" t-value="'View %s %s on Github' % ('PR' if branch.is_pr else 'Branch', branch.name)"/>
|
||||
<a t-att-href="branch.branch_url" class="dropdown-item" t-att-title="link_title"><span class="font-italic text-muted"><t t-esc="branch.remote_id.short_name"/></span> <t t-esc="branch.name"/></a>
|
||||
<a t-att-href="branch.branch_url" class="dropdown-item" t-att-title="link_title">
|
||||
<span class="font-italic text-muted" t-esc="branch.remote_id.short_name"/> <span t-att-class="'' if branch.alive else 'line-through'" t-esc="branch.name"/> <i t-if="not branch.alive" title="deleted/closed" class="fa fa-ban text-danger"/>
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -4,7 +4,7 @@ import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from .common import RunbotCase, RunbotCaseMinimalSetup
|
||||
|
||||
|
||||
@ -183,9 +183,12 @@ class TestBuildResult(RunbotCase):
|
||||
'local_result': 'ko'
|
||||
})
|
||||
|
||||
other.write({'local_result': 'ok'})
|
||||
self.assertEqual(other.local_result, 'ko')
|
||||
|
||||
# test a bulk write, that one cannot change from 'ko' to 'ok'
|
||||
builds = self.Build.browse([build.id, other.id])
|
||||
with self.assertRaises(AssertionError):
|
||||
with self.assertRaises(ValidationError):
|
||||
builds.write({'local_result': 'ok'})
|
||||
|
||||
def test_markdown_description(self):
|
||||
|
@ -7,6 +7,7 @@
|
||||
<form string="Projects">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="keep_sticky_running"/>
|
||||
<field name="dockerfile_id"/>
|
||||
<field name="group_ids"/>
|
||||
<field name="trigger_ids"/>
|
||||
@ -15,6 +16,18 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_runbot_project_tree" model="ir.ui.view">
|
||||
<field name="model">runbot.project</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Projects">
|
||||
<field name="name"/>
|
||||
<field name="keep_sticky_running"/>
|
||||
<field name="dockerfile_id"/>
|
||||
<field name="group_ids"/>
|
||||
<field name="trigger_ids"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_runbot_bundle" model="ir.ui.view">
|
||||
<field name="model">runbot.bundle</field>
|
||||
|
Loading…
Reference in New Issue
Block a user