##// END OF EJS Templates
integrations: parse pushed tags, and lightweight tags for git....
marcink -
r2422:ac36fdfd default
parent child Browse files
Show More
@@ -0,0 +1,141 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Test suite for making push/pull operations, on specially modified INI files
23
24 .. important::
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
28 """
29
30 import pytest
31 import requests
32
33 from rhodecode import events
34 from rhodecode.model.db import Integration
35 from rhodecode.model.integration import IntegrationModel
36 from rhodecode.model.meta import Session
37
38 from rhodecode.tests import GIT_REPO, HG_REPO
39 from rhodecode.tests.other.vcs_operations import Command, _add_files_and_push
40 from rhodecode.integrations.types.webhook import WebhookIntegrationType
41
42
43 def check_connection():
44 try:
45 response = requests.get('http://httpbin.org')
46 return response.status_code == 200
47 except Exception as e:
48 print(e)
49
50 return False
51
52
53 connection_available = pytest.mark.skipif(
54 not check_connection(), reason="No outside internet connection available")
55
56
57 @pytest.fixture
58 def enable_webhook_push_integration(request):
59 integration = Integration()
60 integration.integration_type = WebhookIntegrationType.key
61 Session().add(integration)
62
63 settings = dict(
64 url='http://httpbin.org',
65 secret_token='secret',
66 username=None,
67 password=None,
68 custom_header_key=None,
69 custom_header_val=None,
70 method_type='get',
71 events=[events.RepoPushEvent.name],
72 log_data=True
73 )
74
75 IntegrationModel().update_integration(
76 integration,
77 name='IntegrationWebhookTest',
78 enabled=True,
79 settings=settings,
80 repo=None,
81 repo_group=None,
82 child_repos_only=False,
83 )
84 Session().commit()
85 integration_id = integration.integration_id
86
87 @request.addfinalizer
88 def cleanup():
89 integration = Integration.get(integration_id)
90 Session().delete(integration)
91 Session().commit()
92
93
94 @pytest.mark.usefixtures(
95 "disable_locking", "disable_anonymous_user",
96 "enable_webhook_push_integration")
97 class TestVCSOperationsOnCustomIniConfig(object):
98
99 def test_push_tag_with_commit_hg(self, rc_web_server, tmpdir):
100 clone_url = rc_web_server.repo_clone_url(HG_REPO)
101 stdout, stderr = Command('/tmp').execute(
102 'hg clone', clone_url, tmpdir.strpath)
103
104 push_url = rc_web_server.repo_clone_url(HG_REPO)
105 _add_files_and_push(
106 'hg', tmpdir.strpath, clone_url=push_url,
107 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
108
109 rc_log = rc_web_server.get_rc_log()
110 assert 'ERROR' not in rc_log
111 assert "'name': u'v1.0.0'" in rc_log
112
113 def test_push_tag_with_commit_git(
114 self, rc_web_server, tmpdir):
115 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
116 stdout, stderr = Command('/tmp').execute(
117 'git clone', clone_url, tmpdir.strpath)
118
119 push_url = rc_web_server.repo_clone_url(GIT_REPO)
120 _add_files_and_push(
121 'git', tmpdir.strpath, clone_url=push_url,
122 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
123
124 rc_log = rc_web_server.get_rc_log()
125 assert 'ERROR' not in rc_log
126 assert "'name': u'v1.0.0'" in rc_log
127
128 def test_push_tag_with_no_commit_git(
129 self, rc_web_server, tmpdir):
130 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
131 stdout, stderr = Command('/tmp').execute(
132 'git clone', clone_url, tmpdir.strpath)
133
134 push_url = rc_web_server.repo_clone_url(GIT_REPO)
135 _add_files_and_push(
136 'git', tmpdir.strpath, clone_url=push_url,
137 tags=[{'name': 'v1.0.0', 'commit': 'added tag v1.0.0'}])
138
139 rc_log = rc_web_server.get_rc_log()
140 assert 'ERROR' not in rc_log
141 assert "'name': u'v1.0.0'" in rc_log
@@ -18,6 +18,7 b''
18 18
19 19 import collections
20 20 import logging
21 import datetime
21 22
22 23 from rhodecode.translation import lazy_ugettext
23 24 from rhodecode.model.db import User, Repository, Session
@@ -58,22 +59,60 b' def _commits_as_dict(event, commit_ids, '
58 59 return commits # return early if we have the commits we need
59 60
60 61 vcs_repo = repo.scm_instance(cache=False)
62
61 63 try:
62 64 # use copy of needed_commits since we modify it while iterating
63 65 for commit_id in list(needed_commits):
64 try:
65 cs = vcs_repo.get_changeset(commit_id)
66 except CommitDoesNotExistError:
67 continue # maybe its in next repo
66 if commit_id.startswith('tag=>'):
67 raw_id = commit_id[5:]
68 cs_data = {
69 'raw_id': commit_id, 'short_id': commit_id,
70 'branch': None,
71 'git_ref_change': 'tag_add',
72 'message': 'Added new tag {}'.format(raw_id),
73 'author': event.actor.full_contact,
74 'date': datetime.datetime.now(),
75 'refs': {
76 'branches': [],
77 'bookmarks': [],
78 'tags': []
79 }
80 }
81 commits.append(cs_data)
68 82
69 cs_data = cs.__json__()
70 cs_data['refs'] = cs._get_refs()
83 elif commit_id.startswith('delete_branch=>'):
84 raw_id = commit_id[15:]
85 cs_data = {
86 'raw_id': commit_id, 'short_id': commit_id,
87 'branch': None,
88 'git_ref_change': 'branch_delete',
89 'message': 'Deleted branch {}'.format(raw_id),
90 'author': event.actor.full_contact,
91 'date': datetime.datetime.now(),
92 'refs': {
93 'branches': [],
94 'bookmarks': [],
95 'tags': []
96 }
97 }
98 commits.append(cs_data)
99
100 else:
101 try:
102 cs = vcs_repo.get_changeset(commit_id)
103 except CommitDoesNotExistError:
104 continue # maybe its in next repo
105
106 cs_data = cs.__json__()
107 cs_data['refs'] = cs._get_refs()
108
71 109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
72 110 cs_data['reviewers'] = reviewers
73 111 cs_data['url'] = RepoModel().get_commit_url(
74 112 repo, cs_data['raw_id'], request=event.request)
75 113 cs_data['permalink_url'] = RepoModel().get_commit_url(
76 repo, cs_data['raw_id'], request=event.request, permalink=True)
114 repo, cs_data['raw_id'], request=event.request,
115 permalink=True)
77 116 urlified_message, issues_data = process_patterns(
78 117 cs_data['message'], repo.repo_name)
79 118 cs_data['issues'] = issues_data
@@ -85,8 +124,8 b' def _commits_as_dict(event, commit_ids, '
85 124
86 125 needed_commits.remove(commit_id)
87 126
88 except Exception as e:
89 log.exception(e)
127 except Exception:
128 log.exception('Failed to extract commits data')
90 129 # we don't send any commits when crash happens, only full list
91 130 # matters we short circuit then.
92 131 return []
@@ -248,6 +287,7 b' class RepoPushEvent(RepoVCSEvent):'
248 287 def __init__(self, repo_name, pushed_commit_ids, extras):
249 288 super(RepoPushEvent, self).__init__(repo_name, extras)
250 289 self.pushed_commit_ids = pushed_commit_ids
290 self.new_refs = extras.new_refs
251 291
252 292 def as_dict(self):
253 293 data = super(RepoPushEvent, self).as_dict()
@@ -256,6 +296,10 b' class RepoPushEvent(RepoVCSEvent):'
256 296 return '{}/changelog?branch={}'.format(
257 297 data['repo']['url'], branch_name)
258 298
299 def tag_url(tag_name):
300 return '{}/files/{}/'.format(
301 data['repo']['url'], tag_name)
302
259 303 commits = _commits_as_dict(
260 304 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
261 305
@@ -265,8 +309,21 b' class RepoPushEvent(RepoVCSEvent):'
265 309 last_branch = commit['branch']
266 310 issues = _issues_as_dict(commits)
267 311
268 branches = set(
269 commit['branch'] for commit in commits if commit['branch'])
312 branches = set()
313 tags = set()
314 for commit in commits:
315 if commit['refs']['tags']:
316 for tag in commit['refs']['tags']:
317 tags.add(tag)
318 if commit['branch']:
319 branches.add(commit['branch'])
320
321 # maybe we have branches in new_refs ?
322 try:
323 branches = branches.union(set(self.new_refs['branches']))
324 except Exception:
325 pass
326
270 327 branches = [
271 328 {
272 329 'name': branch,
@@ -275,9 +332,24 b' class RepoPushEvent(RepoVCSEvent):'
275 332 for branch in branches
276 333 ]
277 334
335 # maybe we have branches in new_refs ?
336 try:
337 tags = tags.union(set(self.new_refs['tags']))
338 except Exception:
339 pass
340
341 tags = [
342 {
343 'name': tag,
344 'url': tag_url(tag)
345 }
346 for tag in tags
347 ]
348
278 349 data['push'] = {
279 350 'commits': commits,
280 351 'issues': issues,
281 352 'branches': branches,
353 'tags': tags,
282 354 }
283 355 return data
@@ -110,6 +110,11 b' class WebhookHandler(object):'
110 110
111 111 branches_commits = OrderedDict()
112 112 for commit in data['push']['commits']:
113 if commit.get('git_ref_change'):
114 # special case for GIT that allows creating tags,
115 # deleting branches without associated commit
116 continue
117
113 118 if commit['branch'] not in branches_commits:
114 119 branch_commits = {'branch': branch_data[commit['branch']],
115 120 'commits': []}
@@ -378,7 +383,8 b' def post_to_webhook(url_calls, settings)'
378 383
379 384 log.debug('calling Webhook with method: %s, and auth:%s',
380 385 call_method, auth)
381
386 if settings.get('log_data'):
387 log.debug('calling webhook with data: %s', data)
382 388 resp = call_method(url, json={
383 389 'token': token,
384 390 'event': data
@@ -404,7 +404,6 b' class RepoIntegrationsView(IntegrationSe'
404 404 c.repo_name = self.db_repo.repo_name
405 405 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
406 406
407
408 407 return c
409 408
410 409 @LoginRequired()
@@ -198,7 +198,6 b' class IntegrationOptionsSchemaBase(colan'
198 198 )
199 199
200 200
201
202 201 def make_integration_schema(IntegrationType, settings=None):
203 202 """
204 203 Return a colander schema for an integration type
@@ -21,6 +21,7 b''
21 21
22 22 import pytest
23 23 from rhodecode import events
24 from rhodecode.lib.utils2 import AttributeDict
24 25
25 26
26 27 @pytest.fixture
@@ -34,7 +35,7 b' def repo_push_event(backend, user_regula'
34 35 ]
35 36 commit_ids = backend.create_master_repo(commits).values()
36 37 repo = backend.create_repo()
37 scm_extras = {
38 scm_extras = AttributeDict({
38 39 'ip': '127.0.0.1',
39 40 'username': user_regular.username,
40 41 'user_id': user_regular.user_id,
@@ -46,7 +47,7 b' def repo_push_event(backend, user_regula'
46 47 'make_lock': None,
47 48 'locked_by': [None],
48 49 'commit_ids': commit_ids,
49 }
50 })
50 51
51 52 return events.RepoPushEvent(repo_name=repo.repo_name,
52 53 pushed_commit_ids=commit_ids,
@@ -77,12 +77,13 b' class Command(object):'
77 77 assert self.process.returncode == 0
78 78
79 79
80 def _add_files_and_push(vcs, dest, clone_url=None, **kwargs):
80 def _add_files_and_push(vcs, dest, clone_url=None, tags=None, **kwargs):
81 81 """
82 82 Generate some files, add it to DEST repo and push back
83 83 vcs is git or hg and defines what VCS we want to make those files for
84 84 """
85 85 # commit some stuff into this repo
86 tags = tags or []
86 87 cwd = path = jn(dest)
87 88 added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next())
88 89 Command(cwd).execute('touch %s' % added_file)
@@ -92,7 +93,7 b' def _add_files_and_push(vcs, dest, clone'
92 93 git_ident = "git config user.name {} && git config user.email {}".format(
93 94 'Marcin KuΕΊminski', 'me@email.com')
94 95
95 for i in xrange(kwargs.get('files_no', 3)):
96 for i in range(kwargs.get('files_no', 3)):
96 97 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
97 98 Command(cwd).execute(cmd)
98 99 if vcs == 'hg':
@@ -104,6 +105,22 b' def _add_files_and_push(vcs, dest, clone'
104 105 git_ident, i, added_file)
105 106 Command(cwd).execute(cmd)
106 107
108 for tag in tags:
109 if vcs == 'hg':
110 stdout, stderr = Command(cwd).execute(
111 'hg tag', tag['name'])
112 elif vcs == 'git':
113 if tag['commit']:
114 # annotated tag
115 stdout, stderr = Command(cwd).execute(
116 """%s && git tag -a %s -m "%s" """ % (
117 git_ident, tag['name'], tag['commit']))
118 else:
119 # lightweight tag
120 stdout, stderr = Command(cwd).execute(
121 """%s && git tag %s""" % (
122 git_ident, tag['name']))
123
107 124 # PUSH it back
108 125 stdout = stderr = None
109 126 if vcs == 'hg':
@@ -111,7 +128,8 b' def _add_files_and_push(vcs, dest, clone'
111 128 'hg push --verbose', clone_url)
112 129 elif vcs == 'git':
113 130 stdout, stderr = Command(cwd).execute(
114 """%s && git push --verbose %s master""" % (
131 """%s &&
132 git push --verbose --tags %s master""" % (
115 133 git_ident, clone_url))
116 134
117 135 return stdout, stderr
@@ -57,20 +57,24 b' def assert_no_running_instance(url):'
57 57 "Port is not free at %s, cannot start web interface" % url)
58 58
59 59
60 def get_port(pyramid_config):
61 config = ConfigParser.ConfigParser()
62 config.read(pyramid_config)
63 return config.get('server:main', 'port')
64
65
60 66 def get_host_url(pyramid_config):
61 67 """Construct the host url using the port in the test configuration."""
62 config = ConfigParser.ConfigParser()
63 config.read(pyramid_config)
64
65 return '127.0.0.1:%s' % config.get('server:main', 'port')
68 return '127.0.0.1:%s' % get_port(pyramid_config)
66 69
67 70
68 71 class RcWebServer(object):
69 72 """
70 73 Represents a running RCE web server used as a test fixture.
71 74 """
72 def __init__(self, pyramid_config):
75 def __init__(self, pyramid_config, log_file):
73 76 self.pyramid_config = pyramid_config
77 self.log_file = log_file
74 78
75 79 def repo_clone_url(self, repo_name, **kwargs):
76 80 params = {
@@ -86,6 +90,10 b' class RcWebServer(object):'
86 90 def host_url(self):
87 91 return 'http://' + get_host_url(self.pyramid_config)
88 92
93 def get_rc_log(self):
94 with open(self.log_file) as f:
95 return f.read()
96
89 97
90 98 @pytest.fixture(scope="module")
91 99 def rcextensions(request, baseapp, tmpdir_factory):
@@ -155,12 +163,11 b' def rc_web_server('
155 163 env = os.environ.copy()
156 164 env['RC_NO_TMP_PATH'] = '1'
157 165
158 rc_log = RC_LOG
159 server_out = open(rc_log, 'w')
166 rc_log = list(RC_LOG.partition('.log'))
167 rc_log.insert(1, get_port(rc_web_server_config))
168 rc_log = ''.join(rc_log)
160 169
161 # TODO: Would be great to capture the output and err of the subprocess
162 # and make it available in a section of the py.test report in case of an
163 # error.
170 server_out = open(rc_log, 'w')
164 171
165 172 host_url = 'http://' + get_host_url(rc_web_server_config)
166 173 assert_no_running_instance(host_url)
@@ -184,7 +191,7 b' def rc_web_server('
184 191 server_out.flush()
185 192 server_out.close()
186 193
187 return RcWebServer(rc_web_server_config)
194 return RcWebServer(rc_web_server_config, log_file=rc_log)
188 195
189 196
190 197 @pytest.fixture
General Comments 0
You need to be logged in to leave comments. Login now