github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/scenarios/kubernetes_bazel.py (about) 1 #!/usr/bin/env python 2 3 # Copyright 2017 The Kubernetes Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """Runs bazel build/test for current repo.""" 18 19 import argparse 20 import os 21 import subprocess 22 import sys 23 24 ORIG_CWD = os.getcwd() 25 26 def test_infra(*paths): 27 """Return path relative to root of test-infra repo.""" 28 return os.path.join(ORIG_CWD, os.path.dirname(__file__), '..', *paths) 29 30 def check(*cmd): 31 """Log and run the command, raising on errors.""" 32 print >>sys.stderr, 'Run:', cmd 33 subprocess.check_call(cmd) 34 35 def check_output(*cmd): 36 """Log and run the command, raising on errors, return output""" 37 print >>sys.stderr, 'Run:', cmd 38 return subprocess.check_output(cmd) 39 40 def upload_string(gcs_path, text): 41 """Uploads text to gcs_path""" 42 cmd = ['gsutil', '-q', '-h', 'Content-Type:text/plain', 'cp', '-', gcs_path] 43 print >>sys.stderr, 'Run:', cmd, 'stdin=%s'%text 44 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) 45 proc.communicate(input=text) 46 47 def echo_result(res): 48 """echo error message bazed on value of res""" 49 echo_map = { 50 0:'Success', 51 1:'Build failed', 52 2:'Bad environment or flags', 53 3:'Build passed, tests failed or timed out', 54 4:'Build passed, no tests found', 55 5:'Interrupted' 56 } 57 print echo_map.get(res, 'Unknown exit code : %s' % res) 58 59 def get_version(): 60 """Return kubernetes version""" 61 with open('bazel-genfiles/version') as fp: 62 return fp.read().strip() 63 64 def get_changed(base, pull): 65 """Get affected packages between base sha and pull sha.""" 66 diff = check_output( 67 'git', 'diff', '--name-only', 68 '--diff-filter=d', '%s...%s' % (base, pull)) 69 return check_output( 70 'bazel', 'query', 71 '--noshow_progress', 72 'set(%s)' % diff).split('\n') 73 74 def query(kind, selected_pkgs, changed_pkgs): 75 """ 76 Run a bazel query against target kind, include targets from args. 77 78 Returns a list of kind objects from bazel query. 79 """ 80 81 # Changes are calculated and no packages found, return empty list. 82 if changed_pkgs == []: 83 return [] 84 85 selection = '//...' 86 if selected_pkgs: 87 selection = 'set(%s)' % ' '.join(selected_pkgs) 88 89 changes = '//...' 90 if changed_pkgs: 91 changes = 'set(%s)' % ' '.join(changed_pkgs) 92 93 return filter(None, check_output( 94 'bazel', 'query', 95 '--keep_going', 96 '--noshow_progress', 97 'kind(%s, rdeps(%s, %s))' % (kind, selection, changes) 98 ).split('\n')) 99 100 101 def clean_file_in_dir(dirname, filename): 102 """Recursively remove all file with filename in dirname.""" 103 for parent, _, filenames in os.walk(dirname): 104 for name in filenames: 105 if name == filename: 106 os.remove(os.path.join(parent, name)) 107 108 def main(args): 109 """Trigger a bazel build/test run, and upload results.""" 110 # pylint:disable=too-many-branches, too-many-statements, too-many-locals 111 if args.install: 112 for install in args.install: 113 if not os.path.isfile(install): 114 raise ValueError('Invalid install path: %s' % install) 115 check('pip', 'install', '-r', install) 116 117 check('bazel', 'clean', '--expunge') 118 res = 0 119 try: 120 affected = None 121 if args.affected: 122 base = os.getenv('PULL_BASE_SHA', '') 123 pull = os.getenv('PULL_PULL_SHA', 'HEAD') 124 if not base: 125 raise ValueError('PULL_BASE_SHA must be set!') 126 affected = get_changed(base, pull) 127 128 build_pkgs = None 129 test_pkgs = None 130 if args.build: 131 build_pkgs = args.build.split(' ') 132 if args.test: 133 test_pkgs = args.test.split(' ') 134 135 buildables = [] 136 if build_pkgs or affected: 137 buildables = query('.*_binary', build_pkgs, affected) 138 139 if buildables: 140 check('bazel', 'build', *buildables) 141 else: 142 # Call bazel build regardless, to establish bazel symlinks 143 check('bazel', 'build') 144 145 # clean up previous test.xml 146 clean_file_in_dir('./bazel-testlogs', 'test.xml') 147 148 if args.release: 149 check('bazel', 'build', *args.release.split(' ')) 150 151 if test_pkgs or affected: 152 tests = query('test', test_pkgs, affected) 153 if tests: 154 if args.test_args: 155 tests = args.test_args + tests 156 check('bazel', 'test', *tests) 157 except subprocess.CalledProcessError as exp: 158 res = exp.returncode 159 160 if args.release and res == 0: 161 version = get_version() 162 if not version: 163 print 'Kubernetes version missing; not uploading ci artifacts.' 164 res = 1 165 else: 166 try: 167 gcs_build = '%s/%s' % (args.gcs, version) 168 check('bazel', 'run', '//:push-build', '--', gcs_build) 169 # log push-build location to path child jobs can find 170 # (gs://<shared-bucket>/$PULL_REFS/bazel-build-location.txt) 171 pull_refs = os.getenv('PULL_REFS', '') 172 gcs_shared = os.path.join(args.gcs_shared, pull_refs, 'bazel-build-location.txt') 173 if pull_refs: 174 upload_string(gcs_shared, gcs_build) 175 except subprocess.CalledProcessError as exp: 176 res = exp.returncode 177 178 # Coalesce test results into one file for upload. 179 check(test_infra('images/bazelbuild/coalesce.py')) 180 181 echo_result(res) 182 if res != 0: 183 sys.exit(res) 184 185 186 def create_parser(): 187 """Create argparser.""" 188 parser = argparse.ArgumentParser() 189 parser.add_argument( 190 '--affected', action='store_true', 191 help='If build/test affected targets. Filtered by --build and --test flags.') 192 parser.add_argument( 193 '--build', help='Bazel build targets, split by one space') 194 # TODO(krzyzacy): Convert to bazel build rules 195 parser.add_argument( 196 '--install', action="append", help='Python dependency(s) that need to be installed') 197 parser.add_argument( 198 '--release', help='Run bazel build, and push release build to --gcs bucket') 199 parser.add_argument( 200 '--gcs-shared', 201 default="gs://kubernetes-jenkins/shared-results/", 202 help='If $PULL_REFS is set push build location to this bucket') 203 parser.add_argument( 204 '--test', help='Bazel test targets, split by one space') 205 parser.add_argument( 206 '--test-args', action="append", help='Bazel test args') 207 parser.add_argument( 208 '--gcs', 209 default='gs://kubernetes-release-dev/bazel', 210 help='GCS path for where to push build') 211 return parser 212 213 def parse_args(args=None): 214 """Return parsed args.""" 215 parser = create_parser() 216 return parser.parse_args(args) 217 218 if __name__ == '__main__': 219 main(parse_args())