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())