Add support for filtering based on tags, in addition to branches

This commit is contained in:
Joseph Schorr 2014-10-23 16:39:10 -04:00
parent 94c24a93c2
commit fb2470615b
7 changed files with 236 additions and 63 deletions

View file

@ -231,15 +231,16 @@ class GithubBuildTrigger(BuildTrigger):
return repos_by_org return repos_by_org
def matches_branch(self, branch_name, regex): def matches_ref(self, ref, regex):
match_string = ref.split('/', 1)[1]
if not regex: if not regex:
return False return False
m = regex.match(branch_name) m = regex.match(match_string)
if not m: if not m:
return False return False
return len(m.group(0)) == len(branch_name) return len(m.group(0)) == len(match_string)
def list_build_subdirs(self, auth_token, config): def list_build_subdirs(self, auth_token, config):
gh_client = self._get_client(auth_token) gh_client = self._get_client(auth_token)
@ -250,11 +251,11 @@ class GithubBuildTrigger(BuildTrigger):
# Find the first matching branch. # Find the first matching branch.
branches = None branches = None
if 'branch_regex' in config: if 'branchtag_regex' in config:
try: try:
regex = re.compile(config['branch_regex']) regex = re.compile(config['branchtag_regex'])
branches = [branch.name for branch in repo.get_branches() branches = [branch.name for branch in repo.get_branches()
if self.matches_branch(branch.name, regex)] if self.matches_ref('refs/heads/' + branch.name, regex)]
except: except:
pass pass
@ -370,14 +371,13 @@ class GithubBuildTrigger(BuildTrigger):
commit_sha = payload['head_commit']['id'] commit_sha = payload['head_commit']['id']
commit_message = payload['head_commit'].get('message', '') commit_message = payload['head_commit'].get('message', '')
if 'branch_regex' in config: if 'branchtag_regex' in config:
try: try:
regex = re.compile(config['branch_regex']) regex = re.compile(config['branchtag_regex'])
except: except:
regex = re.compile('.*') regex = re.compile('.*')
branch = ref.split('/')[-1] if not self.matches_ref(ref, regex):
if not self.matches_branch(branch, regex):
raise SkipRequestException() raise SkipRequestException()
if should_skip_commit(commit_message): if should_skip_commit(commit_message):
@ -414,6 +414,19 @@ class GithubBuildTrigger(BuildTrigger):
def list_field_values(self, auth_token, config, field_name): def list_field_values(self, auth_token, config, field_name):
if field_name == 'refs':
branches = self.list_field_values(auth_token, config, 'branch_name')
tags = self.list_field_values(auth_token, config, 'tag_name')
return ([{'kind': 'branch', 'name': b} for b in branches] +
[{'kind': 'tag', 'name': tag} for tag in tags])
if field_name == 'tag_name':
gh_client = self._get_client(auth_token)
source = config['build_source']
repo = gh_client.get_repo(source)
return [tag.name for tag in repo.get_tags()]
if field_name == 'branch_name': if field_name == 'branch_name':
gh_client = self._get_client(auth_token) gh_client = self._get_client(auth_token)
source = config['build_source'] source = config['build_source']

View file

@ -19,6 +19,18 @@
} }
} }
.dropdown.input-group-addon {
padding: 0px;
border: 0px;
background-color: transparent;
text-align: left;
}
.dropdown.input-group-addon .dropdown-toggle {
border-left: 0px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
#quay-logo { #quay-logo {
width: 100px; width: 100px;
@ -4109,20 +4121,37 @@ pre.command:before {
border-bottom-left-radius: 0px; border-bottom-left-radius: 0px;
} }
.trigger-setup-github-element .branch-reference.not-match { .trigger-setup-github-element .ref-reference {
color: #ccc !important; color: #ccc;
} }
.trigger-setup-github-element .branch-reference.not-match a { .trigger-setup-github-element .ref-reference span {
color: #ccc !important; cursor: pointer;
text-decoration: line-through; text-decoration: line-through;
} }
.trigger-setup-github-element .branch-filter { .trigger-setup-github-element .ref-reference:hover {
color: #3276b1;
}
.trigger-setup-github-element .ref-reference:hover span {
text-decoration: none;
}
.trigger-setup-github-element .ref-reference.match {
color: black;
}
.trigger-setup-github-element .ref-reference.match span {
text-decoration: none;
cursor: default;
}
.trigger-setup-github-element .ref-filter {
white-space: nowrap; white-space: nowrap;
} }
.trigger-setup-github-element .branch-filter span { .trigger-setup-github-element .ref-filter span {
display: inline-block; display: inline-block;
} }
@ -4145,19 +4174,37 @@ pre.command:before {
padding-left: 6px; padding-left: 6px;
} }
.trigger-setup-github-element .matching-branches { .trigger-setup-github-element .matching-refs {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
margin-left: 10px; margin-left: 10px;
display: inline-block; display: inline-block;
} }
.trigger-setup-github-element .matching-branches li:before { .trigger-setup-github-element .ref-matches {
padding-left: 70px;
position: relative;
margin-bottom: 10px;
}
.trigger-setup-github-element .ref-matches .kind {
font-weight: bold;
position: absolute;
top: 0px;
left: 0px;
}
.trigger-setup-github-element .matching-refs.tags li:before {
content: "\f02b";
font-family: FontAwesome;
}
.trigger-setup-github-element .matching-refs.branches li:before {
content: "\f126"; content: "\f126";
font-family: FontAwesome; font-family: FontAwesome;
} }
.trigger-setup-github-element .matching-branches li { .trigger-setup-github-element .matching-refs li {
list-style: none; list-style: none;
display: inline-block; display: inline-block;
margin-left: 10px; margin-left: 10px;
@ -4333,11 +4380,14 @@ pre.command:before {
} }
.trigger-pull-credentials { .trigger-pull-credentials {
margin-top: 4px;
padding-left: 26px; padding-left: 26px;
font-size: 12px; font-size: 12px;
} }
.trigger-pull-credentials .entity-reference {
margin-left: 10px;
}
.trigger-pull-credentials .context-tooltip { .trigger-pull-credentials .context-tooltip {
color: gray; color: gray;
margin-right: 4px; margin-right: 4px;
@ -4345,7 +4395,8 @@ pre.command:before {
.trigger-description .trigger-description-subtitle { .trigger-description .trigger-description-subtitle {
display: inline-block; display: inline-block;
margin-right: 34px; width: 100px;
margin-bottom: 4px;
} }
.trigger-option-section:not(:first-child) { .trigger-option-section:not(:first-child) {

View file

@ -4,9 +4,9 @@
Push to GitHub repository <a href="https://github.com/{{ trigger.config.build_source }}" target="_new">{{ trigger.config.build_source }}</a> Push to GitHub repository <a href="https://github.com/{{ trigger.config.build_source }}" target="_new">{{ trigger.config.build_source }}</a>
<div style="margin-top: 4px; margin-left: 26px; font-size: 12px; color: gray;" ng-if="!short"> <div style="margin-top: 4px; margin-left: 26px; font-size: 12px; color: gray;" ng-if="!short">
<div> <div>
<span class="trigger-description-subtitle">Branches:</span> <span class="trigger-description-subtitle">Branches/Tags:</span>
<span ng-if="trigger.config.branch_regex">Matching Regular Expression {{ trigger.config.branch_regex }}</span> <span ng-if="trigger.config.branchtag_regex">Matching Regular Expression {{ trigger.config.branchtag_regex }}</span>
<span ng-if="!trigger.config.branch_regex">(All Branches)</span> <span ng-if="!trigger.config.branchtag_regex">(All Branches and Tags)</span>
</div> </div>
<div> <div>

View file

@ -10,19 +10,19 @@
<div class="current-repo"> <div class="current-repo">
<img class="dropdown-select-icon github-org-icon" <img class="dropdown-select-icon github-org-icon"
ng-src="{{ currentRepo.avatar_url ? currentRepo.avatar_url : '//www.gravatar.com/avatar/' }}"> ng-src="{{ currentRepo.avatar_url ? currentRepo.avatar_url : '//www.gravatar.com/avatar/' }}">
{{ currentRepo.repo }} <a ng-href="https://github.com/{{ currentRepo.repo }}" target="_blank">{{ currentRepo.repo }}</a>
</div> </div>
</td> </td>
</tr> </tr>
<tr ng-show="nextStepCounter > 1"> <tr ng-show="nextStepCounter > 1">
<td> <td>
Branches: Branches and Tags:
</td> </td>
<td> <td>
<div class="branch-filter"> <div class="ref-filter">
<span ng-if="!state.hasBranchFilter">(All Branches)</span> <span ng-if="!state.hasBranchTagFilter">(Build All)</span>
<span ng-if="state.hasBranchFilter">Regular Expression: <code>{{ state.branchFilter }}</code></span> <span ng-if="state.hasBranchTagFilter">Regular Expression: <code>{{ state.branchTagFilter }}</code></span>
</div> </div>
</td> </td>
</tr> </tr>
@ -68,45 +68,76 @@
</div> </div>
</div> </div>
<!-- Branch filter/select --> <!-- Branch/Tag filter/select -->
<div class="step-view-step" complete-condition="!state.hasBranchFilter || state.branchFilter" <div class="step-view-step" complete-condition="!state.hasBranchTagFilter || state.branchTagFilter"
load-callback="loadBranches(callback)" load-callback="loadBranchesAndTags(callback)"
load-message="Loading Branches"> load-message="Loading Branches and Tags">
<div style="margin-bottom: 12px">Please choose the branches to which this trigger will apply:</div> <div style="margin-bottom: 12px">Please choose the branches and tags to which this trigger will apply:</div>
<div style="margin-left: 20px;"> <div style="margin-left: 20px;">
<div class="btn-group btn-group-sm" style="margin-bottom: 12px"> <div class="btn-group btn-group-sm" style="margin-bottom: 12px">
<button type="button" class="btn btn-default" <button type="button" class="btn btn-default"
ng-class="state.hasBranchFilter ? '' : 'active btn-info'" ng-click="state.hasBranchFilter = false"> ng-class="state.hasBranchTagFilter ? '' : 'active btn-info'" ng-click="state.hasBranchTagFilter = false">
All Branches All Branches and Tags
</button> </button>
<button type="button" class="btn btn-default" <button type="button" class="btn btn-default"
ng-class="state.hasBranchFilter ? 'active btn-info' : ''" ng-click="state.hasBranchFilter = true"> ng-class="state.hasBranchTagFilter ? 'active btn-info' : ''" ng-click="state.hasBranchTagFilter = true">
Matching Regular Expression Matching Regular Expression
</button> </button>
</div> </div>
<div ng-show="state.hasBranchFilter" style="margin-top: 10px;"> <div ng-show="state.hasBranchTagFilter" style="margin-top: 10px;">
<form> <form>
<input class="form-control" type="text" ng-model="state.branchFilter" <div class="form-group">
placeholder="(Regular expression)" required> <div class="input-group">
<input class="form-control" type="text" ng-model="state.branchTagFilter"
placeholder="(Regular expression. Examples: heads/branchname, tags/tagname)" required>
<div class="dropdown input-group-addon">
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right">
<li><a href="javascript:void(0)" ng-click="state.branchTagFilter = 'heads/.+'">
<i class="fa fa-code-fork"></i>All Branches</a>
</li>
<li><a href="javascript:void(0)" ng-click="state.branchTagFilter = 'tags/.+'">
<i class="fa fa-tag"></i>All Tags</a>
</li>
</ul>
</div>
</div>
</div>
</form> </form>
<div style="margin-top: 10px"> <div style="margin-top: 10px">
<div ng-if="branchNames.length"> <div class="ref-matches" ng-if="branchNames.length">
Branches: <span class="kind">Branches:</span>
<ul class="matching-branches"> <ul class="matching-refs branches">
<li ng-repeat="branchName in branchNames | limitTo:20" <li ng-repeat="branchName in branchNames | limitTo:20"
class="branch-reference" class="ref-reference"
ng-class="isMatchingBranch(branchName, state.branchFilter) ? 'match' : 'not-match'"> ng-class="isMatching('heads', branchName, state.branchTagFilter) ? 'match' : 'not-match'">
<a href="https://github.com/{{ currentRepo.repo }}/tree/{{ branchName }}" target="_blank"> <span ng-click="addRef('heads', branchName)" target="_blank">
{{ branchName }} {{ branchName }}
</a> </span>
</li> </li>
</ul> </ul>
<span ng-if="branchNames.length > 20">...</span> <span ng-if="branchNames.length > 20">...</span>
</div> </div>
<div ng-if="state.branchFilter && !branchNames.length" <div class="ref-matches" ng-if="tagNames.length" style="margin-bottom: -20px">
<span class="kind">Tags:</span>
<ul class="matching-refs tags">
<li ng-repeat="tagName in tagNames | limitTo:20"
class="ref-reference"
ng-class="isMatching('tags', tagName, state.branchTagFilter) ? 'match' : 'not-match'">
<span ng-click="addRef('tags', tagName)" target="_blank">
{{ tagName }}
</span>
</li>
</ul>
<span ng-if="tagNames.length > 20">...</span>
</div>
<div ng-if="state.branchTagFilter && !branchNames.length"
style="margin-top: 10px"> style="margin-top: 10px">
<strong>Warning:</strong> No branches found <strong>Warning:</strong> No branches found
</div> </div>

View file

@ -5258,25 +5258,41 @@ quayApp.directive('triggerSetupGithub', function () {
controller: function($scope, $element, ApiService) { controller: function($scope, $element, ApiService) {
$scope.analyzeCounter = 0; $scope.analyzeCounter = 0;
$scope.setupReady = false; $scope.setupReady = false;
$scope.refs = null;
$scope.branchNames = null; $scope.branchNames = null;
$scope.tagNames = null;
$scope.state = { $scope.state = {
'branchFilter': '', 'branchTagFilter': '',
'hasBranchFilter': false, 'hasBranchTagFilter': false,
'isInvalidLocation': true, 'isInvalidLocation': true,
'currentLocation': null 'currentLocation': null
}; };
$scope.isMatchingBranch = function(branchName, filter) { $scope.isMatching = function(kind, name, filter) {
try { try {
var patt = new RegExp(filter); var patt = new RegExp(filter);
} catch (ex) { } catch (ex) {
return false; return false;
} }
var m = branchName.match(patt); var fullname = (kind + '/' + name);
return m && m[0].length == branchName.length; var m = fullname.match(patt);
return m && m[0].length == fullname.length;
}
$scope.addRef = function(kind, name) {
if ($scope.isMatching(kind, name, $scope.state.branchTagFilter)) {
return;
}
var newFilter = kind + '/' + name;
var existing = $scope.state.branchTagFilter;
if (existing) {
$scope.state.branchTagFilter = '(' + existing + ')|(' + newFilter + ')';
} else {
$scope.state.branchTagFilter = newFilter;
}
} }
$scope.stepsCompleted = function() { $scope.stepsCompleted = function() {
@ -5296,17 +5312,29 @@ quayApp.directive('triggerSetupGithub', function () {
}, ApiService.errorDisplay('Cannot load repositories')); }, ApiService.errorDisplay('Cannot load repositories'));
}; };
$scope.loadBranches = function(callback) { $scope.loadBranchesAndTags = function(callback) {
var params = { var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name, 'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': $scope.trigger['id'], 'trigger_uuid': $scope.trigger['id'],
'field_name': 'branch_name' 'field_name': 'refs'
}; };
ApiService.listTriggerFieldValues($scope.trigger['config'], params).then(function(resp) { ApiService.listTriggerFieldValues($scope.trigger['config'], params).then(function(resp) {
$scope.branchNames = resp['values']; $scope.refs = resp['values'];
$scope.branchNames = [];
$scope.tagNames = [];
for (var i = 0; i < $scope.refs.length; ++i) {
var ref = $scope.refs[i];
if (ref.kind == 'branch') {
$scope.branchNames.push(ref.name);
} else {
$scope.tagNames.push(ref.name);
}
}
callback(); callback();
}, ApiService.errorDisplay('Cannot load branch names')); }, ApiService.errorDisplay('Cannot load branch and tag names'));
}; };
$scope.loadLocations = function(callback) { $scope.loadLocations = function(callback) {
@ -5413,13 +5441,13 @@ quayApp.directive('triggerSetupGithub', function () {
} }
}); });
$scope.$watch('state.branchFilter', function(bf) { $scope.$watch('state.branchTagFilter', function(bf) {
if (!$scope.trigger) { return; } if (!$scope.trigger) { return; }
if ($scope.state.hasBranchFilter) { if ($scope.state.hasBranchTagFilter) {
$scope.trigger['config']['branch_regex'] = bf; $scope.trigger['config']['branchtag_regex'] = bf;
} else { } else {
delete $scope.trigger['config']['branch_regex']; delete $scope.trigger['config']['branchtag_regex'];
} }
}); });
} }

View file

@ -260,7 +260,7 @@
<span class="entity-reference" entity="trigger.pull_robot"></span> <span class="entity-reference" entity="trigger.pull_robot"></span>
</div> </div>
</td> </td>
<td style="white-space: nowrap;"> <td style="white-space: nowrap;" valign="top">
<div class="dropdown" style="display: inline-block" ng-visible="trigger.is_active"> <div class="dropdown" style="display: inline-block" ng-visible="trigger.is_active">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" data-title="Build History" bs-tooltip="tooltip.title" data-container="body" <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" data-title="Build History" bs-tooltip="tooltip.title" data-container="body"
ng-click="loadTriggerBuildHistory(trigger)"> ng-click="loadTriggerBuildHistory(trigger)">

View file

@ -0,0 +1,50 @@
import argparse
import logging
import json
from app import app
from data import model
from data.database import RepositoryBuildTrigger, configure
configure(app.config)
logger = logging.getLogger(__name__)
def run_branchregex_migration():
encountered = set()
while True:
found = list(RepositoryBuildTrigger.select().where(RepositoryBuildTrigger.config ** "%branch_regex%",
~(RepositoryBuildTrigger.config ** "%branchtag_regex%")))
found = [f for f in found if not f.uuid in encountered]
if not found:
logger.debug('No additional records found')
return
logger.debug('Found %s records to be changed', len(found))
for trigger in found:
encountered.add(trigger.uuid)
try:
config = json.loads(trigger.config)
except:
logging.error("Cannot parse config for trigger %s", trigger.uuid)
continue
logger.debug("Checking trigger %s", trigger.uuid)
existing_regex = config['branch_regex']
logger.debug("Found branch regex '%s'", existing_regex)
sub_regex = existing_regex.split('|')
new_regex = '|'.join(['heads/' + sub for sub in sub_regex])
config['branchtag_regex'] = new_regex
logger.debug("Updating to branchtag regex '%s'", new_regex)
trigger.config = json.dumps(config)
trigger.save()
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('boto').setLevel(logging.CRITICAL)
run_branchregex_migration()