diff --git a/jenkins/bootstrap.py b/jenkins/bootstrap.py index aabe8c98d7d2..feb6a903ff5f 100755 --- a/jenkins/bootstrap.py +++ b/jenkins/bootstrap.py @@ -143,13 +143,14 @@ def checkout(repo, branch, pull): call([git, 'checkout', 'FETCH_HEAD']) -def start(gsutil, paths, stamp, node_name, version): +def start(gsutil, paths, stamp, node_name, version, git_commit): """Construct and upload started.json.""" data = { 'timestamp': stamp, 'jenkins-node': node_name, 'node': node_name, 'version': version, + 'git-commit': git_commit, } gsutil.upload_json(paths.started, data) @@ -333,17 +334,24 @@ def node(): def find_version(): - """Determine and return the version of the build.""" - version_file = 'version' - if os.path.isfile(version_file): - # e2e tests which download kubernetes use this path: - with open(version_file) as fp: - return fp.read().strip() - - version_script = 'hack/lib/version.sh' - if os.path.isfile(version_script): - cmd = [ - 'bash', '-c', ( + """Determine and return the version and git commit of the build.""" + try: + # First try using kubectl + for loc in ('client/bin', 'platforms/linux/amd64'): + kubectl_bin = os.path.join(loc, 'kubectl') + if os.path.isfile(kubectl_bin): + kubectl_version_output = call([kubectl_bin, 'version', '--client'], output=True) + m = re.match( + 'Client Version: version.Info\{.*GitVersion:"(?P[^"]+)".*GitCommit:"(?P[^"]+)".*\}', + kubectl_version_output) + if m: + return m.group('version'), m.group('commit') + + # If we can't find kubectl, try using the version.sh script + version_script = 'hack/lib/version.sh' + if os.path.isfile(version_script): + cmd = [ + 'bash', '-c', ( """ set -o errexit set -o nounset @@ -351,11 +359,17 @@ def find_version(): source %s kube::version::get_version_vars echo $KUBE_GIT_VERSION +echo $KUBE_GIT_COMMIT """ % version_script) - ] - return call(cmd, output=True).strip() + ] + version_lines = call(cmd, output=True).splitlines() + if len(version_lines) == 2: + return version_lines[0], version_lines[1] + + except subprocess.CalledProcessError: + pass - return 'unknown' + return 'unknown', 'unknown' class Paths(object): # pylint: disable=too-many-instance-attributes,too-few-public-methods @@ -579,7 +593,7 @@ def bootstrap(job, repo, branch, pull, root): os.chdir(root) checkout(repo, branch, pull) logging.info('Configure environment...') - version = find_version() + version, git_commit = find_version() setup_magic_environment(job) setup_credentials() if pull: @@ -588,7 +602,7 @@ def bootstrap(job, repo, branch, pull, root): paths = ci_paths(job, build) gsutil = GSUtil() logging.info('Start %s at %s...', build, version) - start(gsutil, paths, started, node(), version) + start(gsutil, paths, started, node(), version, git_commit) success = False try: cmd = [job_script(job)] diff --git a/jenkins/bootstrap_test.py b/jenkins/bootstrap_test.py index ab3cbf3c92e6..cb062331d066 100755 --- a/jenkins/bootstrap_test.py +++ b/jenkins/bootstrap_test.py @@ -964,6 +964,64 @@ def testAllJobsHaveErrExit(self): '%s not found in %s' % (expected, job_path)) +def FailCall(cmd, **kw): + """Simulate a subprocess exiting nonzero:""" + raise subprocess.CalledProcessError(1, cmd, None) + + +class FindVersionTest(unittest.TestCase): + + test_cases = [ + ('v1.4.1', '33cf7b9acbb2cb7c9c72a10d6636321fb180b159'), + ('v1.5.0-alpha.2.484+04a74570323eae', '04a74570323eae3fc843ca7a6c34c28ada2847a9'), + ] + + def testVersionFromKubectl(self): + for p in ('client/bin', 'platforms/linux/amd64'): + kubectl = os.path.join(p, 'kubectl') + with Stub(os.path, 'isfile', lambda f: f == kubectl): + with Stub(bootstrap, 'call', lambda *a, **kw: kubectl_output): + for test_case in self.test_cases: + # More correct would be to update Major and Minor, but that takes + # effort. + kubectl_output = ( + 'Client Version: version.Info{Major:"1", Minor:"4", ' + 'GitVersion:"%s", GitCommit:"%s", ' + 'GitTreeState:"clean", BuildDate:"2016-10-10T18:19:49Z", ' + 'GoVersion:"go1.6.3", Compiler:"gc", Platform:"linux/amd64"}\n' + % test_case) + self.assertEqual(bootstrap.find_version(), test_case) + + def testKubectlFailures(self): + with Stub(os.path, 'isfile', lambda f: f == 'platforms/linux/amd64/kubectl'): + with Stub(bootstrap, 'call', lambda *a, **kw: 'something bad'): + self.assertEqual(('unknown', 'unknown'), bootstrap.find_version()) + + with Stub(bootstrap, 'call', FailCall): + self.assertEqual(('unknown', 'unknown'), bootstrap.find_version()) + + def testVersionFromHackScript(self): + for test_case in self.test_cases: + script_output = '%s\n%s\n' % test_case + with Stub(os.path, 'isfile', lambda f: f == 'hack/lib/version.sh'): + with Stub(bootstrap, 'call', lambda *a, **kw: script_output): + self.assertEqual(bootstrap.find_version(), test_case) + + def testHackScriptFailures(self): + with Stub(os.path, 'isfile', lambda f: f == 'hack/lib/version.sh'): + with Stub(bootstrap, 'call', lambda *a, **kw: ''): + self.assertEqual(('unknown', 'unknown'), bootstrap.find_version()) + + with Stub(bootstrap, 'call', lambda *a, **kw: 'one\ntwo\nthree\n'): + self.assertEqual(('unknown', 'unknown'), bootstrap.find_version()) + + with Stub(bootstrap, 'call', FailCall): + self.assertEqual(('unknown', 'unknown'), bootstrap.find_version()) + + def testNoKubectlOrHackScript(self): + with Stub(os.path, 'isfile', lambda *a, **kw: False): + self.assertEqual(('unknown', 'unknown'), bootstrap.find_version()) + if __name__ == '__main__': unittest.main()