k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/releng/prepare_release_branch.py (about)

     1  #!/usr/bin/env python3
     2  
     3  # Copyright 2019 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  # pylint can't figure out sh.bazel
    18  # pylint: disable=no-member
    19  
    20  import os
    21  import sys
    22  import glob
    23  import re
    24  
    25  import sh
    26  import ruamel.yaml as yaml
    27  
    28  import requests
    29  
    30  TEST_CONFIG_YAML = "test_config.yaml"
    31  JOB_CONFIG = "../config/jobs"
    32  BRANCH_JOB_DIR = "../config/jobs/kubernetes/sig-release/release-branch-jobs"
    33  
    34  max_config_count = 5
    35  min_config_count = 3
    36  
    37  suffixes = ['beta', 'stable1', 'stable2', 'stable3', 'stable4']
    38  
    39  class ToolError(Exception):
    40      pass
    41  
    42  def get_config_files(branch_path):
    43      print("Retrieving config files...")
    44      files = glob.glob(os.path.join(branch_path, "*.yaml"))
    45      return files
    46  
    47  def has_correct_amount_of_configs(branch_path):
    48      print("Checking config count...")
    49      files = get_config_files(branch_path)
    50      if len(files) < min_config_count or len(files) > max_config_count:
    51          print(
    52              "Expected between %s and %s yaml files in %s, but found %s" % (
    53                  min_config_count,
    54                  max_config_count,
    55                  branch_path,
    56                  len(files),
    57                  )
    58              )
    59          return False
    60      return True
    61  
    62  def check_version(branch_path):
    63      print("Checking latest version...")
    64      files = get_config_files(branch_path)
    65      if not has_correct_amount_of_configs(branch_path):
    66          raise ToolError("Incorrect release config count. Cannot continue.")
    67      basenames = [os.path.splitext(os.path.basename(x))[0] for x in files]
    68      numbers = sorted([list(map(int, x.split('.'))) for x in basenames])
    69      lowest = numbers[0]
    70      for i, num in enumerate(numbers):
    71          if num[1] != lowest[1] + i:
    72              raise ToolError("Branches are not sequential.")
    73      return numbers[-1]
    74  
    75  
    76  def delete_stale_branch(branch_path, current_version):
    77      print("Deleting stale branch...")
    78      filename = '%d.%d.yaml' % (current_version[0], current_version[1] - 3)
    79      filepath = os.path.join(branch_path, filename)
    80  
    81      if os.path.exists(filepath):
    82          os.unlink(filepath)
    83      else:
    84          print("the branch config (%s) does not exist" % filename)
    85  
    86  
    87  def rotate_files(rotator_bin, branch_path, current_version):
    88      print("Rotating files...")
    89      for i in range(max_config_count - 1):
    90          filename = '%d.%d.yaml' % (current_version[0], current_version[1] - i)
    91          from_suffix = suffixes[i]
    92          to_suffix = suffixes[i+1]
    93          sh.Command(rotator_bin)(
    94              old=from_suffix,
    95              new=to_suffix,
    96              config_file=os.path.join(branch_path, filename),
    97              _fg=True)
    98  
    99  
   100  def fork_new_file(forker_bin, branch_path, prowjob_path, current_version, go_version):
   101      print("Forking new file...")
   102      next_version = (current_version[0], current_version[1] + 1)
   103      filename = '%d.%d.yaml' % (next_version[0], next_version[1])
   104      sh.Command(forker_bin)(
   105          job_config=os.path.abspath(prowjob_path),
   106          output=os.path.abspath(os.path.join(branch_path, filename)),
   107          version='%d.%d' % next_version,
   108          go_version=go_version,
   109          _fg=True)
   110  
   111  
   112  def update_generated_config(path, latest_version):
   113      print("Updating test_config.yaml...")
   114      with open(path, 'r') as f:
   115          config = yaml.round_trip_load(f)
   116  
   117      v = latest_version
   118      for i, s in enumerate(suffixes):
   119          vs = "%d.%d" % (v[0], v[1] + 1 - i)
   120          markers = config['k8sVersions'][s]
   121          markers['version'] = vs
   122          for j, arg in enumerate(markers['args']):
   123              markers['args'][j] = re.sub(
   124                  r'latest(-\d+\.\d+)?', 'latest-%s' % vs, arg)
   125  
   126          node = config['nodeK8sVersions'][s]
   127          for k, arg in enumerate(node['args']):
   128              node['args'][k] = re.sub(
   129                  r'master|release-\d+\.\d+', 'release-%s' % vs, arg)
   130          node['prowImage'] = node['prowImage'].rpartition('-')[0] + '-' + vs
   131  
   132      with open(path, 'w') as f:
   133          yaml.round_trip_dump(config, f)
   134  
   135  
   136  def regenerate_files(generate_tests_bin, test_config):
   137      print("Regenerating files...")
   138      sh.Command(generate_tests_bin)(
   139          yaml_config_path=test_config,
   140          _fg=True)
   141  
   142  def go_version_kubernetes_master():
   143      resp = requests.get(
   144          'https://raw.githubusercontent.com/kubernetes/kubernetes/master/.go-version')
   145      resp.raise_for_status()
   146      data = resp.content.decode("utf-8")
   147      return data
   148  
   149  def main():
   150      if os.environ.get('BUILD_WORKSPACE_DIRECTORY'):
   151          print("Please run me via make rule!")
   152          print("make -C releng prepare-release-branch")
   153          sys.exit(1)
   154      rotator_bin = sys.argv[1]
   155      forker_bin = sys.argv[2]
   156      generate_tests_bin = sys.argv[3]
   157  
   158      cur_file_dir = os.path.dirname(__file__)
   159      branch_job_dir_abs = os.path.join(cur_file_dir, BRANCH_JOB_DIR)
   160  
   161      version = check_version(branch_job_dir_abs)
   162      print("Current version: %d.%d" % (version[0], version[1]))
   163  
   164      go_version = go_version_kubernetes_master()
   165      print("Current Go Version: %s" % go_version)
   166  
   167      files = get_config_files(branch_job_dir_abs)
   168      if len(files) > max_config_count:
   169          print("There should be a maximum of %s release branch configs." % max_config_count)
   170          print("Deleting the oldest config before rotation...")
   171  
   172          delete_stale_branch(branch_job_dir_abs, version)
   173  
   174      rotate_files(rotator_bin, branch_job_dir_abs, version)
   175  
   176      fork_new_file(forker_bin, branch_job_dir_abs,
   177                    os.path.join(cur_file_dir, JOB_CONFIG), version, go_version)
   178  
   179      update_generated_config(os.path.join(cur_file_dir, TEST_CONFIG_YAML), version)
   180  
   181      regenerate_files(generate_tests_bin, os.path.join(cur_file_dir, TEST_CONFIG_YAML))
   182  
   183  
   184  if __name__ == "__main__":
   185      main()