37274de7a2
* Download hello package from S3 for apt test. (cherry picked from commit83fd82ca7e
) * Fix passing of env vars to Shippable. (cherry picked from commit9979a32e5c
) * Use correct interpreter for ansible-test injector. (cherry picked from commiteec21a3d12
) * Correct ansible-test injector python behavior. Inject a symlink to the correct python into the copied injector directory instead of altering the shebang of the injector. This has the side-effect of also intercepting `python` for integration tests which simplifies cases where it needs to be directly invoked without collecting code coverage. (cherry picked from commitd6bf45cd9d
) * Fix ansible-test merge change detection. (cherry picked from commitaa7fe919d3
) * Fix ansible-test interpreter tracking. Track the interpreter for each copy of the injector by the interpreter path instead of the interpreter version. This avoids the possibility of mixing different interpreters with the same version. (cherry picked from commitfa53b4805b
) * Use `state: latest` for `dpkg_selections` test. We don't need to test with `upgrade: dist`, since we're not trying to test the `apt` module. We just need to make sure the hold set by the `dpkg_selections` module is working. This change will avoid updating all the packages on the system, which is slow, unnecessary, and can cause the installed python to be changed. (cherry picked from commit136a2cca2f
)
175 lines
5.7 KiB
Python
175 lines
5.7 KiB
Python
"""Detect changes in Ansible code."""
|
|
|
|
from __future__ import absolute_import, print_function
|
|
|
|
import re
|
|
import os
|
|
|
|
from lib.util import (
|
|
ApplicationError,
|
|
SubprocessError,
|
|
MissingEnvironmentVariable,
|
|
CommonConfig,
|
|
display,
|
|
)
|
|
|
|
from lib.http import (
|
|
HttpClient,
|
|
urlencode,
|
|
)
|
|
|
|
from lib.git import (
|
|
Git,
|
|
)
|
|
|
|
|
|
class InvalidBranch(ApplicationError):
|
|
"""Exception for invalid branch specification."""
|
|
def __init__(self, branch, reason):
|
|
"""
|
|
:type branch: str
|
|
:type reason: str
|
|
"""
|
|
message = 'Invalid branch: %s\n%s' % (branch, reason)
|
|
|
|
super(InvalidBranch, self).__init__(message)
|
|
|
|
self.branch = branch
|
|
|
|
|
|
class ChangeDetectionNotSupported(ApplicationError):
|
|
"""Exception for cases where change detection is not supported."""
|
|
pass
|
|
|
|
|
|
class ShippableChanges(object):
|
|
"""Change information for Shippable build."""
|
|
def __init__(self, args, git):
|
|
"""
|
|
:type args: CommonConfig
|
|
:type git: Git
|
|
"""
|
|
self.args = args
|
|
|
|
try:
|
|
self.branch = os.environ['BRANCH']
|
|
self.is_pr = os.environ['IS_PULL_REQUEST'] == 'true'
|
|
self.is_tag = os.environ['IS_GIT_TAG'] == 'true'
|
|
self.commit = os.environ['COMMIT']
|
|
self.project_id = os.environ['PROJECT_ID']
|
|
self.commit_range = os.environ['SHIPPABLE_COMMIT_RANGE']
|
|
except KeyError as ex:
|
|
raise MissingEnvironmentVariable(name=ex.args[0])
|
|
|
|
if self.is_tag:
|
|
raise ChangeDetectionNotSupported('Change detection is not supported for tags.')
|
|
|
|
if self.is_pr:
|
|
self.paths = sorted(git.get_diff_names([self.commit_range]))
|
|
self.diff = git.get_diff([self.commit_range])
|
|
else:
|
|
merge_runs = self.get_merge_runs(self.project_id, self.branch)
|
|
last_successful_commit = self.get_last_successful_commit(git, merge_runs)
|
|
|
|
if last_successful_commit:
|
|
self.paths = sorted(git.get_diff_names([last_successful_commit, self.commit]))
|
|
self.diff = git.get_diff([last_successful_commit, self.commit])
|
|
else:
|
|
# first run for branch
|
|
self.paths = None # act as though change detection not enabled, do not filter targets
|
|
self.diff = []
|
|
|
|
def get_merge_runs(self, project_id, branch):
|
|
"""
|
|
:type project_id: str
|
|
:type branch: str
|
|
:rtype: list[dict]
|
|
"""
|
|
params = dict(
|
|
isPullRequest='false',
|
|
projectIds=project_id,
|
|
branch=branch,
|
|
)
|
|
|
|
client = HttpClient(self.args, always=True)
|
|
response = client.get('https://api.shippable.com/runs?%s' % urlencode(params))
|
|
return response.json()
|
|
|
|
@staticmethod
|
|
def get_last_successful_commit(git, merge_runs):
|
|
"""
|
|
:type git: Git
|
|
:type merge_runs: dict | list[dict]
|
|
:rtype: str
|
|
"""
|
|
if 'id' in merge_runs and merge_runs['id'] == 4004:
|
|
display.warning('Unable to find project. Cannot determine changes. All tests will be executed.')
|
|
return None
|
|
|
|
successful_commits = set(run['commitSha'] for run in merge_runs if run['statusCode'] == 30)
|
|
commit_history = git.get_rev_list(max_count=100)
|
|
ordered_successful_commits = [commit for commit in commit_history if commit in successful_commits]
|
|
last_successful_commit = ordered_successful_commits[0] if ordered_successful_commits else None
|
|
|
|
if last_successful_commit is None:
|
|
display.warning('No successful commit found. All tests will be executed.')
|
|
|
|
return last_successful_commit
|
|
|
|
|
|
class LocalChanges(object):
|
|
"""Change information for local work."""
|
|
def __init__(self, args, git):
|
|
"""
|
|
:type args: CommonConfig
|
|
:type git: Git
|
|
"""
|
|
self.args = args
|
|
self.current_branch = git.get_branch()
|
|
|
|
if self.is_official_branch(self.current_branch):
|
|
raise InvalidBranch(branch=self.current_branch,
|
|
reason='Current branch is not a feature branch.')
|
|
|
|
self.fork_branch = None
|
|
self.fork_point = None
|
|
|
|
self.local_branches = sorted(git.get_branches())
|
|
self.official_branches = sorted([b for b in self.local_branches if self.is_official_branch(b)])
|
|
|
|
for self.fork_branch in self.official_branches:
|
|
try:
|
|
self.fork_point = git.get_branch_fork_point(self.fork_branch)
|
|
break
|
|
except SubprocessError:
|
|
pass
|
|
|
|
if self.fork_point is None:
|
|
raise ApplicationError('Unable to auto-detect fork branch and fork point.')
|
|
|
|
# tracked files (including unchanged)
|
|
self.tracked = sorted(git.get_file_names(['--cached']))
|
|
# untracked files (except ignored)
|
|
self.untracked = sorted(git.get_file_names(['--others', '--exclude-standard']))
|
|
# tracked changes (including deletions) committed since the branch was forked
|
|
self.committed = sorted(git.get_diff_names([self.fork_point, 'HEAD']))
|
|
# tracked changes (including deletions) which are staged
|
|
self.staged = sorted(git.get_diff_names(['--cached']))
|
|
# tracked changes (including deletions) which are not staged
|
|
self.unstaged = sorted(git.get_diff_names([]))
|
|
# diff of all tracked files from fork point to working copy
|
|
self.diff = git.get_diff([self.fork_point])
|
|
|
|
@staticmethod
|
|
def is_official_branch(name):
|
|
"""
|
|
:type name: str
|
|
:rtype: bool
|
|
"""
|
|
if name == 'devel':
|
|
return True
|
|
|
|
if re.match(r'^stable-[0-9]+\.[0-9]+$', name):
|
|
return True
|
|
|
|
return False
|